1 { config, lib, pkgs, ... }:
6 cfg = config.services.radicale;
8 format = pkgs.formats.ini {
9 listToValue = concatMapStringsSep ", " (generators.mkValueStringDefault { });
12 pkg = if cfg.package == null then
17 confFile = if cfg.settings == { } then
18 pkgs.writeText "radicale.conf" cfg.config
20 format.generate "radicale.conf" cfg.settings;
22 rightsFile = format.generate "radicale.rights" cfg.rights;
24 bindLocalhost = cfg.settings != { } && !hasAttrByPath [ "server" "hosts" ] cfg.settings;
27 options.services.radicale = {
28 enable = mkEnableOption "Radicale CalDAV and CardDAV server";
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; };
36 defaultText = literalExpression "pkgs.radicale";
43 Radicale configuration, this will set the service
45 This option is mutually exclusive with {option}`settings`.
46 This option is deprecated. Use {option}`settings` instead.
54 Configuration for Radicale. See
55 <https://radicale.org/3.0.html#documentation/configuration>.
56 This option is mutually exclusive with {option}`config`.
58 example = literalExpression ''
60 hosts = [ "0.0.0.0:5232" "[::]:5232" ];
64 htpasswd_filename = "/etc/radicale/users";
65 htpasswd_encryption = "bcrypt";
68 filesystem_folder = "/var/lib/radicale/collections";
76 Configuration for Radicale's rights file. See
77 <https://radicale.org/3.0.html#documentation/authentication-and-rights>.
78 This option only works in conjunction with {option}`settings`.
79 Setting this will also set {option}`settings.rights.type` and
80 {option}`settings.rights.file` to appropriate values.
83 example = literalExpression ''
91 collection = "{user}";
96 collection = "{user}/[^/]+";
102 extraArgs = mkOption {
103 type = types.listOf types.str;
105 description = "Extra arguments passed to the Radicale daemon.";
109 config = mkIf cfg.enable {
112 assertion = cfg.settings == { } || cfg.config == "";
114 The options services.radicale.config and services.radicale.settings
115 are mutually exclusive.
120 warnings = optional (cfg.package == null && 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 (cfg.package == null && 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.
136 services.radicale.settings.rights = mkIf (cfg.rights != { }) {
138 file = toString rightsFile;
141 environment.systemPackages = [ pkg ];
143 users.users.radicale = {
148 users.groups.radicale = {};
150 systemd.services.radicale = {
151 description = "A Simple Calendar and Contact Server";
152 after = [ "network.target" ];
153 requires = [ "network.target" ];
154 wantedBy = [ "multi-user.target" ];
156 ExecStart = concatStringsSep " " ([
157 "${pkg}/bin/radicale" "-C" confFile
159 map escapeShellArg cfg.extraArgs
163 StateDirectory = "radicale/collections";
164 StateDirectoryMode = "0750";
166 CapabilityBoundingSet = [ "" ];
167 DeviceAllow = [ "/dev/stdin" "/dev/urandom" ];
168 DevicePolicy = "strict";
169 IPAddressAllow = mkIf bindLocalhost "localhost";
170 IPAddressDeny = mkIf bindLocalhost "any";
171 LockPersonality = true;
172 MemoryDenyWriteExecute = true;
173 NoNewPrivileges = true;
174 PrivateDevices = true;
179 ProtectControlGroups = true;
181 ProtectHostname = true;
182 ProtectKernelLogs = true;
183 ProtectKernelModules = true;
184 ProtectKernelTunables = true;
185 ProtectProc = "invisible";
186 ProtectSystem = "strict";
187 ReadWritePaths = lib.optional
188 (hasAttrByPath [ "storage" "filesystem_folder" ] cfg.settings)
189 cfg.settings.storage.filesystem_folder;
191 RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
192 RestrictNamespaces = true;
193 RestrictRealtime = true;
194 RestrictSUIDSGID = true;
195 SystemCallArchitectures = "native";
196 SystemCallFilter = [ "@system-service" "~@privileged" "~@resources" ];
198 WorkingDirectory = "/var/lib/radicale";
203 meta.maintainers = with lib.maintainers; [ dotlambda ];