2020-10-12 00:22:58 +00: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 ) ;
2021-07-02 22:36:30 +00:00
# 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 ) ;
2020-10-12 00:22:58 +00:00
genTlsConfig = tls :
{
SSLType = if ! tls . enable then
" N o n e "
else if tls . useStartTls then
" S T A R T T L S "
else
" I M A P S " ;
} // optionalAttrs ( tls . enable && tls . certificatesFile != null ) {
CertificateFile = toString tls . certificatesFile ;
} ;
2021-07-02 22:36:30 +00:00
imports = [
( mkRenamedOptionModule [ " p r o g r a m s " " m b s y n c " " m a s t e r S l a v e M a p p i n g " ] [
" p r o g r a m s "
" m b s y n c "
" n e a r F a r M a p p i n g "
] )
] ;
nearFarMapping = {
2020-10-12 00:22:58 +00:00
none = " N o n e " ;
2021-07-02 22:36:30 +00:00
imap = " F a r " ;
maildir = " N e a r " ;
2020-10-12 00:22:58 +00:00
both = " B o t h " ;
} ;
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
2023-01-10 09:35:00 +00:00
lib . hm . booleans . yesNo v
2020-10-12 00:22:58 +00:00
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 " m b s y n c : u n e x p e c t e d v a l u e f o r o p t i o n ${ n } : ' ${ prettyV } ' " ;
in ''
$ { header }
$ { concatStringsSep " \n "
( mapAttrsToList ( n : v : " ${ n } ${ genValue n v } " ) entries ) }
'' ;
genAccountConfig = account :
with account ;
genSection " I M A P A c c o u n t ${ 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 " I M A P S t o r e ${ name } - r e m o t e "
( { Account = name ; } // mbsync . extraConfig . remote ) + " \n "
+ genSection " M a i l d i r S t o r e ${ name } - l o c a l " ( {
Inbox = " ${ maildir . absPath } / ${ folders . inbox } " ;
2021-07-02 22:36:30 +00:00
} // optionalAttrs
( mbsync . subFolders != " M a i l d i r + + " || mbsync . flatten != null ) {
Path = " ${ maildir . absPath } / " ;
} // optionalAttrs ( mbsync . flatten == null ) {
SubFolders = mbsync . subFolders ;
} // optionalAttrs ( mbsync . flatten != null ) { Flatten = mbsync . flatten ; }
2020-10-12 00:22:58 +00:00
// mbsync . extraConfig . local ) + " \n " + genChannels account ;
genChannels = account :
with account ;
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 :
with account ;
genSection " C h a n n e l ${ name } " ( {
2021-07-02 22:36:30 +00:00
Far = " : ${ name } - r e m o t e : " ;
Near = " : ${ name } - l o c a l : " ;
2020-10-12 00:22:58 +00:00
Patterns = mbsync . patterns ;
2021-07-02 22:36:30 +00:00
Create = nearFarMapping . ${ mbsync . create } ;
Remove = nearFarMapping . ${ mbsync . remove } ;
Expunge = nearFarMapping . ${ mbsync . expunge } ;
2020-10-12 00:22:58 +00:00
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
2021-07-02 22:36:30 +00:00
# "far" pattern definition.
2020-10-12 00:22:58 +00:00
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 = 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
" P a t t e r n " + concatStringsSep " "
( map ( pat : if hasSpace pat then escapeValue pat else pat )
patterns ) + " \n "
else
" " ;
in genSection " C h a n n e l ${ groupName } - ${ channel . name } " ( {
2021-07-02 22:36:30 +00:00
Far = " : ${ storeName } - r e m o t e : ${ channel . farPattern } " ;
Near = " : ${ storeName } - l o c a l : ${ channel . nearPattern } " ;
2020-10-12 00:22:58 +00:00
} // 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 :
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.
2023-01-10 09:35:00 +00:00
in concatStringsSep " \n "
( remove " " ( mapAttrsToList ( name : group : genGroupsChannels group ) groups ) ) ;
2020-10-12 00:22:58 +00: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 : " C h a n n e l ${ 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 :
flatten ( optionals ( group . channels != { } ) ( [ " G r o u p ${ group . name } " ]
++ ( genChannelStrings group . name group . channels ) ) ) ;
# Given set of groups, generates list of strings, where each string is one
2023-08-08 10:19:01 +00:00
# of the groups and its constituent channels.
2020-10-12 00:22:58 +00:00
genGroupsStrings = mapAttrsToList ( name : info :
concatStringsSep " \n " ( genGroupChannelString groups . ${ name } ) ) groups ;
2023-01-10 09:35:00 +00:00
# Join all non-empty groups.
combined = concatStringsSep " \n \n " ( remove " " genGroupsStrings ) + " \n " ;
in combined ;
2020-10-12 00:22:58 +00:00
genGroupConfig = name : channels :
let
genGroupChannel = n : boxes : " C h a n n e l ${ n } : ${ concatStringsSep " , " boxes } " ;
in " \n " + concatStringsSep " \n "
( [ " G r o u p ${ name } " ] ++ mapAttrsToList genGroupChannel channels ) ;
in {
meta . maintainers = [ maintainers . KarlJoad ] ;
options = {
programs . mbsync = {
enable = mkEnableOption " m b s y n c I M A P 4 a n d M a i l d i r m a i l b o x s y n c h r o n i z e r " ;
package = mkOption {
type = types . package ;
default = pkgs . isync ;
2021-11-04 16:42:44 +00:00
defaultText = literalExpression " p k g s . i s y n c " ;
example = literalExpression " p k g s . i s y n c " ;
2020-10-12 00:22:58 +00:00
description = " T h e p a c k a g e t o u s e f o r t h e m b s y n c b i n a r y . " ;
} ;
groups = mkOption {
type = types . attrsOf ( types . attrsOf ( types . listOf types . str ) ) ;
default = { } ;
2021-11-04 16:42:44 +00:00
example = literalExpression ''
2020-10-12 00:22:58 +00:00
{
inboxes = {
account1 = [ " I n b o x " ] ;
account2 = [ " I n b o x " ] ;
} ;
}
'' ;
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 ) ) ;
} ;
} ;
2021-07-02 22:36:30 +00:00
config = mkIf cfg . enable ( mkMerge [
{
assertions = let
checkAccounts = pred : msg :
let badAccounts = filter pred mbsyncAccounts ;
in {
assertion = badAccounts == [ ] ;
message = " m b s y n c : ${ msg } f o r a c c o u n t s : "
+ concatMapStringsSep " , " ( a : a . name ) badAccounts ;
} ;
in [
( checkAccounts ( a : a . maildir == null ) " M i s s i n g m a i l d i r c o n f i g u r a t i o n " )
( checkAccounts ( a : a . imap == null ) " M i s s i n g I M A P c o n f i g u r a t i o n " )
( checkAccounts ( a : a . passwordCommand == null ) " M i s s i n g p a s s w o r d C o m m a n d " )
( checkAccounts ( a : a . userName == null ) " M i s s i n g u s e r n a m e " )
] ;
}
( mkIf ( accountInvalidOption mbsyncAccounts " m a s t e r P a t t e r n " ) {
warnings = [
" m b s y n c c h a n n e l s n o l o n g e r u s e m a s t e r P a t t e r n . U s e f a r P a t t e r n i n i t s p l a c e . "
] ;
} )
( mkIf ( accountInvalidOption mbsyncAccounts " s l a v e P a t t e r n " ) {
warnings = [
" m b s y n c c h a n n e l s n o l o n g e r u s e s l a v e P a t t e r n . U s e n e a r P a t t e r n i n i t s p l a c e . "
] ;
} )
{
home . packages = [ cfg . package ] ;
programs . notmuch . new . ignore = [ " . u i d v a l i d i t y " " . m b s y n c s t a t e " ] ;
home . file . " . m b s y n c r c " . 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 [ " l i n k G e n e r a t i o n " ] [ " w r i t e B o u n d a r y " ] ''
$ DRY_RUN_CMD mkdir - m700 - p $ VERBOSE_ARG $ {
concatMapStringsSep " " ( a : a . maildir . absPath ) mbsyncAccounts
}
'' ;
} ;
}
] ) ;
2020-10-12 00:22:58 +00:00
}