mirror of
https://github.com/nix-community/home-manager
synced 2024-11-26 22:50:39 +00:00
aerc: improve module (#3150)
* aerc: add space after definitions * aerc: only generate files, if options were set * aerc: improve file permission warning * aerc: remove redundant access to builtins * aerc: allow overwriting of derived values the order of merging the config subsets did not allow the user to specify outgoing, source and password command values, if they were previously derived from the SMTP, IMAP, Maildir etc config. The values from `account.<name>.extraAccounts` now have the highest precedence. Appropriate tests were added as well. * aerc: write primary account first
This commit is contained in:
parent
cbbceb4894
commit
8da1135365
4 changed files with 139 additions and 48 deletions
|
@ -4,7 +4,6 @@ with lib;
|
||||||
|
|
||||||
let
|
let
|
||||||
mapAttrNames = f: attr:
|
mapAttrNames = f: attr:
|
||||||
with builtins;
|
|
||||||
listToAttrs (attrValues (mapAttrs (k: v: {
|
listToAttrs (attrValues (mapAttrs (k: v: {
|
||||||
name = f k;
|
name = f k;
|
||||||
value = v;
|
value = v;
|
||||||
|
@ -59,6 +58,7 @@ in {
|
||||||
See aerc-config(5).
|
See aerc-config(5).
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
extraBinds = mkOption {
|
extraBinds = mkOption {
|
||||||
type = confSections;
|
type = confSections;
|
||||||
default = { };
|
default = { };
|
||||||
|
@ -70,6 +70,7 @@ in {
|
||||||
See <citerefentry><refentrytitle>aerc-config</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
|
See <citerefentry><refentrytitle>aerc-config</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
extraConfig = mkOption {
|
extraConfig = mkOption {
|
||||||
type = confSections;
|
type = confSections;
|
||||||
default = { };
|
default = { };
|
||||||
|
@ -110,14 +111,18 @@ in {
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
mkAccount = name: account:
|
mkAccount = name: account:
|
||||||
let
|
let
|
||||||
nullOrMap = f: v: if v == null then v else f v;
|
nullOrMap = f: v: if v == null then v else f v;
|
||||||
|
|
||||||
optPort = port: if port != null then ":${toString port}" else "";
|
optPort = port: if port != null then ":${toString port}" else "";
|
||||||
|
|
||||||
optAttr = k: v:
|
optAttr = k: v:
|
||||||
if v != null && v != [ ] && v != "" then { ${k} = v; } else { };
|
if v != null && v != [ ] && v != "" then { ${k} = v; } else { };
|
||||||
|
|
||||||
optPwCmd = k: p:
|
optPwCmd = k: p:
|
||||||
optAttr "${k}-cred-cmd" (nullOrMap (builtins.concatStringsSep " ") p);
|
optAttr "${k}-cred-cmd" (nullOrMap (concatStringsSep " ") p);
|
||||||
|
|
||||||
useOauth = auth: builtins.elem auth [ "oauthbearer" "xoauth2" ];
|
useOauth = auth: builtins.elem auth [ "oauthbearer" "xoauth2" ];
|
||||||
|
|
||||||
|
@ -133,6 +138,7 @@ in {
|
||||||
source =
|
source =
|
||||||
"maildir://${config.accounts.email.maildirBasePath}/${cfg.maildir.path}";
|
"maildir://${config.accounts.email.maildirBasePath}/${cfg.maildir.path}";
|
||||||
};
|
};
|
||||||
|
|
||||||
imap = { userName, imap, passwordCommand, aerc, ... }@cfg:
|
imap = { userName, imap, passwordCommand, aerc, ... }@cfg:
|
||||||
let
|
let
|
||||||
loginMethod' =
|
loginMethod' =
|
||||||
|
@ -147,11 +153,14 @@ in {
|
||||||
if imap.tls.useStartTls then "imap" else "imaps${loginMethod'}"
|
if imap.tls.useStartTls then "imap" else "imaps${loginMethod'}"
|
||||||
else
|
else
|
||||||
"imap+insecure";
|
"imap+insecure";
|
||||||
|
|
||||||
port' = optPort imap.port;
|
port' = optPort imap.port;
|
||||||
|
|
||||||
in {
|
in {
|
||||||
source =
|
source =
|
||||||
"${protocol}://${userName}@${imap.host}${port'}${oauthParams'}";
|
"${protocol}://${userName}@${imap.host}${port'}${oauthParams'}";
|
||||||
} // optPwCmd "source" passwordCommand;
|
} // optPwCmd "source" passwordCommand;
|
||||||
|
|
||||||
smtp = { userName, smtp, passwordCommand, ... }@cfg:
|
smtp = { userName, smtp, passwordCommand, ... }@cfg:
|
||||||
let
|
let
|
||||||
loginMethod' =
|
loginMethod' =
|
||||||
|
@ -166,25 +175,32 @@ in {
|
||||||
"smtps${loginMethod'}"
|
"smtps${loginMethod'}"
|
||||||
else
|
else
|
||||||
"smtp${loginMethod'}";
|
"smtp${loginMethod'}";
|
||||||
|
|
||||||
port' = optPort smtp.port;
|
port' = optPort smtp.port;
|
||||||
|
|
||||||
smtp-starttls =
|
smtp-starttls =
|
||||||
if smtp.tls.enable && smtp.tls.useStartTls then "yes" else null;
|
if smtp.tls.enable && smtp.tls.useStartTls then "yes" else null;
|
||||||
|
|
||||||
in {
|
in {
|
||||||
outgoing =
|
outgoing =
|
||||||
"${protocol}://${userName}@${smtp.host}${port'}${oauthParams'}";
|
"${protocol}://${userName}@${smtp.host}${port'}${oauthParams'}";
|
||||||
} // optPwCmd "outgoing" passwordCommand
|
} // optPwCmd "outgoing" passwordCommand
|
||||||
// optAttr "smtp-starttls" smtp-starttls;
|
// optAttr "smtp-starttls" smtp-starttls;
|
||||||
|
|
||||||
msmtp = cfg: {
|
msmtp = cfg: {
|
||||||
outgoing = "msmtpq --read-envelope-from --read-recipients";
|
outgoing = "msmtpq --read-envelope-from --read-recipients";
|
||||||
};
|
};
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
basicCfg = account:
|
basicCfg = account:
|
||||||
{
|
{
|
||||||
from = "${account.realName} <${account.address}>";
|
from = "${account.realName} <${account.address}>";
|
||||||
} // (optAttr "copy-to" account.folders.sent)
|
} // (optAttr "copy-to" account.folders.sent)
|
||||||
// (optAttr "default" account.folders.inbox)
|
// (optAttr "default" account.folders.inbox)
|
||||||
// (optAttr "postpone" account.folders.drafts)
|
// (optAttr "postpone" account.folders.drafts)
|
||||||
// (optAttr "aliases" account.aliases) // account.aerc.extraAccounts;
|
// (optAttr "aliases" account.aliases);
|
||||||
|
|
||||||
sourceCfg = account:
|
sourceCfg = account:
|
||||||
if account.mbsync.enable || account.offlineimap.enable then
|
if account.mbsync.enable || account.offlineimap.enable then
|
||||||
mkConfig.maildir account
|
mkConfig.maildir account
|
||||||
|
@ -192,6 +208,7 @@ in {
|
||||||
mkConfig.imap account
|
mkConfig.imap account
|
||||||
else
|
else
|
||||||
{ };
|
{ };
|
||||||
|
|
||||||
outgoingCfg = account:
|
outgoingCfg = account:
|
||||||
if account.msmtp.enable then
|
if account.msmtp.enable then
|
||||||
mkConfig.msmtp account
|
mkConfig.msmtp account
|
||||||
|
@ -199,9 +216,13 @@ in {
|
||||||
mkConfig.smtp account
|
mkConfig.smtp account
|
||||||
else
|
else
|
||||||
{ };
|
{ };
|
||||||
in (basicCfg account) // (sourceCfg account) // (outgoingCfg account);
|
|
||||||
|
in (basicCfg account) // (sourceCfg account) // (outgoingCfg account)
|
||||||
|
// account.aerc.extraAccounts;
|
||||||
|
|
||||||
mkAccountConfig = name: account:
|
mkAccountConfig = name: account:
|
||||||
mapAttrNames (addAccountName name) account.aerc.extraConfig;
|
mapAttrNames (addAccountName name) account.aerc.extraConfig;
|
||||||
|
|
||||||
mkAccountBinds = name: account:
|
mkAccountBinds = name: account:
|
||||||
mapAttrNames (addAccountName name) account.aerc.extraBinds;
|
mapAttrNames (addAccountName name) account.aerc.extraBinds;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,24 +3,32 @@
|
||||||
with lib;
|
with lib;
|
||||||
let
|
let
|
||||||
cfg = config.programs.aerc;
|
cfg = config.programs.aerc;
|
||||||
|
|
||||||
primitive = with types;
|
primitive = with types;
|
||||||
((type: either type (listOf type)) (nullOr (oneOf [ str int bool float ])))
|
((type: either type (listOf type)) (nullOr (oneOf [ str int bool float ])))
|
||||||
// {
|
// {
|
||||||
description =
|
description =
|
||||||
"values (null, bool, int, string of float) or a list of values, that will be joined with a comma";
|
"values (null, bool, int, string of float) or a list of values, that will be joined with a comma";
|
||||||
};
|
};
|
||||||
|
|
||||||
confSection = types.attrsOf primitive;
|
confSection = types.attrsOf primitive;
|
||||||
|
|
||||||
confSections = types.attrsOf confSection;
|
confSections = types.attrsOf confSection;
|
||||||
|
|
||||||
sectionsOrLines = types.either types.lines confSections;
|
sectionsOrLines = types.either types.lines confSections;
|
||||||
|
|
||||||
accounts = import ./aerc-accounts.nix {
|
accounts = import ./aerc-accounts.nix {
|
||||||
inherit config pkgs lib confSection confSections;
|
inherit config pkgs lib confSection confSections;
|
||||||
};
|
};
|
||||||
|
|
||||||
aerc-accounts =
|
aerc-accounts =
|
||||||
attrsets.filterAttrs (_: v: v.aerc.enable) config.accounts.email.accounts;
|
attrsets.filterAttrs (_: v: v.aerc.enable) config.accounts.email.accounts;
|
||||||
|
|
||||||
in {
|
in {
|
||||||
meta.maintainers = with lib.hm.maintainers; [ lukasngl ];
|
meta.maintainers = with lib.hm.maintainers; [ lukasngl ];
|
||||||
|
|
||||||
options.accounts.email.accounts = accounts.type;
|
options.accounts.email.accounts = accounts.type;
|
||||||
|
|
||||||
options.programs.aerc = {
|
options.programs.aerc = {
|
||||||
|
|
||||||
enable = mkEnableOption "aerc";
|
enable = mkEnableOption "aerc";
|
||||||
|
@ -70,6 +78,7 @@ in {
|
||||||
See aerc-stylesets(7).
|
See aerc-stylesets(7).
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
templates = mkOption {
|
templates = mkOption {
|
||||||
type = with types; attrsOf lines;
|
type = with types; attrsOf lines;
|
||||||
default = { };
|
default = { };
|
||||||
|
@ -84,16 +93,14 @@ in {
|
||||||
};
|
};
|
||||||
|
|
||||||
config = let
|
config = let
|
||||||
joinCfg = cfgs:
|
joinCfg = cfgs: concatStringsSep "\n" (filter (v: v != "") cfgs);
|
||||||
with builtins;
|
|
||||||
concatStringsSep "\n" (filter (v: v != "") cfgs);
|
|
||||||
toINI = conf: # quirk: global section is prepended w/o section heading
|
toINI = conf: # quirk: global section is prepended w/o section heading
|
||||||
let
|
let
|
||||||
global = conf.global or { };
|
global = conf.global or { };
|
||||||
local = removeAttrs conf [ "global" ];
|
local = removeAttrs conf [ "global" ];
|
||||||
optNewLine = if global != { } && local != { } then "\n" else "";
|
optNewLine = if global != { } && local != { } then "\n" else "";
|
||||||
mkValueString = v:
|
mkValueString = v:
|
||||||
with builtins;
|
|
||||||
if isList v then # join with comma
|
if isList v then # join with comma
|
||||||
concatStringsSep "," (map (generators.mkValueStringDefault { }) v)
|
concatStringsSep "," (map (generators.mkValueStringDefault { }) v)
|
||||||
else
|
else
|
||||||
|
@ -104,64 +111,94 @@ in {
|
||||||
(generators.toKeyValue { inherit mkKeyValue; } global)
|
(generators.toKeyValue { inherit mkKeyValue; } global)
|
||||||
(generators.toINI { inherit mkKeyValue; } local)
|
(generators.toINI { inherit mkKeyValue; } local)
|
||||||
];
|
];
|
||||||
mkINI = conf: if builtins.isString conf then conf else toINI conf;
|
|
||||||
|
mkINI = conf: if isString conf then conf else toINI conf;
|
||||||
|
|
||||||
mkStyleset = attrsets.mapAttrs' (k: v:
|
mkStyleset = attrsets.mapAttrs' (k: v:
|
||||||
let value = if builtins.isString v then v else toINI { global = v; };
|
let value = if isString v then v else toINI { global = v; };
|
||||||
in {
|
in {
|
||||||
name = "aerc/stylesets/${k}";
|
name = "aerc/stylesets/${k}";
|
||||||
value.text = joinCfg [ header value ];
|
value.text = joinCfg [ header value ];
|
||||||
});
|
});
|
||||||
|
|
||||||
mkTemplates = attrsets.mapAttrs' (k: v: {
|
mkTemplates = attrsets.mapAttrs' (k: v: {
|
||||||
name = "aerc/templates/${k}";
|
name = "aerc/templates/${k}";
|
||||||
value.text = v;
|
value.text = v;
|
||||||
});
|
});
|
||||||
accountsExtraAccounts = builtins.mapAttrs accounts.mkAccount aerc-accounts;
|
|
||||||
accountsExtraConfig =
|
primaryAccount = attrsets.filterAttrs (_: v: v.primary) aerc-accounts;
|
||||||
builtins.mapAttrs accounts.mkAccountConfig aerc-accounts;
|
otherAccounts = attrsets.filterAttrs (_: v: !v.primary) aerc-accounts;
|
||||||
accountsExtraBinds =
|
|
||||||
builtins.mapAttrs accounts.mkAccountBinds aerc-accounts;
|
primaryAccountAccounts = mapAttrs accounts.mkAccount primaryAccount;
|
||||||
joinContextual = contextual:
|
|
||||||
with builtins;
|
accountsExtraAccounts = mapAttrs accounts.mkAccount otherAccounts;
|
||||||
joinCfg (map mkINI (attrValues contextual));
|
|
||||||
|
accountsExtraConfig = mapAttrs accounts.mkAccountConfig aerc-accounts;
|
||||||
|
|
||||||
|
accountsExtraBinds = mapAttrs accounts.mkAccountBinds aerc-accounts;
|
||||||
|
|
||||||
|
joinContextual = contextual: joinCfg (map mkINI (attrValues contextual));
|
||||||
|
|
||||||
|
isRecursivelyEmpty = x:
|
||||||
|
if isAttrs x then
|
||||||
|
all (x: x == { } || isRecursivelyEmpty x) (attrValues x)
|
||||||
|
else
|
||||||
|
false;
|
||||||
|
|
||||||
|
genAccountsConf = ((cfg.extraAccounts != "" && cfg.extraAccounts != { })
|
||||||
|
|| !(isRecursivelyEmpty accountsExtraAccounts)
|
||||||
|
|| !(isRecursivelyEmpty primaryAccountAccounts));
|
||||||
|
|
||||||
|
genAercConf = ((cfg.extraConfig != "" && cfg.extraConfig != { })
|
||||||
|
|| !(isRecursivelyEmpty accountsExtraConfig));
|
||||||
|
|
||||||
|
genBindsConf = ((cfg.extraBinds != "" && cfg.extraBinds != { })
|
||||||
|
|| !(isRecursivelyEmpty accountsExtraBinds));
|
||||||
|
|
||||||
header = ''
|
header = ''
|
||||||
# Generated by Home Manager.
|
# Generated by Home Manager.
|
||||||
'';
|
'';
|
||||||
|
|
||||||
in mkIf cfg.enable {
|
in mkIf cfg.enable {
|
||||||
warnings = if ((cfg.extraAccounts != "" && cfg.extraAccounts != { })
|
warnings = if genAccountsConf
|
||||||
|| accountsExtraAccounts != { })
|
|
||||||
&& (cfg.extraConfig.general.unsafe-accounts-conf or false) == false then [''
|
&& (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.
|
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.
|
This will prevent aerc from starting, see `unsafe-accounts-conf` in the man page aerc-config(5), which states:
|
||||||
Consider setting the option `extraConfig.general.unsafe-accounts-conf` to true.
|
> By default, the file permissions of accounts.conf must be restrictive and only allow reading by the file owner (0600).
|
||||||
|
> Set this option to true to ignore this permission check. Use this with care as it may expose your credentials.
|
||||||
|
These file permissions are not possible with home-manger, since the generated file is stored in the nix-store with read-only access for all users (0444).
|
||||||
|
If `passwordCommand` is properly set, no credentials will be stored in the nix store.
|
||||||
|
Therefore, consider setting the option `extraConfig.general.unsafe-accounts-conf` to true.
|
||||||
''] else
|
''] else
|
||||||
[ ];
|
[ ];
|
||||||
|
|
||||||
home.packages = [ cfg.package ];
|
home.packages = [ cfg.package ];
|
||||||
|
|
||||||
xdg.configFile = {
|
xdg.configFile = {
|
||||||
"aerc/accounts.conf" = mkIf
|
"aerc/accounts.conf" = mkIf genAccountsConf {
|
||||||
((cfg.extraAccounts != "" && cfg.extraAccounts != { })
|
text = joinCfg [
|
||||||
|| accountsExtraAccounts != { }) {
|
header
|
||||||
text = joinCfg [
|
(mkINI cfg.extraAccounts)
|
||||||
header
|
(mkINI primaryAccountAccounts)
|
||||||
(mkINI cfg.extraAccounts)
|
(mkINI accountsExtraAccounts)
|
||||||
(mkINI accountsExtraAccounts)
|
];
|
||||||
];
|
};
|
||||||
};
|
|
||||||
"aerc/aerc.conf" =
|
"aerc/aerc.conf" = mkIf genAercConf {
|
||||||
mkIf (cfg.extraConfig != "" && cfg.extraConfig != { }) {
|
text = joinCfg [
|
||||||
text = joinCfg [
|
header
|
||||||
header
|
(mkINI cfg.extraConfig)
|
||||||
(mkINI cfg.extraConfig)
|
(joinContextual accountsExtraConfig)
|
||||||
(joinContextual accountsExtraConfig)
|
];
|
||||||
];
|
};
|
||||||
};
|
|
||||||
"aerc/binds.conf" = mkIf ((cfg.extraBinds != "" && cfg.extraBinds != { })
|
"aerc/binds.conf" = mkIf genBindsConf {
|
||||||
|| accountsExtraBinds != { }) {
|
text = joinCfg [
|
||||||
text = joinCfg [
|
header
|
||||||
header
|
(mkINI cfg.extraBinds)
|
||||||
(mkINI cfg.extraBinds)
|
(joinContextual accountsExtraBinds)
|
||||||
(joinContextual accountsExtraBinds)
|
];
|
||||||
];
|
};
|
||||||
};
|
|
||||||
} // (mkStyleset cfg.stylesets) // (mkTemplates cfg.templates);
|
} // (mkStyleset cfg.stylesets) // (mkTemplates cfg.templates);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,10 @@ source = maildir:///dev/null
|
||||||
[Test2]
|
[Test2]
|
||||||
pgp-key-id = 42
|
pgp-key-id = 42
|
||||||
|
|
||||||
|
[primary]
|
||||||
|
from = Foo Bar <addr@mail.invalid>
|
||||||
|
source = imap://foobar@imap.host.invalid:1337
|
||||||
|
|
||||||
[a_imap-nopasscmd-tls-starttls-folders]
|
[a_imap-nopasscmd-tls-starttls-folders]
|
||||||
copy-to = aercSent
|
copy-to = aercSent
|
||||||
default = aercInbox
|
default = aercInbox
|
||||||
|
@ -74,3 +78,9 @@ outgoing = smtps+login://foobar@smtp.host.invalid:42
|
||||||
[o_msmtp]
|
[o_msmtp]
|
||||||
from = Foo Bar <addr@mail.invalid>
|
from = Foo Bar <addr@mail.invalid>
|
||||||
outgoing = msmtpq --read-envelope-from --read-recipients
|
outgoing = msmtpq --read-envelope-from --read-recipients
|
||||||
|
|
||||||
|
[p_overwrite_defaults]
|
||||||
|
from = test <test@email.invalid>
|
||||||
|
outgoing = imap+plain://intentionallyWrong:PaSsWorD@smtp.host.invalid:1337
|
||||||
|
postpone = dRaFts
|
||||||
|
source = smtp+plain://intentionallyWrong:PaSsWorD@smtp.host.invalid:1337
|
||||||
|
|
|
@ -102,7 +102,7 @@ with lib;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
in {
|
in {
|
||||||
a_imap-nopasscmd-tls-starttls-folders = basics // {
|
primary = basics // {
|
||||||
primary = true;
|
primary = true;
|
||||||
imap = {
|
imap = {
|
||||||
host = "imap.host.invalid";
|
host = "imap.host.invalid";
|
||||||
|
@ -110,6 +110,14 @@ with lib;
|
||||||
tls.enable = true;
|
tls.enable = true;
|
||||||
tls.useStartTls = true;
|
tls.useStartTls = true;
|
||||||
};
|
};
|
||||||
|
};
|
||||||
|
a_imap-nopasscmd-tls-starttls-folders = basics // {
|
||||||
|
imap = {
|
||||||
|
host = "imap.host.invalid";
|
||||||
|
port = 1337;
|
||||||
|
tls.enable = true;
|
||||||
|
tls.useStartTls = true;
|
||||||
|
};
|
||||||
folders = {
|
folders = {
|
||||||
drafts = "aercDrafts";
|
drafts = "aercDrafts";
|
||||||
inbox = "aercInbox";
|
inbox = "aercInbox";
|
||||||
|
@ -224,6 +232,21 @@ with lib;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
o_msmtp = basics // { msmtp = { enable = true; }; };
|
o_msmtp = basics // { msmtp = { enable = true; }; };
|
||||||
|
p_overwrite_defaults = basics // {
|
||||||
|
smtp.host = "should.be.overwritten.invalid";
|
||||||
|
imap.host = "should.be.overwritten.invalid";
|
||||||
|
aerc = {
|
||||||
|
enable = true;
|
||||||
|
extraAccounts = {
|
||||||
|
from = "test <test@email.invalid>";
|
||||||
|
outgoing =
|
||||||
|
"imap+plain://intentionallyWrong:PaSsWorD@smtp.host.invalid:1337";
|
||||||
|
source =
|
||||||
|
"smtp+plain://intentionallyWrong:PaSsWorD@smtp.host.invalid:1337";
|
||||||
|
postpone = "dRaFts";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue