isync/mbsync: replace master/slave with far/near (#1776)

* isync/mbsync: replace master/slave with far/near

  isync/mbsync: update tests to match new changes

* isync/mbsync: use mkRenamedOptionModule to alert user to near/far change

* isync/mbsync: use warnings to alert about master/slave far/near change

  Fix capitalization

  isync/mbsync: fix nitpicks

* isync/mbsync: run format script

* isync/mbsync: include new test for expected master/slave warnings

* isync/mbsync: add news about changes
This commit is contained in:
Karl H 2021-05-22 17:31:06 -04:00 committed by GitHub
parent 4f70f49cec
commit 64607f58b7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 239 additions and 87 deletions

View file

@ -2029,6 +2029,23 @@ in
A new module is available: 'programs.foot'. 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.
'';
}
]; ];
}; };
} }

View file

@ -50,13 +50,13 @@ let
''; '';
}; };
masterPattern = mkOption { farPattern = mkOption {
type = types.str; type = types.str;
default = ""; default = "";
example = "[Gmail]/Sent Mail"; example = "[Gmail]/Sent Mail";
description = '' description = ''
IMAP4 patterns for which mailboxes on the remote mail server to sync. IMAP4 patterns for which mailboxes on the remote mail server to sync.
If <literal>Patterns</literal> are specified, <literal>masterPattern</literal> If <literal>Patterns</literal> are specified, <literal>farPattern</literal>
is interpreted as a prefix which is not matched against the patterns, is interpreted as a prefix which is not matched against the patterns,
and is not affected by mailbox list overrides. and is not affected by mailbox list overrides.
</para><para> </para><para>
@ -65,14 +65,14 @@ let
''; '';
}; };
slavePattern = mkOption { nearPattern = mkOption {
type = types.str; type = types.str;
default = ""; default = "";
example = "Sent"; example = "Sent";
description = '' description = ''
Name for where mail coming from the master mail server will end up Name for where mail coming from the remote (far) mail server will end up
locally. The mailbox specified by the master's pattern will be placed locally. The mailbox specified by the far pattern will be placed in
in this directory. this directory.
</para><para> </para><para>
If this is left as the default, then mbsync will default to the pattern If this is left as the default, then mbsync will default to the pattern
<literal>INBOX</literal>. <literal>INBOX</literal>.
@ -85,7 +85,7 @@ let
example = [ "INBOX" ]; example = [ "INBOX" ];
description = '' description = ''
Instead of synchronizing <emphasis>just</emphasis> the mailboxes that Instead of synchronizing <emphasis>just</emphasis> the mailboxes that
match the <literal>masterPattern</literal>, use it as a prefix which is match the <literal>farPattern</literal>, use it as a prefix which is
not matched against the patterns, and is not affected by mailbox list not matched against the patterns, and is not affected by mailbox list
overrides. overrides.
''; '';

View file

@ -10,6 +10,24 @@ let
mbsyncAccounts = mbsyncAccounts =
filter (a: a.mbsync.enable) (attrValues config.accounts.email.accounts); 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: genTlsConfig = tls:
{ {
SSLType = if !tls.enable then SSLType = if !tls.enable then
@ -22,10 +40,18 @@ let
CertificateFile = toString tls.certificatesFile; CertificateFile = toString tls.certificatesFile;
}; };
masterSlaveMapping = { imports = [
(mkRenamedOptionModule [ "programs" "mbsync" "masterSlaveMapping" ] [
"programs"
"mbsync"
"nearFarMapping"
])
];
nearFarMapping = {
none = "None"; none = "None";
imap = "Master"; imap = "Far";
maildir = "Slave"; maildir = "Near";
both = "Both"; both = "Both";
}; };
@ -88,18 +114,18 @@ let
genAccountWideChannel = account: genAccountWideChannel = account:
with account; with account;
genSection "Channel ${name}" ({ genSection "Channel ${name}" ({
Master = ":${name}-remote:"; Far = ":${name}-remote:";
Slave = ":${name}-local:"; Near = ":${name}-local:";
Patterns = mbsync.patterns; Patterns = mbsync.patterns;
Create = masterSlaveMapping.${mbsync.create}; Create = nearFarMapping.${mbsync.create};
Remove = masterSlaveMapping.${mbsync.remove}; Remove = nearFarMapping.${mbsync.remove};
Expunge = masterSlaveMapping.${mbsync.expunge}; Expunge = nearFarMapping.${mbsync.expunge};
SyncState = "*"; SyncState = "*";
} // mbsync.extraConfig.channel) + "\n"; } // mbsync.extraConfig.channel) + "\n";
# Given the attr set of groups, return a string of channels that will direct # 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 # mail to the proper directories, according to the pattern used in channel's
# master pattern definition. # "far" pattern definition.
genGroupChannelConfig = storeName: groups: genGroupChannelConfig = storeName: groups:
let let
# Given the name of the group this channel is part of and the channel # Given the name of the group this channel is part of and the channel
@ -118,8 +144,8 @@ let
else else
""; "";
in genSection "Channel ${groupName}-${channel.name}" ({ in genSection "Channel ${groupName}-${channel.name}" ({
Master = ":${storeName}-remote:${channel.masterPattern}"; Far = ":${storeName}-remote:${channel.farPattern}";
Slave = ":${storeName}-local:${channel.slavePattern}"; Near = ":${storeName}-local:${channel.nearPattern}";
} // channel.extraConfig) + genChannelPatterns channel.patterns; } // channel.extraConfig) + genChannelPatterns channel.patterns;
# Given the group name, and a attr set of channels within that group, # Given the group name, and a attr set of channels within that group,
# Generate a list of strings for each channels' configuration. # Generate a list of strings for each channels' configuration.
@ -206,50 +232,66 @@ in {
}; };
}; };
config = mkIf cfg.enable { config = mkIf cfg.enable (mkMerge [
assertions = let {
checkAccounts = pred: msg: assertions = let
let badAccounts = filter pred mbsyncAccounts; checkAccounts = pred: msg:
in { let badAccounts = filter pred mbsyncAccounts;
assertion = badAccounts == [ ]; in {
message = "mbsync: ${msg} for accounts: " assertion = badAccounts == [ ];
+ concatMapStringsSep ", " (a: a.name) badAccounts; message = "mbsync: ${msg} for accounts: "
}; + concatMapStringsSep ", " (a: a.name) badAccounts;
in [ };
(checkAccounts (a: a.maildir == null) "Missing maildir configuration") in [
(checkAccounts (a: a.imap == null) "Missing IMAP configuration") (checkAccounts (a: a.maildir == null) "Missing maildir configuration")
(checkAccounts (a: a.passwordCommand == null) "Missing passwordCommand") (checkAccounts (a: a.imap == null) "Missing IMAP configuration")
(checkAccounts (a: a.userName == null) "Missing username") (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; home.packages = [ cfg.package ];
# 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.
'' programs.notmuch.new.ignore = [ ".uidvalidity" ".mbsyncstate" ];
+ concatStringsSep "\n" (optional (cfg.extraConfig != "") cfg.extraConfig)
+ concatStringsSep "\n\n" accountsConfig
+ concatStringsSep "\n" groupsConfig;
home.activation = mkIf (mbsyncAccounts != [ ]) { home.file.".mbsyncrc".text = let
createMaildir = accountsConfig = map genAccountConfig mbsyncAccounts;
hm.dag.entryBetween [ "linkGeneration" ] [ "writeBoundary" ] '' # Only generate this kind of Group configuration if there are ANY accounts
$DRY_RUN_CMD mkdir -m700 -p $VERBOSE_ARG ${ # that do NOT have a per-account groups/channels option(s) specified.
concatMapStringsSep " " (a: a.maildir.absPath) mbsyncAccounts 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
}
'';
};
}
]);
} }

View file

@ -16,30 +16,30 @@ Path /home/hm-user/Mail/hm-account/
SubFolders Verbatim SubFolders Verbatim
Channel emptyChannels-empty1 Channel emptyChannels-empty1
Master :hm-account-remote: Far :hm-account-remote:
Slave :hm-account-local: Near :hm-account-local:
Channel emptyChannels-empty2 Channel emptyChannels-empty2
Master :hm-account-remote: Far :hm-account-remote:
Slave :hm-account-local: Near :hm-account-local:
Channel hm-account-earlierPatternMatch Channel hm-account-earlierPatternMatch
Master :hm-account-remote:Label Far :hm-account-remote:Label
Slave :hm-account-local:SomethingUnderLabel Near :hm-account-local:SomethingUnderLabel
Pattern ThingUnderLabel !NotThisMaildirThough "[Weird] Label?" Pattern ThingUnderLabel !NotThisMaildirThough "[Weird] Label?"
Channel hm-account-inbox Channel hm-account-inbox
Master :hm-account-remote:Inbox Far :hm-account-remote:Inbox
Slave :hm-account-local:Inbox Near :hm-account-local:Inbox
Channel hm-account-patternMatch Channel hm-account-patternMatch
Master :hm-account-remote:Label Far :hm-account-remote:Label
Slave :hm-account-local:SomethingUnderLabel Near :hm-account-local:SomethingUnderLabel
Pattern ThingUnderLabel !NotThisMaildirThough "[Weird] Label?" Pattern ThingUnderLabel !NotThisMaildirThough "[Weird] Label?"
Channel hm-account-strangeHostBoxName Channel hm-account-strangeHostBoxName
Master ":hm-account-remote:[Weird]/Label Mess" Far ":hm-account-remote:[Weird]/Label Mess"
Slave :hm-account-local:[AnotherWeird]/Label Near :hm-account-local:[AnotherWeird]/Label
Group emptyChannels Group emptyChannels
Channel emptyChannels-empty1 Channel emptyChannels-empty1
@ -68,12 +68,12 @@ Path /home/hm-user/Mail/hm@example.com/
SubFolders Verbatim SubFolders Verbatim
Channel inboxes-inbox1 Channel inboxes-inbox1
Master :hm@example.com-remote:Inbox1 Far :hm@example.com-remote:Inbox1
Slave :hm@example.com-local:Inboxes Near :hm@example.com-local:Inboxes
Channel inboxes-inbox2 Channel inboxes-inbox2
Master :hm@example.com-remote:Inbox2 Far :hm@example.com-remote:Inbox2
Slave :hm@example.com-local:Inboxes Near :hm@example.com-local:Inboxes
Group inboxes Group inboxes
Channel inboxes-inbox1 Channel inboxes-inbox1

View file

@ -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.<name>.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}
'';
};
}

View file

@ -24,12 +24,12 @@ with lib;
groups.inboxes = { groups.inboxes = {
channels = { channels = {
inbox1 = { inbox1 = {
masterPattern = "Inbox1"; farPattern = "Inbox1";
slavePattern = "Inboxes"; nearPattern = "Inboxes";
}; };
inbox2 = { inbox2 = {
masterPattern = "Inbox2"; farPattern = "Inbox2";
slavePattern = "Inboxes"; nearPattern = "Inboxes";
}; };
}; };
}; };
@ -39,8 +39,8 @@ with lib;
enable = true; enable = true;
groups.hm-account = { groups.hm-account = {
channels.earlierPatternMatch = { channels.earlierPatternMatch = {
masterPattern = "Label"; farPattern = "Label";
slavePattern = "SomethingUnderLabel"; nearPattern = "SomethingUnderLabel";
patterns = [ patterns = [
"ThingUnderLabel" "ThingUnderLabel"
"!NotThisMaildirThough" "!NotThisMaildirThough"
@ -48,16 +48,16 @@ with lib;
]; ];
}; };
channels.inbox = { channels.inbox = {
masterPattern = "Inbox"; farPattern = "Inbox";
slavePattern = "Inbox"; nearPattern = "Inbox";
}; };
channels.strangeHostBoxName = { channels.strangeHostBoxName = {
masterPattern = "[Weird]/Label Mess"; farPattern = "[Weird]/Label Mess";
slavePattern = "[AnotherWeird]/Label"; nearPattern = "[AnotherWeird]/Label";
}; };
channels.patternMatch = { channels.patternMatch = {
masterPattern = "Label"; farPattern = "Label";
slavePattern = "SomethingUnderLabel"; nearPattern = "SomethingUnderLabel";
patterns = [ patterns = [
"ThingUnderLabel" "ThingUnderLabel"
"!NotThisMaildirThough" "!NotThisMaildirThough"