11 cfg = config.services.radicale;
13 format = pkgs.formats.ini {
14 listToValue = concatMapStringsSep ", " (generators.mkValueStringDefault { });
17 pkg = if cfg.package == null then pkgs.radicale else cfg.package;
20 if cfg.settings == { } then
21 pkgs.writeText "radicale.conf" cfg.config
23 format.generate "radicale.conf" cfg.settings;
25 rightsFile = format.generate "radicale.rights" cfg.rights;
27 bindLocalhost = cfg.settings != { } && !hasAttrByPath [ "server" "hosts" ] cfg.settings;
31 options.services.radicale = {
32 enable = mkEnableOption "Radicale CalDAV and CardDAV server";
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; };
40 defaultText = literalExpression "pkgs.radicale";
47 Radicale configuration, this will set the service
49 This option is mutually exclusive with {option}`settings`.
50 This option is deprecated. Use {option}`settings` instead.
58 Configuration for Radicale. See
59 <https://radicale.org/v3.html#configuration>.
60 This option is mutually exclusive with {option}`config`.
62 example = literalExpression ''
64 hosts = [ "0.0.0.0:5232" "[::]:5232" ];
68 htpasswd_filename = "/etc/radicale/users";
69 htpasswd_encryption = "bcrypt";
72 filesystem_folder = "/var/lib/radicale/collections";
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.
87 example = literalExpression ''
95 collection = "{user}";
100 collection = "{user}/[^/]+";
106 extraArgs = mkOption {
107 type = types.listOf types.str;
109 description = "Extra arguments passed to the Radicale daemon.";
113 config = mkIf cfg.enable {
116 assertion = cfg.settings == { } || cfg.config == "";
118 The options services.radicale.config and services.radicale.settings
119 are mutually exclusive.
125 optional (cfg.package == null && versionOlder config.system.stateVersion "17.09") ''
126 The configuration and storage formats of your existing Radicale
127 installation might be incompatible with the newest version.
128 For upgrade instructions see
129 https://radicale.org/2.1.html#documentation/migration-from-1xx-to-2xx.
130 Set services.radicale.package to suppress this warning.
132 ++ optional (cfg.package == null && versionOlder config.system.stateVersion "20.09") ''
133 The configuration format of your existing Radicale installation might be
134 incompatible with the newest version. For upgrade instructions see
135 https://github.com/Kozea/Radicale/blob/3.0.6/NEWS.md#upgrade-checklist.
136 Set services.radicale.package to suppress this warning.
138 ++ optional (cfg.config != "") ''
139 The option services.radicale.config is deprecated.
140 Use services.radicale.settings instead.
143 services.radicale.settings.rights = mkIf (cfg.rights != { }) {
145 file = toString rightsFile;
148 environment.systemPackages = [ pkg ];
150 users.users.radicale = {
155 users.groups.radicale = { };
157 systemd.services.radicale = {
158 description = "A Simple Calendar and Contact Server";
159 after = [ "network.target" ];
160 requires = [ "network.target" ];
161 wantedBy = [ "multi-user.target" ];
163 ExecStart = concatStringsSep " " (
165 "${pkg}/bin/radicale"
169 ++ (map escapeShellArg cfg.extraArgs)
173 StateDirectory = "radicale/collections";
174 StateDirectoryMode = "0750";
176 CapabilityBoundingSet = [ "" ];
181 DevicePolicy = "strict";
182 IPAddressAllow = mkIf bindLocalhost "localhost";
183 IPAddressDeny = mkIf bindLocalhost "any";
184 LockPersonality = true;
185 MemoryDenyWriteExecute = true;
186 NoNewPrivileges = true;
187 PrivateDevices = true;
192 ProtectControlGroups = true;
194 ProtectHostname = true;
195 ProtectKernelLogs = true;
196 ProtectKernelModules = true;
197 ProtectKernelTunables = true;
198 ProtectProc = "invisible";
199 ProtectSystem = "strict";
200 ReadWritePaths = lib.optional (hasAttrByPath [
203 ] cfg.settings) cfg.settings.storage.filesystem_folder;
205 RestrictAddressFamilies = [
209 RestrictNamespaces = true;
210 RestrictRealtime = true;
211 RestrictSUIDSGID = true;
212 SystemCallArchitectures = "native";
219 WorkingDirectory = "/var/lib/radicale";
224 meta.maintainers = with lib.maintainers; [ dotlambda ];