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