at 25.11-pre 7.1 kB view raw
1{ 2 config, 3 lib, 4 pkgs, 5 ... 6}: 7 8with lib; 9 10let 11 12 cfg = config.services.vdirsyncer; 13 14 toIniJson = 15 with generators; 16 toINI { 17 mkKeyValue = mkKeyValueDefault { 18 mkValueString = builtins.toJSON; 19 } "="; 20 }; 21 22 toConfigFile = 23 name: cfg': 24 if cfg'.configFile != null then 25 cfg'.configFile 26 else 27 pkgs.writeText "vdirsyncer-${name}.conf" ( 28 toIniJson ( 29 { 30 general = cfg'.config.general // { 31 status_path = 32 if cfg'.config.statusPath == null then "/var/lib/vdirsyncer/${name}" else cfg'.config.statusPath; 33 }; 34 } 35 // (mapAttrs' (name: nameValuePair "pair ${name}") cfg'.config.pairs) 36 // (mapAttrs' (name: nameValuePair "storage ${name}") cfg'.config.storages) 37 ) 38 ); 39 40 userUnitConfig = name: cfg': { 41 serviceConfig = 42 { 43 User = if cfg'.user == null then "vdirsyncer" else cfg'.user; 44 Group = if cfg'.group == null then "vdirsyncer" else cfg'.group; 45 } 46 // (optionalAttrs (cfg'.user == null) { 47 DynamicUser = true; 48 ProtectHome = true; 49 }) 50 // (optionalAttrs (cfg'.additionalGroups != [ ]) { 51 SupplementaryGroups = cfg'.additionalGroups; 52 }) 53 // (optionalAttrs (cfg'.config.statusPath == null) { 54 StateDirectory = "vdirsyncer/${name}"; 55 StateDirectoryMode = "0700"; 56 }); 57 }; 58 59 commonUnitConfig = { 60 after = [ "network.target" ]; 61 serviceConfig = { 62 Type = "oneshot"; 63 # Sandboxing 64 PrivateTmp = true; 65 NoNewPrivileges = true; 66 ProtectSystem = "strict"; 67 ProtectKernelTunables = true; 68 ProtectKernelModules = true; 69 ProtectControlGroups = true; 70 RestrictNamespaces = true; 71 MemoryDenyWriteExecute = true; 72 RestrictRealtime = true; 73 RestrictSUIDSGID = true; 74 RestrictAddressFamilies = "AF_INET AF_INET6"; 75 LockPersonality = true; 76 }; 77 }; 78 79in 80{ 81 options = { 82 services.vdirsyncer = { 83 enable = mkEnableOption "vdirsyncer"; 84 85 package = mkPackageOption pkgs "vdirsyncer" { }; 86 87 jobs = mkOption { 88 description = "vdirsyncer job configurations"; 89 type = types.attrsOf ( 90 types.submodule { 91 options = { 92 enable = (mkEnableOption "this vdirsyncer job") // { 93 default = true; 94 example = false; 95 }; 96 97 user = mkOption { 98 type = types.nullOr types.str; 99 default = null; 100 description = '' 101 User account to run vdirsyncer as, otherwise as a systemd 102 dynamic user 103 ''; 104 }; 105 106 group = mkOption { 107 type = types.nullOr types.str; 108 default = null; 109 description = "group to run vdirsyncer as"; 110 }; 111 112 additionalGroups = mkOption { 113 type = types.listOf types.str; 114 default = [ ]; 115 description = "additional groups to add the dynamic user to"; 116 }; 117 118 forceDiscover = mkOption { 119 type = types.bool; 120 default = false; 121 description = '' 122 Run `yes | vdirsyncer discover` prior to `vdirsyncer sync` 123 ''; 124 }; 125 126 timerConfig = mkOption { 127 type = types.attrs; 128 default = { 129 OnBootSec = "1h"; 130 OnUnitActiveSec = "6h"; 131 }; 132 description = "systemd timer configuration"; 133 }; 134 135 configFile = mkOption { 136 type = types.nullOr types.path; 137 default = null; 138 description = "existing configuration file"; 139 }; 140 141 config = { 142 statusPath = mkOption { 143 type = types.nullOr types.str; 144 default = null; 145 defaultText = literalExpression "/var/lib/vdirsyncer/\${attrName}"; 146 description = "vdirsyncer's status path"; 147 }; 148 149 general = mkOption { 150 type = types.attrs; 151 default = { }; 152 description = "general configuration"; 153 }; 154 155 pairs = mkOption { 156 type = types.attrsOf types.attrs; 157 default = { }; 158 description = "vdirsyncer pair configurations"; 159 example = literalExpression '' 160 { 161 my_contacts = { 162 a = "my_cloud_contacts"; 163 b = "my_local_contacts"; 164 collections = [ "from a" ]; 165 conflict_resolution = "a wins"; 166 metadata = [ "color" "displayname" ]; 167 }; 168 }; 169 ''; 170 }; 171 172 storages = mkOption { 173 type = types.attrsOf types.attrs; 174 default = { }; 175 description = "vdirsyncer storage configurations"; 176 example = literalExpression '' 177 { 178 my_cloud_contacts = { 179 type = "carddav"; 180 url = "https://dav.example.com/"; 181 read_only = true; 182 username = "user"; 183 "password.fetch" = [ "command" "cat" "/etc/vdirsyncer/cloud.passwd" ]; 184 }; 185 my_local_contacts = { 186 type = "carddav"; 187 url = "https://localhost/"; 188 username = "user"; 189 "password.fetch" = [ "command" "cat" "/etc/vdirsyncer/local.passwd" ]; 190 }; 191 } 192 ''; 193 }; 194 }; 195 }; 196 } 197 ); 198 }; 199 }; 200 }; 201 202 config = mkIf cfg.enable { 203 systemd.services = mapAttrs' ( 204 name: cfg': 205 nameValuePair "vdirsyncer@${name}" ( 206 foldr recursiveUpdate { } [ 207 commonUnitConfig 208 (userUnitConfig name cfg') 209 { 210 description = "synchronize calendars and contacts (${name})"; 211 environment.VDIRSYNCER_CONFIG = toConfigFile name cfg'; 212 serviceConfig.ExecStart = 213 (optional cfg'.forceDiscover ( 214 pkgs.writeShellScript "vdirsyncer-discover-yes" '' 215 set -e 216 yes | ${cfg.package}/bin/vdirsyncer discover 217 '' 218 )) 219 ++ [ "${cfg.package}/bin/vdirsyncer sync" ]; 220 } 221 ] 222 ) 223 ) (filterAttrs (name: cfg': cfg'.enable) cfg.jobs); 224 225 systemd.timers = mapAttrs' ( 226 name: cfg': 227 nameValuePair "vdirsyncer@${name}" { 228 wantedBy = [ "timers.target" ]; 229 description = "synchronize calendars and contacts (${name})"; 230 inherit (cfg') timerConfig; 231 } 232 ) cfg.jobs; 233 }; 234}