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'.
'';
}
{
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;
default = "";
example = "[Gmail]/Sent Mail";
description = ''
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,
and is not affected by mailbox list overrides.
</para><para>
@ -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.
</para><para>
If this is left as the default, then mbsync will default to the pattern
<literal>INBOX</literal>.
@ -85,7 +85,7 @@ let
example = [ "INBOX" ];
description = ''
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
overrides.
'';

View file

@ -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
}
'';
};
}
]);
}

View file

@ -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

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 = {
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"