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