2018-06-27 00:45:00 +02:00
|
|
|
{ config, lib, pkgs, ... }:
|
|
|
|
|
|
|
|
with lib;
|
|
|
|
|
|
|
|
let
|
|
|
|
|
|
|
|
cfg = config.programs.mbsync;
|
|
|
|
|
|
|
|
# Accounts for which mbsync is enabled.
|
|
|
|
mbsyncAccounts =
|
|
|
|
filter (a: a.mbsync.enable) (attrValues config.accounts.email.accounts);
|
|
|
|
|
2020-02-02 00:39:17 +01:00
|
|
|
genTlsConfig = tls:
|
|
|
|
{
|
|
|
|
SSLType = if !tls.enable then
|
|
|
|
"None"
|
|
|
|
else if tls.useStartTls then
|
|
|
|
"STARTTLS"
|
|
|
|
else
|
|
|
|
"IMAPS";
|
|
|
|
} // optionalAttrs (tls.enable && tls.certificatesFile != null) {
|
2019-08-06 00:10:58 +02:00
|
|
|
CertificateFile = toString tls.certificatesFile;
|
2018-06-27 00:45:00 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
masterSlaveMapping = {
|
|
|
|
none = "None";
|
|
|
|
imap = "Master";
|
|
|
|
maildir = "Slave";
|
|
|
|
both = "Both";
|
|
|
|
};
|
|
|
|
|
|
|
|
genSection = header: entries:
|
|
|
|
let
|
2020-02-02 00:39:17 +01:00
|
|
|
escapeValue = escape [ ''"'' ];
|
2018-09-02 07:58:54 +02:00
|
|
|
hasSpace = v: builtins.match ".* .*" v != null;
|
2019-01-15 21:39:33 +01:00
|
|
|
genValue = n: v:
|
2020-02-02 00:39:17 +01:00
|
|
|
if isList v then
|
|
|
|
concatMapStringsSep " " (genValue n) v
|
|
|
|
else if isBool v then
|
|
|
|
(if v then "yes" else "no")
|
|
|
|
else if isInt v then
|
|
|
|
toString v
|
|
|
|
else if isString v && hasSpace v then
|
|
|
|
''"${escapeValue v}"''
|
|
|
|
else if isString v then
|
|
|
|
v
|
2019-01-15 21:39:33 +01:00
|
|
|
else
|
2020-02-02 00:39:17 +01:00
|
|
|
let prettyV = lib.generators.toPretty { } v;
|
|
|
|
in throw "mbsync: unexpected value for option ${n}: '${prettyV}'";
|
|
|
|
in ''
|
|
|
|
${header}
|
|
|
|
${concatStringsSep "\n"
|
|
|
|
(mapAttrsToList (n: v: "${n} ${genValue n v}") entries)}
|
|
|
|
'';
|
|
|
|
|
|
|
|
genAccountConfig = account:
|
|
|
|
with account;
|
|
|
|
genSection "IMAPAccount ${name}" ({
|
|
|
|
Host = imap.host;
|
|
|
|
User = userName;
|
|
|
|
PassCmd = toString passwordCommand;
|
|
|
|
} // genTlsConfig imap.tls
|
|
|
|
// optionalAttrs (imap.port != null) { Port = toString imap.port; }
|
|
|
|
// mbsync.extraConfig.account) + "\n"
|
|
|
|
+ genSection "IMAPStore ${name}-remote"
|
|
|
|
({ Account = name; } // mbsync.extraConfig.remote) + "\n"
|
|
|
|
+ genSection "MaildirStore ${name}-local" ({
|
|
|
|
Path = "${maildir.absPath}/";
|
|
|
|
Inbox = "${maildir.absPath}/${folders.inbox}";
|
|
|
|
SubFolders = "Verbatim";
|
|
|
|
} // optionalAttrs (mbsync.flatten != null) { Flatten = mbsync.flatten; }
|
2020-06-27 02:25:06 +02:00
|
|
|
// mbsync.extraConfig.local) + "\n"
|
|
|
|
+ genGroupChannelConfig name mbsync.groups
|
|
|
|
+ genAccountGroups mbsync.groups;
|
|
|
|
|
2020-06-26 20:22:45 +02:00
|
|
|
# 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.
|
|
|
|
genGroupChannelConfig = storeName: groups:
|
|
|
|
let
|
|
|
|
# Given the name of the group this channel is part of and the channel
|
|
|
|
# itself, generate the string for the desired configuration.
|
|
|
|
genChannelString = groupName: channel:
|
2020-06-27 02:14:20 +02:00
|
|
|
let
|
|
|
|
escapeValue = escape [ ''\"'' ];
|
|
|
|
hasSpace = v: builtins.match ".* .*" v != null;
|
|
|
|
# Given a list of patterns, will return the string requested.
|
|
|
|
# Only prints if the pattern is NOT the empty list, the default.
|
|
|
|
genChannelPatterns = patterns: if (length patterns) != 0 then
|
|
|
|
"Pattern " + concatStringsSep " " (map
|
|
|
|
(pat: if hasSpace pat then escapeValue pat else pat) patterns) + "\n"
|
|
|
|
else "";
|
|
|
|
in
|
2020-06-26 20:22:45 +02:00
|
|
|
genSection "Channel ${groupName}-${channel.name}" ({
|
|
|
|
Master = ":${storeName}-remote:${channel.masterPattern}";
|
|
|
|
Slave = ":${storeName}-local:${channel.slavePattern}";
|
2020-06-27 02:14:20 +02:00
|
|
|
} // channel.extraConfig)
|
|
|
|
+ genChannelPatterns channel.patterns
|
|
|
|
+ "\n";
|
2020-06-26 20:22:45 +02:00
|
|
|
# Given the group name, and a attr set of channels within that group,
|
|
|
|
# Generate a list of strings for each channels' configuration.
|
|
|
|
genChannelStrings = groupName: channels:
|
|
|
|
mapAttrsToList (channelName: info: genChannelString groupName info) channels;
|
|
|
|
# Given a group, return a string that configures all the channels within
|
|
|
|
# the group.
|
|
|
|
genGroupsChannels = group: concatStrings
|
|
|
|
(genChannelStrings group.name group.channels);
|
|
|
|
in
|
|
|
|
# Generate all channel configurations for all groups for this account.
|
|
|
|
concatStringsSep "\n"
|
|
|
|
(mapAttrsToList (name: group: genGroupsChannels group) groups);
|
|
|
|
|
2020-06-26 18:32:47 +02:00
|
|
|
# Given the attr set of groups, return a string which maps channels to groups
|
|
|
|
genAccountGroups = groups:
|
|
|
|
let
|
|
|
|
# Given the name of the group and the attribute set of channels, make
|
|
|
|
# make "Channel <grpName>-<chnName>" for each channel to list os strings
|
|
|
|
genChannelStrings = groupName: channels: mapAttrsToList
|
|
|
|
(name: info: "Channel ${groupName}-${name}") channels;
|
|
|
|
# Take in 1 group, construct the "Group <grpName>" header, and construct
|
|
|
|
# each of the channels.
|
|
|
|
genGroupChannelString = group:
|
|
|
|
[("Group " + group.name)] ++
|
|
|
|
(genChannelStrings group.name group.channels);
|
|
|
|
# Given set of groups, generates list of strings, where each string is one
|
|
|
|
# of the groups and its consituent channels.
|
|
|
|
genGroupsStrings = mapAttrsToList (name: info: concatStringsSep "\n"
|
|
|
|
(genGroupChannelString groups.${name})) groups;
|
|
|
|
in (concatStringsSep "\n\n" genGroupsStrings) # Put all strings together.
|
|
|
|
# 2 \n needed in concatStringsSep because last element genGroupsStrings
|
|
|
|
# has no \n.
|
|
|
|
+ "\n\n"; # Additional spacing after this account's group setup.
|
2018-06-27 00:45:00 +02:00
|
|
|
|
|
|
|
genGroupConfig = name: channels:
|
|
|
|
let
|
|
|
|
genGroupChannel = n: boxes: "Channel ${n}:${concatStringsSep "," boxes}";
|
2020-02-02 00:39:17 +01:00
|
|
|
in concatStringsSep "\n"
|
|
|
|
([ "Group ${name}" ] ++ mapAttrsToList genGroupChannel channels);
|
2018-06-27 00:45:00 +02:00
|
|
|
|
2020-02-02 00:39:17 +01:00
|
|
|
in {
|
2018-06-27 00:45:00 +02:00
|
|
|
options = {
|
|
|
|
programs.mbsync = {
|
|
|
|
enable = mkEnableOption "mbsync IMAP4 and Maildir mailbox synchronizer";
|
|
|
|
|
|
|
|
package = mkOption {
|
|
|
|
type = types.package;
|
|
|
|
default = pkgs.isync;
|
2019-08-28 00:12:28 +02:00
|
|
|
defaultText = literalExample "pkgs.isync";
|
2018-06-27 00:45:00 +02:00
|
|
|
example = literalExample "pkgs.isync";
|
|
|
|
description = "The package to use for the mbsync binary.";
|
|
|
|
};
|
|
|
|
|
|
|
|
groups = mkOption {
|
|
|
|
type = types.attrsOf (types.attrsOf (types.listOf types.str));
|
2020-02-02 00:39:17 +01:00
|
|
|
default = { };
|
2018-06-27 00:45:00 +02:00
|
|
|
example = literalExample ''
|
|
|
|
{
|
|
|
|
inboxes = {
|
|
|
|
account1 = [ "Inbox" ];
|
|
|
|
account2 = [ "Inbox" ];
|
|
|
|
};
|
|
|
|
}
|
|
|
|
'';
|
|
|
|
description = ''
|
|
|
|
Definition of groups.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
|
|
|
extraConfig = mkOption {
|
|
|
|
type = types.lines;
|
|
|
|
default = "";
|
|
|
|
description = ''
|
|
|
|
Extra configuration lines to add to the mbsync configuration.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
};
|
2020-06-16 00:45:20 +02:00
|
|
|
|
|
|
|
accounts.email.accounts = mkOption {
|
|
|
|
type = with types; attrsOf (submodule (import ./mbsync-accounts.nix));
|
|
|
|
};
|
2018-06-27 00:45:00 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
config = mkIf cfg.enable {
|
2020-02-02 00:39:17 +01:00
|
|
|
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")
|
|
|
|
];
|
2018-06-27 00:45:00 +02:00
|
|
|
|
|
|
|
home.packages = [ cfg.package ];
|
|
|
|
|
2018-06-28 23:42:25 +02:00
|
|
|
programs.notmuch.new.ignore = [ ".uidvalidity" ".mbsyncstate" ];
|
|
|
|
|
2020-02-02 00:39:17 +01:00
|
|
|
home.file.".mbsyncrc".text = let
|
|
|
|
accountsConfig = map genAccountConfig mbsyncAccounts;
|
|
|
|
groupsConfig = mapAttrsToList genGroupConfig cfg.groups;
|
|
|
|
in concatStringsSep "\n" ([''
|
|
|
|
# Generated by Home Manager.
|
|
|
|
''] ++ optional (cfg.extraConfig != "") cfg.extraConfig ++ accountsConfig
|
|
|
|
++ groupsConfig) + "\n";
|
|
|
|
|
|
|
|
home.activation = mkIf (mbsyncAccounts != [ ]) {
|
2019-12-08 21:46:30 +01:00
|
|
|
createMaildir =
|
2020-01-16 23:41:14 +01:00
|
|
|
hm.dag.entryBetween [ "linkGeneration" ] [ "writeBoundary" ] ''
|
2019-12-08 21:46:30 +01:00
|
|
|
$DRY_RUN_CMD mkdir -m700 -p $VERBOSE_ARG ${
|
|
|
|
concatMapStringsSep " " (a: a.maildir.absPath) mbsyncAccounts
|
|
|
|
}
|
|
|
|
'';
|
|
|
|
};
|
2018-06-27 00:45:00 +02:00
|
|
|
};
|
|
|
|
}
|