1
0
mirror of https://github.com/nix-community/home-manager synced 2024-07-15 17:40:20 +02:00
home-manager/modules/programs/mbsync.nix
Karl Hallsby f9d8aa6b25 mbsync: write function to generate channel configuration blocks
This function takes in a set of groups, which includes their
consituent channels and writes the appropriate .mbsyncrc block for the
channel. The block that is generated is shown below:
      Channel groupName1-channelName1
      Master :<accountName>-remote:<master-pattern>
      Slave :<accountName>-local:<slave-pattern>

      Channel groupName2-channelName2
      Master :<accountName>-remote:<master-pattern>
      Slave :<accountName>-local:<slave-pattern>

Each group must have a unique name, no matter which account it is
declared under. The same holds true for channels.

Using channels with the patterns set up this way allows one to specify
which maildir directories are to be synchronized FROM the master TO
the slave. In addition, it allows for these maildirs to be remapped,
between the master server and the local slave.
This is critical, because Gmail has a strange way of storing its mail
that makes using mbsync, mu, and mu4e more difficult.

There are additional channel parameters that are already present in
this codebase from the previous use of group-channel configuration,
which will be reused.
2020-06-26 20:22:45 +02:00

215 lines
7.5 KiB
Nix

{ 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);
genTlsConfig = tls:
{
SSLType = if !tls.enable then
"None"
else if tls.useStartTls then
"STARTTLS"
else
"IMAPS";
} // optionalAttrs (tls.enable && tls.certificatesFile != null) {
CertificateFile = toString tls.certificatesFile;
};
masterSlaveMapping = {
none = "None";
imap = "Master";
maildir = "Slave";
both = "Both";
};
genSection = header: entries:
let
escapeValue = escape [ ''"'' ];
hasSpace = v: builtins.match ".* .*" v != null;
genValue = n: v:
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
else
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; }
// mbsync.extraConfig.local) + "\n" + genSection "Channel ${name}" ({
Master = ":${name}-remote:";
Slave = ":${name}-local:";
Patterns = mbsync.patterns;
Create = masterSlaveMapping.${mbsync.create};
Remove = masterSlaveMapping.${mbsync.remove};
Expunge = masterSlaveMapping.${mbsync.expunge};
SyncState = "*";
} // mbsync.extraConfig.channel) + "\n";
# Given the attr set of groups, return a string of channels to put into each group.
# 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:
genSection "Channel ${groupName}-${channel.name}" ({
Master = ":${storeName}-remote:${channel.masterPattern}";
Slave = ":${storeName}-local:${channel.slavePattern}";
}) + "\n";
# 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);
# 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.
genGroupConfig = name: channels:
let
genGroupChannel = n: boxes: "Channel ${n}:${concatStringsSep "," boxes}";
in concatStringsSep "\n"
([ "Group ${name}" ] ++ mapAttrsToList genGroupChannel channels);
in {
options = {
programs.mbsync = {
enable = mkEnableOption "mbsync IMAP4 and Maildir mailbox synchronizer";
package = mkOption {
type = types.package;
default = pkgs.isync;
defaultText = literalExample "pkgs.isync";
example = literalExample "pkgs.isync";
description = "The package to use for the mbsync binary.";
};
groups = mkOption {
type = types.attrsOf (types.attrsOf (types.listOf types.str));
default = { };
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.
'';
};
};
accounts.email.accounts = mkOption {
type = with types; attrsOf (submodule (import ./mbsync-accounts.nix));
};
};
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")
];
home.packages = [ cfg.package ];
programs.notmuch.new.ignore = [ ".uidvalidity" ".mbsyncstate" ];
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 != [ ]) {
createMaildir =
hm.dag.entryBetween [ "linkGeneration" ] [ "writeBoundary" ] ''
$DRY_RUN_CMD mkdir -m700 -p $VERBOSE_ARG ${
concatMapStringsSep " " (a: a.maildir.absPath) mbsyncAccounts
}
'';
};
};
}