normcap: fix on GNOME wayland when used via keybind or alt-f2 (#351763)
[NixPkgs.git] / nixos / modules / services / networking / radicale.nix
blobd210efe14b3c974454e0b6d0d3aa325134c5851c
2   config,
3   lib,
4   pkgs,
5   ...
6 }:
8 with lib;
10 let
11   cfg = config.services.radicale;
13   format = pkgs.formats.ini {
14     listToValue = concatMapStringsSep ", " (generators.mkValueStringDefault { });
15   };
17   pkg = if cfg.package == null then pkgs.radicale else cfg.package;
19   confFile =
20     if cfg.settings == { } then
21       pkgs.writeText "radicale.conf" cfg.config
22     else
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";
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     };
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     };
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     };
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     };
106     extraArgs = mkOption {
107       type = types.listOf types.str;
108       default = [ ];
109       description = "Extra arguments passed to the Radicale daemon.";
110     };
111   };
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     ];
124     warnings =
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.
131       ''
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.
137       ''
138       ++ optional (cfg.config != "") ''
139         The option services.radicale.config is deprecated.
140         Use services.radicale.settings instead.
141       '';
143     services.radicale.settings.rights = mkIf (cfg.rights != { }) {
144       type = "from_file";
145       file = toString rightsFile;
146     };
148     environment.systemPackages = [ pkg ];
150     users.users.radicale = {
151       isSystemUser = true;
152       group = "radicale";
153     };
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" ];
162       serviceConfig = {
163         ExecStart = concatStringsSep " " (
164           [
165             "${pkg}/bin/radicale"
166             "-C"
167             confFile
168           ]
169           ++ (map escapeShellArg cfg.extraArgs)
170         );
171         User = "radicale";
172         Group = "radicale";
173         StateDirectory = "radicale/collections";
174         StateDirectoryMode = "0750";
175         # Hardening
176         CapabilityBoundingSet = [ "" ];
177         DeviceAllow = [
178           "/dev/stdin"
179           "/dev/urandom"
180         ];
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;
188         PrivateTmp = true;
189         PrivateUsers = true;
190         ProcSubset = "pid";
191         ProtectClock = true;
192         ProtectControlGroups = true;
193         ProtectHome = true;
194         ProtectHostname = true;
195         ProtectKernelLogs = true;
196         ProtectKernelModules = true;
197         ProtectKernelTunables = true;
198         ProtectProc = "invisible";
199         ProtectSystem = "strict";
200         ReadWritePaths = lib.optional (hasAttrByPath [
201           "storage"
202           "filesystem_folder"
203         ] cfg.settings) cfg.settings.storage.filesystem_folder;
204         RemoveIPC = true;
205         RestrictAddressFamilies = [
206           "AF_INET"
207           "AF_INET6"
208         ];
209         RestrictNamespaces = true;
210         RestrictRealtime = true;
211         RestrictSUIDSGID = true;
212         SystemCallArchitectures = "native";
213         SystemCallFilter = [
214           "@system-service"
215           "~@privileged"
216           "~@resources"
217         ];
218         UMask = "0027";
219         WorkingDirectory = "/var/lib/radicale";
220       };
221     };
222   };
224   meta.maintainers = with lib.maintainers; [ dotlambda ];