1
0
Fork 0
mirror of https://github.com/nix-community/home-manager.git synced 2025-11-08 19:46:05 +01:00

accounts.email: add option to disable an account

Allow a user to disable an email account by setting
`accounts.email.accounts.<name>.enable = false`.  This is useful if
someone wants to configure email accounts globally but only use them in
certain circumstances.

Everywhere email account configuration is used, check if the account is
enabled before checking any attributes of the account.
This commit is contained in:
Adam Dinwoodie 2025-08-05 11:04:49 +01:00 committed by Austin Horstman
parent 07b994baed
commit dbfcd3292d
22 changed files with 164 additions and 26 deletions

View file

@ -9,6 +9,7 @@ let
; ;
cfg = config.accounts.email; cfg = config.accounts.email;
enabledAccounts = lib.filterAttrs (n: v: v.enable) cfg.accounts;
gpgModule = types.submodule { gpgModule = types.submodule {
options = { options = {
@ -353,6 +354,16 @@ let
''; '';
}; };
enable = mkOption {
type = types.bool;
default = true;
description = ''
Whether this account is enabled. Potentially useful to allow
setting email configuration globally then enabling or disabling on
specific systems.
'';
};
flavor = mkOption { flavor = mkOption {
type = types.enum [ type = types.enum [
"davmail" "davmail"
@ -669,11 +680,11 @@ in
}; };
}; };
config = mkIf (cfg.accounts != { }) { config = mkIf (enabledAccounts != { }) {
assertions = [ assertions = [
( (
let let
primaries = lib.catAttrs "name" (lib.filter (a: a.primary) (lib.attrValues cfg.accounts)); primaries = lib.catAttrs "name" (lib.filter (a: a.primary) (lib.attrValues enabledAccounts));
in in
{ {
assertion = lib.length primaries == 1; assertion = lib.length primaries == 1;

View file

@ -4,7 +4,9 @@
time = "2024-12-08T17:22:13+00:00"; time = "2024-12-08T17:22:13+00:00";
condition = condition =
let let
usingMbsync = lib.any (a: a.mbsync.enable) (lib.attrValues config.accounts.email.accounts); usingMbsync = lib.any (a: a.enable && a.mbsync.enable) (
lib.attrValues config.accounts.email.accounts
);
in in
usingMbsync; usingMbsync;
message = '' message = ''

View file

@ -47,7 +47,9 @@ let
; ;
}; };
aerc-accounts = attrsets.filterAttrs (_: v: v.aerc.enable) config.accounts.email.accounts; aerc-accounts = attrsets.filterAttrs (
_: v: v.enable && v.aerc.enable
) config.accounts.email.accounts;
configDir = configDir =
if (pkgs.stdenv.isDarwin && !config.xdg.enable) then if (pkgs.stdenv.isDarwin && !config.xdg.enable) then

View file

@ -17,7 +17,9 @@ let
cfg = config.programs.alot; cfg = config.programs.alot;
enabledAccounts = lib.filter (a: a.notmuch.enable) (lib.attrValues config.accounts.email.accounts); enabledAccounts = lib.filter (a: a.enable && a.notmuch.enable) (
lib.attrValues config.accounts.email.accounts
);
# sorted: primary first # sorted: primary first
alotAccounts = lib.sort (a: b: !(a.primary -> b.primary)) enabledAccounts; alotAccounts = lib.sort (a: b: !(a.primary -> b.primary)) enabledAccounts;

View file

@ -11,7 +11,9 @@ let
jsonFormat = pkgs.formats.json { }; jsonFormat = pkgs.formats.json { };
astroidAccounts = lib.filterAttrs (n: v: v.astroid.enable) config.accounts.email.accounts; astroidAccounts = lib.filterAttrs (
n: v: v.enable && v.astroid.enable
) config.accounts.email.accounts;
boolOpt = b: if b then "true" else "false"; boolOpt = b: if b then "true" else "false";

View file

@ -5,7 +5,9 @@
... ...
}: }:
let let
accounts = lib.filter (a: a.getmail.enable) (lib.attrValues config.accounts.email.accounts); accounts = lib.filter (a: a.enable && a.getmail.enable) (
lib.attrValues config.accounts.email.accounts
);
renderAccountConfig = renderAccountConfig =
account: account:

View file

@ -566,7 +566,7 @@ in
{ {
programs.git.iniContent = programs.git.iniContent =
let let
hasSmtp = name: account: account.smtp != null; hasSmtp = name: account: account.enable && account.smtp != null;
genIdentity = genIdentity =
name: account: name: account:

View file

@ -34,7 +34,7 @@ let
mkAccountConfig = mkAccountConfig =
_: account: _: account:
let let
notmuchEnabled = account.notmuch.enable; notmuchEnabled = account.enable && account.notmuch.enable;
imapEnabled = !isNull account.imap && !notmuchEnabled; imapEnabled = !isNull account.imap && !notmuchEnabled;
maildirEnabled = !isNull account.maildir && !imapEnabled && !notmuchEnabled; maildirEnabled = !isNull account.maildir && !imapEnabled && !notmuchEnabled;
@ -165,7 +165,7 @@ in
configFile."himalaya/config.toml".source = configFile."himalaya/config.toml".source =
let let
enabledAccounts = lib.filterAttrs ( enabledAccounts = lib.filterAttrs (
_: account: account.himalaya.enable _: account: account.enable && account.himalaya.enable
) config.accounts.email.accounts; ) config.accounts.email.accounts;
accountsConfig = lib.mapAttrs mkAccountConfig enabledAccounts; accountsConfig = lib.mapAttrs mkAccountConfig enabledAccounts;
globalConfig = compactAttrs himalaya.settings; globalConfig = compactAttrs himalaya.settings;

View file

@ -15,7 +15,9 @@ let
cfg = config.programs.lieer; cfg = config.programs.lieer;
lieerAccounts = lib.filter (a: a.lieer.enable) (lib.attrValues config.accounts.email.accounts); lieerAccounts = lib.filter (a: a.enable && a.lieer.enable) (
lib.attrValues config.accounts.email.accounts
);
nonGmailAccounts = map (a: a.name) (lib.filter (a: a.flavor != "gmail.com") lieerAccounts); nonGmailAccounts = map (a: a.name) (lib.filter (a: a.flavor != "gmail.com") lieerAccounts);

View file

@ -20,7 +20,9 @@ let
cfg = config.programs.mbsync; cfg = config.programs.mbsync;
# Accounts for which mbsync is enabled. # Accounts for which mbsync is enabled.
mbsyncAccounts = lib.filter (a: a.mbsync.enable) (lib.attrValues config.accounts.email.accounts); mbsyncAccounts = lib.filter (a: a.enable && a.mbsync.enable) (
lib.attrValues config.accounts.email.accounts
);
# Given a SINGLE group's channels attribute set, return true if ANY of the channel's # Given a SINGLE group's channels attribute set, return true if ANY of the channel's
# patterns use the invalidOption attribute set value name. # patterns use the invalidOption attribute set value name.

View file

@ -18,7 +18,7 @@ let
tomlFormat = pkgs.formats.toml { }; tomlFormat = pkgs.formats.toml { };
enabledAccounts = lib.attrsets.filterAttrs ( enabledAccounts = lib.attrsets.filterAttrs (
name: value: value.meli.enable or false name: value: value.enable && (value.meli.enable or false)
) config.accounts.email.accounts; ) config.accounts.email.accounts;
meliAccounts = (lib.attrsets.mapAttrs (name: value: (mkMeliAccounts name value)) enabledAccounts); meliAccounts = (lib.attrsets.mapAttrs (name: value: (mkMeliAccounts name value)) enabledAccounts);

View file

@ -9,7 +9,9 @@ let
cfg = config.programs.msmtp; cfg = config.programs.msmtp;
msmtpAccounts = lib.filter (a: a.msmtp.enable) (lib.attrValues config.accounts.email.accounts); msmtpAccounts = lib.filter (a: a.enable && a.msmtp.enable) (
lib.attrValues config.accounts.email.accounts
);
onOff = p: if p then "on" else "off"; onOff = p: if p then "on" else "off";

View file

@ -14,7 +14,9 @@ let
sortedAddresses = sortedAddresses =
let let
# Set of email account sets where mu.enable = true. # Set of email account sets where mu.enable = true.
muAccounts = lib.filter (a: a.mu.enable) (lib.attrValues config.accounts.email.accounts); muAccounts = lib.filter (a: a.enable && a.mu.enable) (
lib.attrValues config.accounts.email.accounts
);
addrs = map (a: a.address) muAccounts; addrs = map (a: a.address) muAccounts;
# Construct list of lists containing email aliases, and flatten # Construct list of lists containing email aliases, and flatten
aliases = map (alias: alias.address or alias) (lib.flatten (map (a: a.aliases) muAccounts)); aliases = map (alias: alias.address or alias) (lib.flatten (map (a: a.aliases) muAccounts));

View file

@ -9,7 +9,9 @@ let
cfg = config.programs.mujmap; cfg = config.programs.mujmap;
mujmapAccounts = lib.filter (a: a.mujmap.enable) (lib.attrValues config.accounts.email.accounts); mujmapAccounts = lib.filter (a: a.enable && a.mujmap.enable) (
lib.attrValues config.accounts.email.accounts
);
missingNotmuchAccounts = map (a: a.name) ( missingNotmuchAccounts = map (a: a.name) (
lib.filter (a: !a.notmuch.enable && a.mujmap.notmuchSetupWarning) mujmapAccounts lib.filter (a: !a.notmuch.enable && a.mujmap.notmuchSetupWarning) mujmapAccounts

View file

@ -20,7 +20,9 @@ let
cfg = config.programs.neomutt; cfg = config.programs.neomutt;
neomuttAccountsCfg = filterAttrs (n: a: a.neomutt.enable) config.accounts.email.accounts; neomuttAccountsCfg = filterAttrs (
n: a: a.enable && a.neomutt.enable
) config.accounts.email.accounts;
neomuttAccounts = attrValues neomuttAccountsCfg; neomuttAccounts = attrValues neomuttAccountsCfg;
accountCommandNeeded = lib.any ( accountCommandNeeded = lib.any (

View file

@ -47,7 +47,7 @@ let
user = user =
let let
accounts = filter (a: a.notmuch.enable) (lib.attrValues config.accounts.email.accounts); accounts = filter (a: a.enable && a.notmuch.enable) (lib.attrValues config.accounts.email.accounts);
primary = filter (a: a.primary) accounts; primary = filter (a: a.primary) accounts;
secondaries = filter (a: !a.primary) accounts; secondaries = filter (a: !a.primary) accounts;
in in

View file

@ -9,7 +9,9 @@ let
cfg = config.programs.offlineimap; cfg = config.programs.offlineimap;
accounts = lib.filter (a: a.offlineimap.enable) (lib.attrValues config.accounts.email.accounts); accounts = lib.filter (a: a.enable && a.offlineimap.enable) (
lib.attrValues config.accounts.email.accounts
);
toIni = lib.generators.toINI { toIni = lib.generators.toINI {
mkKeyValue = mkKeyValue =

View file

@ -32,16 +32,21 @@ let
moduleName = "programs.thunderbird"; moduleName = "programs.thunderbird";
filterEnabled = accounts: attrValues (lib.filterAttrs (_: a: a.thunderbird.enable) accounts);
addId = map (a: a // { id = builtins.hashString "sha256" a.name; }); addId = map (a: a // { id = builtins.hashString "sha256" a.name; });
enabledEmailAccounts = filterEnabled config.accounts.email.accounts; enabledEmailAccounts = filter (a: a.enable && a.thunderbird.enable) (
attrValues config.accounts.email.accounts
);
enabledEmailAccountsWithId = addId enabledEmailAccounts; enabledEmailAccountsWithId = addId enabledEmailAccounts;
enabledCalendarAccounts = filterEnabled config.accounts.calendar.accounts; enabledCalendarAccounts = filter (a: a.thunderbird.enable) (
attrValues config.accounts.calendar.accounts
);
enabledCalendarAccountsWithId = addId enabledCalendarAccounts; enabledCalendarAccountsWithId = addId enabledCalendarAccounts;
enabledContactAccounts = filterEnabled config.accounts.contact.accounts; enabledContactAccounts = filter (a: a.thunderbird.enable) (
attrValues config.accounts.contact.accounts
);
enabledContactAccountsWithId = addId enabledContactAccounts; enabledContactAccountsWithId = addId enabledContactAccounts;
thunderbirdConfigPath = if isDarwin then "Library/Thunderbird" else ".thunderbird"; thunderbirdConfigPath = if isDarwin then "Library/Thunderbird" else ".thunderbird";

View file

@ -8,7 +8,9 @@ let
cfg = config.services.getmail; cfg = config.services.getmail;
accounts = lib.filter (a: a.getmail.enable) (lib.attrValues config.accounts.email.accounts); accounts = lib.filter (a: a.enable && a.getmail.enable) (
lib.attrValues config.accounts.email.accounts
);
# Note: The getmail service does not expect a path, but just the filename! # Note: The getmail service does not expect a path, but just the filename!
renderConfigFilepath = a: if a.primary then "getmailrc" else "getmail${a.name}"; renderConfigFilepath = a: if a.primary then "getmailrc" else "getmail${a.name}";

View file

@ -18,7 +18,7 @@ let
configName = account: "imapnotify-${safeName account.name}-config.json"; configName = account: "imapnotify-${safeName account.name}-config.json";
imapnotifyAccounts = lib.filter (a: a.imapnotify.enable) ( imapnotifyAccounts = lib.filter (a: a.enable && a.imapnotify.enable) (
lib.attrValues config.accounts.email.accounts lib.attrValues config.accounts.email.accounts
); );

View file

@ -7,7 +7,7 @@
let let
cfg = config.services.lieer; cfg = config.services.lieer;
syncAccounts = lib.filter (a: a.lieer.enable && a.lieer.sync.enable) ( syncAccounts = lib.filter (a: a.enable && a.lieer.enable && a.lieer.sync.enable) (
lib.attrValues config.accounts.email.accounts lib.attrValues config.accounts.email.accounts
); );

View file

@ -1,3 +1,4 @@
{ lib, options, ... }:
{ {
accounts.email = { accounts.email = {
maildirBasePath = "Mail"; maildirBasePath = "Mail";
@ -22,6 +23,101 @@
smtp.host = "smtp.example.org"; smtp.host = "smtp.example.org";
smtp.tls.useStartTls = true; smtp.tls.useStartTls = true;
}; };
# Account that throws an error if any interesting account attribute is
# accessed other than the `enable` attribute. This is a bit awkward as
# we can't throw just for accessing some submodules, as some get accessed
# just as part of merging config, but it ensures a disabled account is
# genuinely disabled.
disabled-account =
let
# This is intended for use in generating documentation, but it's
# useful here as a way to get a list of attributes that might be
# defined.
accountAttrOptions = options.accounts.email.accounts.type.nestedTypes.elemType.getSubOptions [ ];
throwOnAttrAccess =
baseName: builtins.mapAttrs (n: v: throw "Unexpected access of ${baseName}.${n}");
# Don't want to do anything with these account attributes.
ignoredAttrNames = [
"_module"
"enable"
];
# These are submodules, which means the config attribute will be
# accessed even if subattributes aren't. This means we can't throw
# an error as soon as one of these is accessed, and instead need to
# throw errors if an attribute of this submodule is accessed.
submoduleAttrNames = [
"aerc"
"alot"
"astroid"
"getmail"
"himalaya"
"imapnotify"
"lieer"
"mbsync"
"meli"
"msmtp"
"mu"
"mujmap"
"neomutt"
"notmuch"
"offlineimap"
"thunderbird"
];
# Other attributes should never be accessed if the account is
# disabled, so throw an error if they are.
baseAttrThrows = throwOnAttrAccess "accounts.email.accounts.disabled-account" (
removeAttrs accountAttrOptions (ignoredAttrNames ++ submoduleAttrNames)
);
submoduleToAttrThrows =
name:
let
submoduleAttrOptions = builtins.getAttr name accountAttrOptions;
# Some submodules have sub-submodules, and they need the same
# special handling.
#
# Ideally this would be some recursive function to avoid
# repeating the code, potentially using introspection to workout
# which options are submodules, but that's complicated and
# unnecessary for now.
subSubmoduleAttrNames =
if name == "lieer" then
[ "sync" ]
else if name == "mbsync" then
[ "extraConfig" ]
else if name == "msmtp" then
[ "tls" ]
else if name == "notmuch" then
[ "neomutt" ]
else if name == "offlineimap" then
[ "extraConfig" ]
else
[ ];
subSubmoduleThrows = lib.genAttrs subSubmoduleAttrNames (
n:
throwOnAttrAccess "accounts.email.accounts.disabled-account.${name}.${n}" (
builtins.getAttr n submoduleAttrOptions
)
);
baseThrows = throwOnAttrAccess "accounts.email.accounts.disabled-account.${name}" (
removeAttrs submoduleAttrOptions subSubmoduleAttrNames
);
in
baseThrows // subSubmoduleThrows;
submoduleAttrThrows = lib.genAttrs submoduleAttrNames submoduleToAttrThrows;
in
lib.mergeAttrsList [
baseAttrThrows
submoduleAttrThrows
{ enable = false; }
];
}; };
}; };
} }