From dbfcd3292d8af44eed8e41b222cbbf8685b950c6 Mon Sep 17 00:00:00 2001 From: Adam Dinwoodie Date: Tue, 5 Aug 2025 11:04:49 +0100 Subject: [PATCH] accounts.email: add option to disable an account Allow a user to disable an email account by setting `accounts.email.accounts..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. --- modules/accounts/email.nix | 15 ++- .../misc/news/2024/12/2024-12-08_17-22-13.nix | 4 +- modules/programs/aerc/default.nix | 4 +- modules/programs/alot/default.nix | 4 +- modules/programs/astroid/default.nix | 4 +- modules/programs/getmail/default.nix | 4 +- modules/programs/git.nix | 2 +- modules/programs/himalaya.nix | 4 +- modules/programs/lieer.nix | 4 +- modules/programs/mbsync/default.nix | 4 +- modules/programs/meli.nix | 2 +- modules/programs/msmtp/default.nix | 4 +- modules/programs/mu.nix | 4 +- modules/programs/mujmap.nix | 4 +- modules/programs/neomutt/default.nix | 4 +- modules/programs/notmuch/default.nix | 2 +- modules/programs/offlineimap/default.nix | 4 +- modules/programs/thunderbird.nix | 13 ++- modules/services/getmail.nix | 4 +- modules/services/imapnotify/default.nix | 2 +- modules/services/lieer.nix | 2 +- .../modules/accounts/email-test-accounts.nix | 96 +++++++++++++++++++ 22 files changed, 164 insertions(+), 26 deletions(-) diff --git a/modules/accounts/email.nix b/modules/accounts/email.nix index 97404d1d..e3f6f044 100644 --- a/modules/accounts/email.nix +++ b/modules/accounts/email.nix @@ -9,6 +9,7 @@ let ; cfg = config.accounts.email; + enabledAccounts = lib.filterAttrs (n: v: v.enable) cfg.accounts; gpgModule = types.submodule { 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 { type = types.enum [ "davmail" @@ -669,11 +680,11 @@ in }; }; - config = mkIf (cfg.accounts != { }) { + config = mkIf (enabledAccounts != { }) { assertions = [ ( 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 { assertion = lib.length primaries == 1; diff --git a/modules/misc/news/2024/12/2024-12-08_17-22-13.nix b/modules/misc/news/2024/12/2024-12-08_17-22-13.nix index 4a42f8dc..fdddc3c5 100644 --- a/modules/misc/news/2024/12/2024-12-08_17-22-13.nix +++ b/modules/misc/news/2024/12/2024-12-08_17-22-13.nix @@ -4,7 +4,9 @@ time = "2024-12-08T17:22:13+00:00"; condition = 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 usingMbsync; message = '' diff --git a/modules/programs/aerc/default.nix b/modules/programs/aerc/default.nix index 6caa1018..442846e4 100644 --- a/modules/programs/aerc/default.nix +++ b/modules/programs/aerc/default.nix @@ -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 = if (pkgs.stdenv.isDarwin && !config.xdg.enable) then diff --git a/modules/programs/alot/default.nix b/modules/programs/alot/default.nix index 7453a0ec..f4673a7e 100644 --- a/modules/programs/alot/default.nix +++ b/modules/programs/alot/default.nix @@ -17,7 +17,9 @@ let 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 alotAccounts = lib.sort (a: b: !(a.primary -> b.primary)) enabledAccounts; diff --git a/modules/programs/astroid/default.nix b/modules/programs/astroid/default.nix index 1ad5b851..7a3c98ec 100644 --- a/modules/programs/astroid/default.nix +++ b/modules/programs/astroid/default.nix @@ -11,7 +11,9 @@ let 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"; diff --git a/modules/programs/getmail/default.nix b/modules/programs/getmail/default.nix index 04ada2e4..128b2ff8 100644 --- a/modules/programs/getmail/default.nix +++ b/modules/programs/getmail/default.nix @@ -5,7 +5,9 @@ ... }: 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 = account: diff --git a/modules/programs/git.nix b/modules/programs/git.nix index 8781178d..4ea963a3 100644 --- a/modules/programs/git.nix +++ b/modules/programs/git.nix @@ -566,7 +566,7 @@ in { programs.git.iniContent = let - hasSmtp = name: account: account.smtp != null; + hasSmtp = name: account: account.enable && account.smtp != null; genIdentity = name: account: diff --git a/modules/programs/himalaya.nix b/modules/programs/himalaya.nix index 4c098631..2c4c8cff 100644 --- a/modules/programs/himalaya.nix +++ b/modules/programs/himalaya.nix @@ -34,7 +34,7 @@ let mkAccountConfig = _: account: let - notmuchEnabled = account.notmuch.enable; + notmuchEnabled = account.enable && account.notmuch.enable; imapEnabled = !isNull account.imap && !notmuchEnabled; maildirEnabled = !isNull account.maildir && !imapEnabled && !notmuchEnabled; @@ -165,7 +165,7 @@ in configFile."himalaya/config.toml".source = let enabledAccounts = lib.filterAttrs ( - _: account: account.himalaya.enable + _: account: account.enable && account.himalaya.enable ) config.accounts.email.accounts; accountsConfig = lib.mapAttrs mkAccountConfig enabledAccounts; globalConfig = compactAttrs himalaya.settings; diff --git a/modules/programs/lieer.nix b/modules/programs/lieer.nix index f7e8be4c..15781dd3 100644 --- a/modules/programs/lieer.nix +++ b/modules/programs/lieer.nix @@ -15,7 +15,9 @@ let 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); diff --git a/modules/programs/mbsync/default.nix b/modules/programs/mbsync/default.nix index 96f8513b..1b252416 100644 --- a/modules/programs/mbsync/default.nix +++ b/modules/programs/mbsync/default.nix @@ -20,7 +20,9 @@ let cfg = config.programs.mbsync; # 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 # patterns use the invalidOption attribute set value name. diff --git a/modules/programs/meli.nix b/modules/programs/meli.nix index 07d45a94..4b853e78 100644 --- a/modules/programs/meli.nix +++ b/modules/programs/meli.nix @@ -18,7 +18,7 @@ let tomlFormat = pkgs.formats.toml { }; enabledAccounts = lib.attrsets.filterAttrs ( - name: value: value.meli.enable or false + name: value: value.enable && (value.meli.enable or false) ) config.accounts.email.accounts; meliAccounts = (lib.attrsets.mapAttrs (name: value: (mkMeliAccounts name value)) enabledAccounts); diff --git a/modules/programs/msmtp/default.nix b/modules/programs/msmtp/default.nix index a597ceb9..1b00918a 100644 --- a/modules/programs/msmtp/default.nix +++ b/modules/programs/msmtp/default.nix @@ -9,7 +9,9 @@ let 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"; diff --git a/modules/programs/mu.nix b/modules/programs/mu.nix index 88634683..7d5f2393 100644 --- a/modules/programs/mu.nix +++ b/modules/programs/mu.nix @@ -14,7 +14,9 @@ let sortedAddresses = let # 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; # Construct list of lists containing email aliases, and flatten aliases = map (alias: alias.address or alias) (lib.flatten (map (a: a.aliases) muAccounts)); diff --git a/modules/programs/mujmap.nix b/modules/programs/mujmap.nix index dba6cc12..b31bfb2d 100644 --- a/modules/programs/mujmap.nix +++ b/modules/programs/mujmap.nix @@ -9,7 +9,9 @@ let 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) ( lib.filter (a: !a.notmuch.enable && a.mujmap.notmuchSetupWarning) mujmapAccounts diff --git a/modules/programs/neomutt/default.nix b/modules/programs/neomutt/default.nix index 1fc12c8e..732a4d47 100644 --- a/modules/programs/neomutt/default.nix +++ b/modules/programs/neomutt/default.nix @@ -20,7 +20,9 @@ let 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; accountCommandNeeded = lib.any ( diff --git a/modules/programs/notmuch/default.nix b/modules/programs/notmuch/default.nix index 0abeb512..c8d6b478 100644 --- a/modules/programs/notmuch/default.nix +++ b/modules/programs/notmuch/default.nix @@ -47,7 +47,7 @@ let user = 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; secondaries = filter (a: !a.primary) accounts; in diff --git a/modules/programs/offlineimap/default.nix b/modules/programs/offlineimap/default.nix index d8f1a829..05bc7d65 100644 --- a/modules/programs/offlineimap/default.nix +++ b/modules/programs/offlineimap/default.nix @@ -9,7 +9,9 @@ let 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 { mkKeyValue = diff --git a/modules/programs/thunderbird.nix b/modules/programs/thunderbird.nix index d245efd5..3e283676 100644 --- a/modules/programs/thunderbird.nix +++ b/modules/programs/thunderbird.nix @@ -32,16 +32,21 @@ let moduleName = "programs.thunderbird"; - filterEnabled = accounts: attrValues (lib.filterAttrs (_: a: a.thunderbird.enable) accounts); 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; - enabledCalendarAccounts = filterEnabled config.accounts.calendar.accounts; + enabledCalendarAccounts = filter (a: a.thunderbird.enable) ( + attrValues config.accounts.calendar.accounts + ); enabledCalendarAccountsWithId = addId enabledCalendarAccounts; - enabledContactAccounts = filterEnabled config.accounts.contact.accounts; + enabledContactAccounts = filter (a: a.thunderbird.enable) ( + attrValues config.accounts.contact.accounts + ); enabledContactAccountsWithId = addId enabledContactAccounts; thunderbirdConfigPath = if isDarwin then "Library/Thunderbird" else ".thunderbird"; diff --git a/modules/services/getmail.nix b/modules/services/getmail.nix index 7eb206af..c676c019 100644 --- a/modules/services/getmail.nix +++ b/modules/services/getmail.nix @@ -8,7 +8,9 @@ let 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! renderConfigFilepath = a: if a.primary then "getmailrc" else "getmail${a.name}"; diff --git a/modules/services/imapnotify/default.nix b/modules/services/imapnotify/default.nix index 39231df9..73fc0148 100644 --- a/modules/services/imapnotify/default.nix +++ b/modules/services/imapnotify/default.nix @@ -18,7 +18,7 @@ let 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 ); diff --git a/modules/services/lieer.nix b/modules/services/lieer.nix index 53fffd23..fcd6340c 100644 --- a/modules/services/lieer.nix +++ b/modules/services/lieer.nix @@ -7,7 +7,7 @@ let 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 ); diff --git a/tests/modules/accounts/email-test-accounts.nix b/tests/modules/accounts/email-test-accounts.nix index 44614733..ae37e7f2 100644 --- a/tests/modules/accounts/email-test-accounts.nix +++ b/tests/modules/accounts/email-test-accounts.nix @@ -1,3 +1,4 @@ +{ lib, options, ... }: { accounts.email = { maildirBasePath = "Mail"; @@ -22,6 +23,101 @@ smtp.host = "smtp.example.org"; 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; } + ]; }; }; }