{ config, lib, pkgs, ... }: with lib; let dag = config.lib.dag; 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 = tls.certificatesFile; }; masterSlaveMapping = { none = "None"; imap = "Master"; maildir = "Slave"; both = "Both"; }; genSection = header: entries: let escapeValue = escape [ "\"" ]; hasSpace = v: builtins.match ".* .*" v != null; genValue = v: if isList v then concatMapStringsSep " " genValue v else if isBool v then (if v then "yes" else "no") else if isInt v then toString v else if hasSpace v then "\"${escapeValue v}\"" else v; in '' ${header} ${concatStringsSep "\n" (mapAttrsToList (n: v: "${n} ${genValue v}") entries)} ''; genAccountConfig = account: with account; if (imap == null || maildir == null) then "" else 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"; 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 = "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. ''; }; }; }; config = mkIf cfg.enable { assertions = [ ( let badAccounts = filter (a: a.maildir == null) mbsyncAccounts; in { assertion = badAccounts == []; message = "mbsync: Missing maildir configuration for accounts: " + concatMapStringsSep ", " (a: a.name) badAccounts; } ) ( let badAccounts = filter (a: a.imap == null) mbsyncAccounts; in { assertion = badAccounts == []; message = "mbsync: Missing IMAP configuration for accounts: " + concatMapStringsSep ", " (a: a.name) badAccounts; } ) ]; 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.\n" ] ++ accountsConfig ++ groupsConfig ++ optional (cfg.extraConfig != "") cfg.extraConfig ); home.activation.createMaildir = dag.entryBetween [ "linkGeneration" ] [ "writeBoundary" ] '' $DRY_RUN_CMD mkdir -m700 -p $VERBOSE_ARG ${ concatMapStringsSep " " (a: a.maildir.absPath) mbsyncAccounts } ''; }; }