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