Add module for aerc (#3070)

This adds support for configuring email accounts, with automatic smtp, imap,
sendmail (msmpt) and maildir (mbsync, offlineimap) setup in aerc,
via `accounts.email`.
This commit is contained in:
Lukas Nagel 2022-08-11 23:08:28 +02:00 committed by GitHub
parent c1addfdad3
commit 324fedcf9f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 681 additions and 0 deletions

View file

@ -0,0 +1,131 @@
{ config, lib, pkgs, confSections, confSection, ... }:
with lib;
let
mapAttrNames = f: attr:
with builtins;
listToAttrs (attrValues (mapAttrs (k: v: {
name = f k;
value = v;
}) attr));
addAccountName = name: k: "${k}:account=${name}";
in {
type = mkOption {
type = types.attrsOf (types.submodule {
options.aerc = {
enable = mkEnableOption "aerc";
extraAccounts = mkOption {
type = confSection;
default = { };
example =
literalExpression ''{ source = "maildir://~/Maildir/example"; }'';
description = ''
Extra config added to the configuration of this account in
<filename>$HOME/.config/aerc/accounts.conf</filename>.
See aerc-config(5).
'';
};
extraBinds = mkOption {
type = confSections;
default = { };
example = literalExpression
''{ messages = { d = ":move ''${folder.trash}<Enter>"; }; }'';
description = ''
Extra bindings specific to this account, added to
<filename>$HOME/.config/aerc/accounts.conf</filename>.
See aerc-config(5).
'';
};
extraConfig = mkOption {
type = confSections;
default = { };
example = literalExpression "{ ui = { sidebar-width = 42; }; }";
description = ''
Extra config specific to this account, added to
<filename>$HOME/.config/aerc/aerc.conf</filename>.
See aerc-config(5).
'';
};
smtpAuth = mkOption {
type = with types; nullOr (enum [ "none" "plain" "login" ]);
default = "plain";
example = "auth";
description = ''
Sets the authentication mechanism if smtp is used as the outgoing
method.
See aerc-smtp(5).
'';
};
};
});
};
mkAccount = name: account:
let
nullOrMap = f: v: if v == null then v else f v;
optPort = port: if port != null then ":${toString port}" else "";
optAttr = k: v:
if v != null && v != [ ] && v != "" then { ${k} = v; } else { };
optPwCmd = k: p:
optAttr "${k}-cred-cmd" (nullOrMap (builtins.concatStringsSep " ") p);
mkConfig = {
maildir = cfg: {
source =
"maildir://${config.accounts.email.maildirBasePath}/${cfg.maildir.path}";
};
imap = { userName, imap, passwordCommand, aerc, ... }@cfg:
let
protocol = if imap.tls.enable then
if imap.tls.useStartTls then "imap" else "imaps"
else
"imap+insecure";
port' = optPort imap.port;
in {
source = "${protocol}://${userName}@${imap.host}${port'}";
} // optPwCmd "source" passwordCommand;
smtp = { userName, smtp, passwordCommand, ... }@cfg:
let
loginMethod' =
if cfg.aerc.smtpAuth != null then "+${cfg.aerc.smtpAuth}" else "";
protocol = if smtp.tls.enable && !smtp.tls.useStartTls then
"smtps${loginMethod'}"
else
"smtp${loginMethod'}";
port' = optPort smtp.port;
smtp-starttls =
if smtp.tls.enable && smtp.tls.useStartTls then "yes" else null;
in {
outgoing = "${protocol}://${userName}@${smtp.host}${port'}";
} // optPwCmd "outgoing" passwordCommand
// optAttr "smtp-starttls" smtp-starttls;
msmtp = cfg: {
outgoing = "msmtpq --read-envelope-from --read-recipients";
};
};
basicCfg = account:
{
from = "${account.realName} <${account.address}>";
} // (optAttr "copy-to" account.folders.sent)
// (optAttr "default" account.folders.inbox)
// (optAttr "postpone" account.folders.drafts)
// (optAttr "aliases" account.aliases) // account.aerc.extraAccounts;
sourceCfg = account:
if account.mbsync.enable || account.offlineimap.enable then
mkConfig.maildir account
else if account.imap != null then
mkConfig.imap account
else
{ };
outgoingCfg = account:
if account.msmtp.enable then
mkConfig.msmtp account
else if account.smtp != null then
mkConfig.smtp account
else
{ };
in (basicCfg account) // (sourceCfg account) // (outgoingCfg account);
mkAccountConfig = name: account:
mapAttrNames (addAccountName name) account.aerc.extraConfig;
mkAccountBinds = name: account:
mapAttrNames (addAccountName name) account.aerc.extraBinds;
}

165
modules/programs/aerc.nix Normal file
View file

@ -0,0 +1,165 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.programs.aerc;
primitive = with types;
((type: either type (listOf type)) (nullOr (oneOf [ str int bool float ])))
// {
description =
"values (null, bool, int, string of float) or a list of values, that will be joined with a comma";
};
confSection = types.attrsOf primitive;
confSections = types.attrsOf confSection;
sectionsOrLines = types.either types.lines confSections;
accounts = import ./aerc-accounts.nix {
inherit config pkgs lib confSection confSections;
};
aerc-accounts =
attrsets.filterAttrs (_: v: v.aerc.enable) config.accounts.email.accounts;
in {
meta.maintainers = with lib.hm.maintainers; [ lukasngl ];
options.accounts.email.accounts = accounts.type;
options.programs.aerc = {
enable = mkEnableOption "aerc";
extraAccounts = mkOption {
type = sectionsOrLines;
default = { };
example = literalExpression
''{ Work = { source = "maildir://~/Maildir/work"; }; }'';
description = ''
Extra lines added to <filename>$HOME/.config/aerc/accounts.conf</filename>.
See aerc-config(5).
'';
};
extraBinds = mkOption {
type = sectionsOrLines;
default = { };
example = literalExpression ''{ messages = { q = ":quit<Enter>"; }; }'';
description = ''
Extra lines added to <filename>$HOME/.config/aerc/binds.conf</filename>.
Global keybindings can be set in the `global` section.
See aerc-config(5).
'';
};
extraConfig = mkOption {
type = sectionsOrLines;
default = { };
example = literalExpression ''{ ui = { sort = "-r date"; }; }'';
description = ''
Extra lines added to <filename>$HOME/.config/aerc/aerc.conf</filename>.
See aerc-config(5).
'';
};
stylesets = mkOption {
type = with types; attrsOf (either confSection lines);
default = { };
example = literalExpression ''
{ default = { ui = { "tab.selected.reverse" = toggle; }; }; };
'';
description = ''
Stylesets added to <filename>$HOME/.config/aerc/stylesets/</filename>.
See aerc-stylesets(7).
'';
};
templates = mkOption {
type = with types; attrsOf lines;
default = { };
example = literalExpression ''
{ new_message = "Hello!"; };
'';
description = ''
Templates added to <filename>$HOME/.config/aerc/templates/</filename>.
See aerc-templates(7).
'';
};
};
config = let
joinCfg = cfgs:
with builtins;
concatStringsSep "\n" (filter (v: v != "") cfgs);
toINI = conf: # quirk: global section is prepended w/o section heading
let
global = conf.global or { };
local = removeAttrs conf [ "global" ];
optNewLine = if global != { } && local != { } then "\n" else "";
mkValueString = v:
with builtins;
if isList v then # join with comma
concatStringsSep "," (map (generators.mkValueStringDefault { }) v)
else
generators.mkValueStringDefault { } v;
mkKeyValue =
generators.mkKeyValueDefault { inherit mkValueString; } " = ";
in joinCfg [
(generators.toKeyValue { inherit mkKeyValue; } global)
(generators.toINI { inherit mkKeyValue; } local)
];
mkINI = conf: if builtins.isString conf then conf else toINI conf;
mkStyleset = attrsets.mapAttrs' (k: v:
let value = if builtins.isString v then v else toINI { global = v; };
in {
name = "aerc/stylesets/${k}";
value.text = joinCfg [ header value ];
});
mkTemplates = attrsets.mapAttrs' (k: v: {
name = "aerc/templates/${k}";
value.text = v;
});
accountsExtraAccounts = builtins.mapAttrs accounts.mkAccount aerc-accounts;
accountsExtraConfig =
builtins.mapAttrs accounts.mkAccountConfig aerc-accounts;
accountsExtraBinds =
builtins.mapAttrs accounts.mkAccountBinds aerc-accounts;
joinContextual = contextual:
with builtins;
joinCfg (map mkINI (attrValues contextual));
header = ''
# Generated by Home Manager.
'';
in mkIf cfg.enable {
warnings = if ((cfg.extraAccounts != "" && cfg.extraAccounts != { })
|| accountsExtraAccounts != { })
&& (cfg.extraConfig.general.unsafe-accounts-conf or false) == false then [''
aerc: An email account was configured, but `extraConfig.general.unsafe-accounts-conf` is set to false or unset.
This will prevent aerc from starting, see `unsafe-accounts-conf` in aerc-config(5) for details.
Consider setting the option `extraConfig.general.unsafe-accounts-conf` to true.
''] else
[ ];
home.packages = [ pkgs.aerc ];
xdg.configFile = {
"aerc/accounts.conf" = mkIf
((cfg.extraAccounts != "" && cfg.extraAccounts != { })
|| accountsExtraAccounts != { }) {
text = joinCfg [
header
(mkINI cfg.extraAccounts)
(mkINI accountsExtraAccounts)
];
};
"aerc/aerc.conf" =
mkIf (cfg.extraConfig != "" && cfg.extraConfig != { }) {
text = joinCfg [
header
(mkINI cfg.extraConfig)
(joinContextual accountsExtraConfig)
];
};
"aerc/binds.conf" = mkIf ((cfg.extraBinds != "" && cfg.extraBinds != { })
|| accountsExtraBinds != { }) {
text = joinCfg [
header
(mkINI cfg.extraBinds)
(joinContextual accountsExtraBinds)
];
};
} // (mkStyleset cfg.stylesets) // (mkTemplates cfg.templates);
};
}