at 25.11-pre 7.1 kB view raw
1{ 2 config, 3 lib, 4 pkgs, 5 ... 6}: 7 8with lib; 9 10let 11 cfg = config.services.radicale; 12 13 format = pkgs.formats.ini { 14 listToValue = concatMapStringsSep ", " (generators.mkValueStringDefault { }); 15 }; 16 17 pkg = if cfg.package == null then pkgs.radicale else cfg.package; 18 19 confFile = 20 if cfg.settings == { } then 21 pkgs.writeText "radicale.conf" cfg.config 22 else 23 format.generate "radicale.conf" cfg.settings; 24 25 rightsFile = format.generate "radicale.rights" cfg.rights; 26 27 bindLocalhost = cfg.settings != { } && !hasAttrByPath [ "server" "hosts" ] cfg.settings; 28 29in 30{ 31 options.services.radicale = { 32 enable = mkEnableOption "Radicale CalDAV and CardDAV server"; 33 34 package = mkOption { 35 description = "Radicale package to use."; 36 # Default cannot be pkgs.radicale because non-null values suppress 37 # warnings about incompatible configuration and storage formats. 38 type = with types; nullOr package // { inherit (package) description; }; 39 default = null; 40 defaultText = literalExpression "pkgs.radicale"; 41 }; 42 43 config = mkOption { 44 type = types.str; 45 default = ""; 46 description = '' 47 Radicale configuration, this will set the service 48 configuration file. 49 This option is mutually exclusive with {option}`settings`. 50 This option is deprecated. Use {option}`settings` instead. 51 ''; 52 }; 53 54 settings = mkOption { 55 type = format.type; 56 default = { }; 57 description = '' 58 Configuration for Radicale. See 59 <https://radicale.org/v3.html#configuration>. 60 This option is mutually exclusive with {option}`config`. 61 ''; 62 example = literalExpression '' 63 server = { 64 hosts = [ "0.0.0.0:5232" "[::]:5232" ]; 65 }; 66 auth = { 67 type = "htpasswd"; 68 htpasswd_filename = "/etc/radicale/users"; 69 htpasswd_encryption = "bcrypt"; 70 }; 71 storage = { 72 filesystem_folder = "/var/lib/radicale/collections"; 73 }; 74 ''; 75 }; 76 77 rights = mkOption { 78 type = format.type; 79 description = '' 80 Configuration for Radicale's rights file. See 81 <https://radicale.org/v3.html#authentication-and-rights>. 82 This option only works in conjunction with {option}`settings`. 83 Setting this will also set {option}`settings.rights.type` and 84 {option}`settings.rights.file` to appropriate values. 85 ''; 86 default = { }; 87 example = literalExpression '' 88 root = { 89 user = ".+"; 90 collection = ""; 91 permissions = "R"; 92 }; 93 principal = { 94 user = ".+"; 95 collection = "{user}"; 96 permissions = "RW"; 97 }; 98 calendars = { 99 user = ".+"; 100 collection = "{user}/[^/]+"; 101 permissions = "rw"; 102 }; 103 ''; 104 }; 105 106 extraArgs = mkOption { 107 type = types.listOf types.str; 108 default = [ ]; 109 description = "Extra arguments passed to the Radicale daemon."; 110 }; 111 }; 112 113 config = mkIf cfg.enable { 114 assertions = [ 115 { 116 assertion = cfg.settings == { } || cfg.config == ""; 117 message = '' 118 The options services.radicale.config and services.radicale.settings 119 are mutually exclusive. 120 ''; 121 } 122 { 123 assertion = cfg.config != "" || (cfg.settings ? auth && cfg.settings.auth ? type); 124 message = '' 125 Radicale 3.5.0 changed the default value for `auth.type` from `none` to `denyall`. 126 You probably don't want `denyall`, so please set this explicitly. 127 https://github.com/Kozea/Radicale/blob/v3.5.0/CHANGELOG.md 128 ''; 129 } 130 ]; 131 132 warnings = 133 optional (cfg.package == null && versionOlder config.system.stateVersion "17.09") '' 134 The configuration and storage formats of your existing Radicale 135 installation might be incompatible with the newest version. 136 For upgrade instructions see 137 https://radicale.org/2.1.html#documentation/migration-from-1xx-to-2xx. 138 Set services.radicale.package to suppress this warning. 139 '' 140 ++ optional (cfg.package == null && versionOlder config.system.stateVersion "20.09") '' 141 The configuration format of your existing Radicale installation might be 142 incompatible with the newest version. For upgrade instructions see 143 https://github.com/Kozea/Radicale/blob/3.0.6/NEWS.md#upgrade-checklist. 144 Set services.radicale.package to suppress this warning. 145 '' 146 ++ optional (cfg.config != "") '' 147 The option services.radicale.config is deprecated. 148 Use services.radicale.settings instead. 149 ''; 150 151 services.radicale.settings.rights = mkIf (cfg.rights != { }) { 152 type = "from_file"; 153 file = toString rightsFile; 154 }; 155 156 environment.systemPackages = [ pkg ]; 157 158 users.users.radicale = { 159 isSystemUser = true; 160 group = "radicale"; 161 }; 162 163 users.groups.radicale = { }; 164 165 systemd.services.radicale = { 166 description = "A Simple Calendar and Contact Server"; 167 after = [ "network.target" ]; 168 requires = [ "network.target" ]; 169 wantedBy = [ "multi-user.target" ]; 170 serviceConfig = { 171 ExecStart = concatStringsSep " " ( 172 [ 173 "${pkg}/bin/radicale" 174 "-C" 175 confFile 176 ] 177 ++ (map escapeShellArg cfg.extraArgs) 178 ); 179 User = "radicale"; 180 Group = "radicale"; 181 StateDirectory = "radicale/collections"; 182 StateDirectoryMode = "0750"; 183 # Hardening 184 CapabilityBoundingSet = [ "" ]; 185 DeviceAllow = [ 186 "/dev/stdin" 187 "/dev/urandom" 188 ]; 189 DevicePolicy = "strict"; 190 IPAddressAllow = mkIf bindLocalhost "localhost"; 191 IPAddressDeny = mkIf bindLocalhost "any"; 192 LockPersonality = true; 193 MemoryDenyWriteExecute = true; 194 NoNewPrivileges = true; 195 PrivateDevices = true; 196 PrivateTmp = true; 197 PrivateUsers = true; 198 ProcSubset = "pid"; 199 ProtectClock = true; 200 ProtectControlGroups = true; 201 ProtectHome = true; 202 ProtectHostname = true; 203 ProtectKernelLogs = true; 204 ProtectKernelModules = true; 205 ProtectKernelTunables = true; 206 ProtectProc = "invisible"; 207 ProtectSystem = "strict"; 208 ReadWritePaths = lib.optional (hasAttrByPath [ 209 "storage" 210 "filesystem_folder" 211 ] cfg.settings) cfg.settings.storage.filesystem_folder; 212 RemoveIPC = true; 213 RestrictAddressFamilies = [ 214 "AF_INET" 215 "AF_INET6" 216 "AF_UNIX" # To log with systemd 217 ]; 218 RestrictNamespaces = true; 219 RestrictRealtime = true; 220 RestrictSUIDSGID = true; 221 SystemCallArchitectures = "native"; 222 SystemCallFilter = [ 223 "@system-service" 224 "~@privileged" 225 "~@resources" 226 ]; 227 UMask = "0027"; 228 WorkingDirectory = "/var/lib/radicale"; 229 }; 230 }; 231 }; 232 233 meta.maintainers = with lib.maintainers; [ dotlambda ]; 234}