mirror of
https://github.com/nix-community/home-manager.git
synced 2025-11-08 19:46:05 +01:00
Allow a user to disable an email account by setting `accounts.email.accounts.<name>.enable = false`. This is useful if someone wants to configure email accounts globally but only use them in certain circumstances. Everywhere email account configuration is used, check if the account is enabled before checking any attributes of the account.
363 lines
11 KiB
Nix
363 lines
11 KiB
Nix
{
|
|
config,
|
|
lib,
|
|
pkgs,
|
|
...
|
|
}:
|
|
let
|
|
inherit (lib)
|
|
any
|
|
concatStringsSep
|
|
concatMapStringsSep
|
|
literalExpression
|
|
mapAttrsToList
|
|
mkIf
|
|
mkOption
|
|
optionalAttrs
|
|
types
|
|
;
|
|
|
|
cfg = config.programs.mbsync;
|
|
|
|
# Accounts for which mbsync is enabled.
|
|
mbsyncAccounts = lib.filter (a: a.enable && a.mbsync.enable) (
|
|
lib.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: lib.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:
|
|
{
|
|
TLSType =
|
|
if !tls.enable then
|
|
"None"
|
|
else if tls.useStartTls then
|
|
"STARTTLS"
|
|
else
|
|
"IMAPS";
|
|
}
|
|
// lib.optionalAttrs (tls.enable && tls.certificatesFile != null) {
|
|
CertificateFile = toString tls.certificatesFile;
|
|
};
|
|
|
|
nearFarMapping = {
|
|
none = "None";
|
|
imap = "Far";
|
|
maildir = "Near";
|
|
both = "Both";
|
|
};
|
|
|
|
genSection =
|
|
header: entries:
|
|
let
|
|
escapeValue = lib.escape [ ''"'' ];
|
|
hasSpace = v: builtins.match ".* .*" v != null;
|
|
genValue =
|
|
n: v:
|
|
if lib.isList v then
|
|
concatMapStringsSep " " (genValue n) v
|
|
else if lib.isBool v then
|
|
lib.hm.booleans.yesNo v
|
|
else if lib.isInt v then
|
|
toString v
|
|
else if lib.isString v && hasSpace v then
|
|
''"${escapeValue v}"''
|
|
else if lib.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:
|
|
let
|
|
inherit (account)
|
|
name
|
|
maildir
|
|
imap
|
|
mbsync
|
|
passwordCommand
|
|
userName
|
|
;
|
|
in
|
|
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" (
|
|
{
|
|
Inbox = "${maildir.absPath}/${account.folders.inbox}";
|
|
}
|
|
// optionalAttrs (mbsync.subFolders != "Maildir++" || mbsync.flatten != null) {
|
|
Path = "${maildir.absPath}/";
|
|
}
|
|
// optionalAttrs (mbsync.flatten == null) {
|
|
SubFolders = mbsync.subFolders;
|
|
}
|
|
// optionalAttrs (mbsync.flatten != null) { Flatten = mbsync.flatten; }
|
|
// mbsync.extraConfig.local
|
|
)
|
|
+ "\n"
|
|
+ genChannels account;
|
|
|
|
genChannels =
|
|
account:
|
|
let
|
|
inherit (account) name mbsync;
|
|
in
|
|
if mbsync.groups == { } then
|
|
genAccountWideChannel account
|
|
else
|
|
genGroupChannelConfig name mbsync.groups + "\n" + genAccountGroups mbsync.groups;
|
|
|
|
# Used when no channels are specified for this account. This will create a
|
|
# single channel for the entire account that is then further refined within
|
|
# the Group for synchronization.
|
|
genAccountWideChannel =
|
|
account:
|
|
let
|
|
inherit (account) name mbsync;
|
|
in
|
|
genSection "Channel ${name}" (
|
|
{
|
|
Far = ":${name}-remote:";
|
|
Near = ":${name}-local:";
|
|
Patterns = mbsync.patterns;
|
|
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
|
|
# "far" 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:
|
|
let
|
|
escapeValue = lib.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 (lib.length patterns) != 0 then
|
|
"Pattern "
|
|
+ concatStringsSep " " (map (pat: if hasSpace pat then escapeValue pat else pat) patterns)
|
|
+ "\n"
|
|
else
|
|
"";
|
|
in
|
|
genSection "Channel ${groupName}-${channel.name}" (
|
|
{
|
|
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.
|
|
genChannelStrings =
|
|
groupName: channels:
|
|
lib.optionals (channels != { }) (
|
|
mapAttrsToList (channelName: info: genChannelString groupName info) channels
|
|
);
|
|
# Given a group, return a string that configures all the channels within
|
|
# the group.
|
|
genGroupsChannels = group: concatStringsSep "\n" (genChannelStrings group.name group.channels);
|
|
# Generate all channel configurations for all groups for this account.
|
|
in
|
|
concatStringsSep "\n" (
|
|
lib.remove "" (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, if the group has channels specified, construct the
|
|
# "Group <grpName>" header and each of the channels.
|
|
genGroupChannelString =
|
|
group:
|
|
lib.flatten (
|
|
lib.optionals (group.channels != { }) (
|
|
[ "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 constituent channels.
|
|
genGroupsStrings = mapAttrsToList (
|
|
name: info: concatStringsSep "\n" (genGroupChannelString groups.${name})
|
|
) groups;
|
|
# Join all non-empty groups.
|
|
combined = concatStringsSep "\n\n" (lib.remove "" genGroupsStrings) + "\n";
|
|
in
|
|
combined;
|
|
|
|
genGroupConfig =
|
|
name: channels:
|
|
let
|
|
genGroupChannel = n: boxes: "Channel ${n}:${concatStringsSep "," boxes}";
|
|
in
|
|
"\n" + concatStringsSep "\n" ([ "Group ${name}" ] ++ mapAttrsToList genGroupChannel channels);
|
|
|
|
in
|
|
{
|
|
meta.maintainers = [ lib.maintainers.KarlJoad ];
|
|
|
|
options = {
|
|
programs.mbsync = {
|
|
enable = lib.mkEnableOption "mbsync IMAP4 and Maildir mailbox synchronizer";
|
|
|
|
package = lib.mkPackageOption pkgs "isync" { };
|
|
|
|
groups = mkOption {
|
|
type = types.attrsOf (types.attrsOf (types.listOf types.str));
|
|
default = { };
|
|
example = literalExpression ''
|
|
{
|
|
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 ./accounts.nix));
|
|
};
|
|
};
|
|
|
|
config = mkIf cfg.enable (
|
|
lib.mkMerge [
|
|
{
|
|
assertions =
|
|
let
|
|
checkAccounts =
|
|
pred: msg:
|
|
let
|
|
badAccounts = lib.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")
|
|
];
|
|
}
|
|
|
|
(mkIf (accountInvalidOption mbsyncAccounts "masterPattern") {
|
|
warnings = [
|
|
"mbsync channels no longer use masterPattern. Use farPattern in its place."
|
|
];
|
|
})
|
|
|
|
(mkIf (accountInvalidOption mbsyncAccounts "slavePattern") {
|
|
warnings = [
|
|
"mbsync channels no longer use slavePattern. Use nearPattern in its place."
|
|
];
|
|
})
|
|
|
|
{
|
|
home.packages = [ cfg.package ];
|
|
|
|
programs.notmuch.new.ignore = [
|
|
".uidvalidity"
|
|
".mbsyncstate"
|
|
];
|
|
|
|
xdg.configFile."isyncrc".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" (lib.optional (cfg.extraConfig != "") cfg.extraConfig)
|
|
+ concatStringsSep "\n\n" accountsConfig
|
|
+ concatStringsSep "\n" groupsConfig;
|
|
|
|
home.activation = mkIf (mbsyncAccounts != [ ]) {
|
|
createMaildir = lib.hm.dag.entryBetween [ "linkGeneration" ] [ "writeBoundary" ] ''
|
|
run mkdir -m700 -p $VERBOSE_ARG ${
|
|
concatMapStringsSep " " (a: lib.escapeShellArg a.maildir.absPath) mbsyncAccounts
|
|
}
|
|
'';
|
|
};
|
|
}
|
|
]
|
|
);
|
|
}
|