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