diff --git a/modules/misc/news.nix b/modules/misc/news.nix index 27b1a5f7d..708fd7f6f 100644 --- a/modules/misc/news.nix +++ b/modules/misc/news.nix @@ -2029,6 +2029,23 @@ in A new module is available: 'programs.foot'. ''; } + + { + time = "2021-04-13T07:19:36+00:00"; + message = '' + mbsync channels no longer accepts the masterPattern or slavePattern + attribute keys. This is due to an upstream change. + They have been renamed: masterPattern -> farPattern, and + slavePattern -> nearPattern. + This is a stateful change, where the database file(s) used to keep track + of mail are silently upgraded once you upgrade both your configuration file + and the mbsync program. + + Note that this change is non-reversible, meaning once you choose to switch to + near/farPattern, you can no longer use your previous slave/masterPattern + configuration file. + ''; + } ]; }; } diff --git a/modules/programs/mbsync-accounts.nix b/modules/programs/mbsync-accounts.nix index cdd5997f1..3f3f2d14d 100644 --- a/modules/programs/mbsync-accounts.nix +++ b/modules/programs/mbsync-accounts.nix @@ -50,13 +50,13 @@ let ''; }; - masterPattern = mkOption { + farPattern = mkOption { type = types.str; default = ""; example = "[Gmail]/Sent Mail"; description = '' IMAP4 patterns for which mailboxes on the remote mail server to sync. - If Patterns are specified, masterPattern + If Patterns are specified, farPattern is interpreted as a prefix which is not matched against the patterns, and is not affected by mailbox list overrides. @@ -65,14 +65,14 @@ let ''; }; - slavePattern = mkOption { + nearPattern = mkOption { type = types.str; default = ""; example = "Sent"; description = '' - Name for where mail coming from the master mail server will end up - locally. The mailbox specified by the master's pattern will be placed - in this directory. + Name for where mail coming from the remote (far) mail server will end up + locally. The mailbox specified by the far pattern will be placed in + this directory. If this is left as the default, then mbsync will default to the pattern INBOX. @@ -85,7 +85,7 @@ let example = [ "INBOX" ]; description = '' Instead of synchronizing just the mailboxes that - match the masterPattern, use it as a prefix which is + match the farPattern, use it as a prefix which is not matched against the patterns, and is not affected by mailbox list overrides. ''; diff --git a/modules/programs/mbsync.nix b/modules/programs/mbsync.nix index 7d55c7343..91e7d0f77 100644 --- a/modules/programs/mbsync.nix +++ b/modules/programs/mbsync.nix @@ -10,6 +10,24 @@ let mbsyncAccounts = filter (a: a.mbsync.enable) (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. + channelInvalidOption = channels: invalidOption: + any (c: c) (mapAttrsToList (c: hasAttr invalidOption) channels); + + # Given a SINGLE account's groups attribute set, return true if ANY of the account's group's channel's patterns use the invalidOption attribute set value name. + groupInvalidOption = groups: invalidOption: + any (g: g) (mapAttrsToList (groupName: groupVals: + channelInvalidOption groupVals.channels invalidOption) groups); + + # Given all accounts (ensure that accounts passed in here ARE mbsync-using accounts) + # return true if ANY of the account's groups' channels' patterns use the + # invalidOption attribute set value name. + accountInvalidOption = accounts: invalidOption: + any (a: a) + (map (account: groupInvalidOption account.mbsync.groups invalidOption) + mbsyncAccounts); + genTlsConfig = tls: { SSLType = if !tls.enable then @@ -22,10 +40,18 @@ let CertificateFile = toString tls.certificatesFile; }; - masterSlaveMapping = { + imports = [ + (mkRenamedOptionModule [ "programs" "mbsync" "masterSlaveMapping" ] [ + "programs" + "mbsync" + "nearFarMapping" + ]) + ]; + + nearFarMapping = { none = "None"; - imap = "Master"; - maildir = "Slave"; + imap = "Far"; + maildir = "Near"; both = "Both"; }; @@ -88,18 +114,18 @@ let genAccountWideChannel = account: with account; genSection "Channel ${name}" ({ - Master = ":${name}-remote:"; - Slave = ":${name}-local:"; + Far = ":${name}-remote:"; + Near = ":${name}-local:"; Patterns = mbsync.patterns; - Create = masterSlaveMapping.${mbsync.create}; - Remove = masterSlaveMapping.${mbsync.remove}; - Expunge = masterSlaveMapping.${mbsync.expunge}; + Create = nearFarMapping.${mbsync.create}; + Remove = nearFarMapping.${mbsync.remove}; + Expunge = nearFarMapping.${mbsync.expunge}; SyncState = "*"; } // mbsync.extraConfig.channel) + "\n"; # Given the attr set of groups, return a string of channels that will direct # mail to the proper directories, according to the pattern used in channel's - # master pattern definition. + # "far" pattern definition. genGroupChannelConfig = storeName: groups: let # Given the name of the group this channel is part of and the channel @@ -118,8 +144,8 @@ let else ""; in genSection "Channel ${groupName}-${channel.name}" ({ - Master = ":${storeName}-remote:${channel.masterPattern}"; - Slave = ":${storeName}-local:${channel.slavePattern}"; + Far = ":${storeName}-remote:${channel.farPattern}"; + Near = ":${storeName}-local:${channel.nearPattern}"; } // channel.extraConfig) + genChannelPatterns channel.patterns; # Given the group name, and a attr set of channels within that group, # Generate a list of strings for each channels' configuration. @@ -206,50 +232,66 @@ in { }; }; - config = mkIf cfg.enable { - assertions = let - checkAccounts = pred: msg: - let badAccounts = filter pred mbsyncAccounts; - in { - assertion = badAccounts == [ ]; - message = "mbsync: ${msg} for accounts: " - + concatMapStringsSep ", " (a: a.name) badAccounts; - }; - in [ - (checkAccounts (a: a.maildir == null) "Missing maildir configuration") - (checkAccounts (a: a.imap == null) "Missing IMAP configuration") - (checkAccounts (a: a.passwordCommand == null) "Missing passwordCommand") - (checkAccounts (a: a.userName == null) "Missing username") - ]; + config = mkIf cfg.enable (mkMerge [ + { + assertions = let + checkAccounts = pred: msg: + let badAccounts = filter pred mbsyncAccounts; + in { + assertion = badAccounts == [ ]; + message = "mbsync: ${msg} for accounts: " + + concatMapStringsSep ", " (a: a.name) badAccounts; + }; + in [ + (checkAccounts (a: a.maildir == null) "Missing maildir configuration") + (checkAccounts (a: a.imap == null) "Missing IMAP configuration") + (checkAccounts (a: a.passwordCommand == null) "Missing passwordCommand") + (checkAccounts (a: a.userName == null) "Missing username") + ]; + } - home.packages = [ cfg.package ]; + (mkIf (accountInvalidOption mbsyncAccounts "masterPattern") { + warnings = [ + "mbsync channels no longer use masterPattern. Use farPattern in its place." + ]; + }) - programs.notmuch.new.ignore = [ ".uidvalidity" ".mbsyncstate" ]; + (mkIf (accountInvalidOption mbsyncAccounts "slavePattern") { + warnings = [ + "mbsync channels no longer use slavePattern. Use nearPattern in its place." + ]; + }) - home.file.".mbsyncrc".text = let - accountsConfig = map genAccountConfig mbsyncAccounts; - # Only generate this kind of Group configuration if there are ANY accounts - # that do NOT have a per-account groups/channels option(s) specified. - groupsConfig = - if any (account: account.mbsync.groups == { }) mbsyncAccounts then - mapAttrsToList genGroupConfig cfg.groups - else - [ ]; - in '' - # Generated by Home Manager. + { + home.packages = [ cfg.package ]; - '' - + concatStringsSep "\n" (optional (cfg.extraConfig != "") cfg.extraConfig) - + concatStringsSep "\n\n" accountsConfig - + concatStringsSep "\n" groupsConfig; + programs.notmuch.new.ignore = [ ".uidvalidity" ".mbsyncstate" ]; - home.activation = mkIf (mbsyncAccounts != [ ]) { - createMaildir = - hm.dag.entryBetween [ "linkGeneration" ] [ "writeBoundary" ] '' - $DRY_RUN_CMD mkdir -m700 -p $VERBOSE_ARG ${ - concatMapStringsSep " " (a: a.maildir.absPath) mbsyncAccounts - } - ''; - }; - }; + home.file.".mbsyncrc".text = let + accountsConfig = map genAccountConfig mbsyncAccounts; + # Only generate this kind of Group configuration if there are ANY accounts + # that do NOT have a per-account groups/channels option(s) specified. + groupsConfig = + if any (account: account.mbsync.groups == { }) mbsyncAccounts then + mapAttrsToList genGroupConfig cfg.groups + else + [ ]; + in '' + # Generated by Home Manager. + + '' + + concatStringsSep "\n" (optional (cfg.extraConfig != "") cfg.extraConfig) + + concatStringsSep "\n\n" accountsConfig + + concatStringsSep "\n" groupsConfig; + + home.activation = mkIf (mbsyncAccounts != [ ]) { + createMaildir = + hm.dag.entryBetween [ "linkGeneration" ] [ "writeBoundary" ] '' + $DRY_RUN_CMD mkdir -m700 -p $VERBOSE_ARG ${ + concatMapStringsSep " " (a: a.maildir.absPath) mbsyncAccounts + } + ''; + }; + } + ]); } diff --git a/tests/modules/programs/mbsync/mbsync-expected.conf b/tests/modules/programs/mbsync/mbsync-expected.conf index 672296047..c1ca921ac 100644 --- a/tests/modules/programs/mbsync/mbsync-expected.conf +++ b/tests/modules/programs/mbsync/mbsync-expected.conf @@ -16,30 +16,30 @@ Path /home/hm-user/Mail/hm-account/ SubFolders Verbatim Channel emptyChannels-empty1 -Master :hm-account-remote: -Slave :hm-account-local: +Far :hm-account-remote: +Near :hm-account-local: Channel emptyChannels-empty2 -Master :hm-account-remote: -Slave :hm-account-local: +Far :hm-account-remote: +Near :hm-account-local: Channel hm-account-earlierPatternMatch -Master :hm-account-remote:Label -Slave :hm-account-local:SomethingUnderLabel +Far :hm-account-remote:Label +Near :hm-account-local:SomethingUnderLabel Pattern ThingUnderLabel !NotThisMaildirThough "[Weird] Label?" Channel hm-account-inbox -Master :hm-account-remote:Inbox -Slave :hm-account-local:Inbox +Far :hm-account-remote:Inbox +Near :hm-account-local:Inbox Channel hm-account-patternMatch -Master :hm-account-remote:Label -Slave :hm-account-local:SomethingUnderLabel +Far :hm-account-remote:Label +Near :hm-account-local:SomethingUnderLabel Pattern ThingUnderLabel !NotThisMaildirThough "[Weird] Label?" Channel hm-account-strangeHostBoxName -Master ":hm-account-remote:[Weird]/Label Mess" -Slave :hm-account-local:[AnotherWeird]/Label +Far ":hm-account-remote:[Weird]/Label Mess" +Near :hm-account-local:[AnotherWeird]/Label Group emptyChannels Channel emptyChannels-empty1 @@ -68,12 +68,12 @@ Path /home/hm-user/Mail/hm@example.com/ SubFolders Verbatim Channel inboxes-inbox1 -Master :hm@example.com-remote:Inbox1 -Slave :hm@example.com-local:Inboxes +Far :hm@example.com-remote:Inbox1 +Near :hm@example.com-local:Inboxes Channel inboxes-inbox2 -Master :hm@example.com-remote:Inbox2 -Slave :hm@example.com-local:Inboxes +Far :hm@example.com-remote:Inbox2 +Near :hm@example.com-local:Inboxes Group inboxes Channel inboxes-inbox1 diff --git a/tests/modules/programs/mbsync/mbsync-master-slave-change.nix b/tests/modules/programs/mbsync/mbsync-master-slave-change.nix new file mode 100644 index 000000000..96f02d906 --- /dev/null +++ b/tests/modules/programs/mbsync/mbsync-master-slave-change.nix @@ -0,0 +1,93 @@ +{ config, lib, pkgs, ... }: + +with lib; + +{ + imports = [ ../../accounts/email-test-accounts.nix ]; + + test.asserts.warnings.expected = [ + "mbsync channels no longer use masterPattern. Use farPattern in its place." + "mbsync channels no longer use slavePattern. Use nearPattern in its place." + ]; + + config = { + programs.mbsync = { + enable = true; + # programs.mbsync.groups and + # accounts.email.accounts..mbsync.groups should NOT be used at the + # same time. + # If they are, then the new version will take precendence. + groups.inboxes = { + "hm@example.com" = [ "Inbox1" "Inbox2" ]; + hm-account = [ "Inbox" ]; + }; + }; + + accounts.email.accounts = { + "hm@example.com".mbsync = { + enable = true; + groups.inboxes = { + channels = { + inbox1 = { + farPattern = "Inbox1"; + nearPattern = "Inboxes"; + }; + inbox2 = { + farPattern = "Inbox2"; + nearPattern = "Inboxes"; + }; + }; + }; + }; + + hm-account.mbsync = { + enable = true; + groups.hm-account = { + channels.earlierPatternMatch = { + farPattern = "Label"; + nearPattern = "SomethingUnderLabel"; + patterns = [ + "ThingUnderLabel" + "!NotThisMaildirThough" + ''"[Weird] Label?"'' + ]; + }; + channels.inbox = { + farPattern = "Inbox"; + nearPattern = "Inbox"; + }; + channels.strangeHostBoxName = { + farPattern = "[Weird]/Label Mess"; + nearPattern = "[AnotherWeird]/Label"; + }; + channels.patternMatch = { + farPattern = "Label"; + nearPattern = "SomethingUnderLabel"; + patterns = [ + "ThingUnderLabel" + "!NotThisMaildirThough" + ''"[Weird] Label?"'' + ]; + }; + }; + # No group should be printed. + groups.emptyGroup = { }; + # Group should be printed, but left with default channels. + groups.emptyChannels = { + channels.empty1 = { }; + channels.empty2 = { }; + }; + }; + }; + + test.asserts.warnings.expected = [ + "mbsync channels no longer use masterPattern. use farPattern in its place." + "mbsync channels no longer use slavePattern. Use nearPattern in its place." + ]; + + nmt.script = '' + assertFileExists home-files/.mbsyncrc + assertFileContent home-files/.mbsyncrc ${./mbsync-expected.conf} + ''; + }; +} diff --git a/tests/modules/programs/mbsync/mbsync.nix b/tests/modules/programs/mbsync/mbsync.nix index a6e555cd4..d80421d6d 100644 --- a/tests/modules/programs/mbsync/mbsync.nix +++ b/tests/modules/programs/mbsync/mbsync.nix @@ -24,12 +24,12 @@ with lib; groups.inboxes = { channels = { inbox1 = { - masterPattern = "Inbox1"; - slavePattern = "Inboxes"; + farPattern = "Inbox1"; + nearPattern = "Inboxes"; }; inbox2 = { - masterPattern = "Inbox2"; - slavePattern = "Inboxes"; + farPattern = "Inbox2"; + nearPattern = "Inboxes"; }; }; }; @@ -39,8 +39,8 @@ with lib; enable = true; groups.hm-account = { channels.earlierPatternMatch = { - masterPattern = "Label"; - slavePattern = "SomethingUnderLabel"; + farPattern = "Label"; + nearPattern = "SomethingUnderLabel"; patterns = [ "ThingUnderLabel" "!NotThisMaildirThough" @@ -48,16 +48,16 @@ with lib; ]; }; channels.inbox = { - masterPattern = "Inbox"; - slavePattern = "Inbox"; + farPattern = "Inbox"; + nearPattern = "Inbox"; }; channels.strangeHostBoxName = { - masterPattern = "[Weird]/Label Mess"; - slavePattern = "[AnotherWeird]/Label"; + farPattern = "[Weird]/Label Mess"; + nearPattern = "[AnotherWeird]/Label"; }; channels.patternMatch = { - masterPattern = "Label"; - slavePattern = "SomethingUnderLabel"; + farPattern = "Label"; + nearPattern = "SomethingUnderLabel"; patterns = [ "ThingUnderLabel" "!NotThisMaildirThough"