diff --git a/modules/accounts/email.nix b/modules/accounts/email.nix index 97404d1d6..e3f6f0445 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 4a42f8dce..fdddc3c52 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 6caa10183..442846e46 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 7453a0ec5..f4673a7ef 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 1ad5b8515..7a3c98ec7 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 04ada2e4c..128b2ff8c 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 8781178dc..4ea963a35 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 4c0986318..2c4c8cff8 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 f7e8be4c3..15781dd39 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 96f8513b4..1b252416c 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 07d45a943..4b853e78c 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 a597ceb90..1b00918a7 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 886346837..7d5f23933 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 dba6cc12c..b31bfb2d0 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 1fc12c8ee..732a4d47b 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 0abeb5120..c8d6b478a 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 d8f1a829f..05bc7d651 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 d245efd52..3e283676f 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 7eb206af9..c676c019d 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 39231df97..73fc01480 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 53fffd23c..fcd6340c2 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 44614733e..ae37e7f2f 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; } + ]; }; }; }