dput-ng: fix eval (#364540)
[NixPkgs.git] / nixos / modules / services / web-apps / hedgedoc.nix
blob817114cb452ca0d3912a62fd5b31a238813af107
2   config,
3   lib,
4   pkgs,
5   ...
6 }:
8 let
9   inherit (lib) mkOption types literalExpression;
11   cfg = config.services.hedgedoc;
13   # 21.03 will not be an official release - it was instead 21.05.  This
14   # versionAtLeast statement remains set to 21.03 for backwards compatibility.
15   # See https://github.com/NixOS/nixpkgs/pull/108899 and
16   # https://github.com/NixOS/rfcs/blob/master/rfcs/0080-nixos-release-schedule.md.
17   name = if lib.versionAtLeast config.system.stateVersion "21.03" then "hedgedoc" else "codimd";
19   settingsFormat = pkgs.formats.json { };
22   meta.maintainers = with lib.maintainers; [
23     SuperSandro2000
24     h7x4
25   ];
27   imports = [
28     (lib.mkRenamedOptionModule [ "services" "codimd" ] [ "services" "hedgedoc" ])
29     (lib.mkRenamedOptionModule
30       [ "services" "hedgedoc" "configuration" ]
31       [ "services" "hedgedoc" "settings" ]
32     )
33     (lib.mkRenamedOptionModule
34       [ "services" "hedgedoc" "groups" ]
35       [ "users" "users" "hedgedoc" "extraGroups" ]
36     )
37     (lib.mkRemovedOptionModule [ "services" "hedgedoc" "workDir" ] ''
38       This option has been removed in favor of systemd managing the state directory.
40       If you have set this option without specifying `services.hedgedoc.settings.uploadsPath`,
41       please move these files to `/var/lib/hedgedoc/uploads`, or set the option to point
42       at the correct location.
43     '')
44   ];
46   options.services.hedgedoc = {
47     package = lib.mkPackageOption pkgs "hedgedoc" { };
48     enable = lib.mkEnableOption "the HedgeDoc Markdown Editor";
50     settings = mkOption {
51       type = types.submodule {
52         freeformType = settingsFormat.type;
53         options = {
54           domain = mkOption {
55             type = with types; nullOr str;
56             default = null;
57             example = "hedgedoc.org";
58             description = ''
59               Domain to use for website.
61               This is useful if you are trying to run hedgedoc behind
62               a reverse proxy.
63             '';
64           };
65           urlPath = mkOption {
66             type = with types; nullOr str;
67             default = null;
68             example = "hedgedoc";
69             description = ''
70               URL path for the website.
72               This is useful if you are hosting hedgedoc on a path like
73               `www.example.com/hedgedoc`
74             '';
75           };
76           host = mkOption {
77             type = with types; nullOr str;
78             default = "localhost";
79             description = ''
80               Address to listen on.
81             '';
82           };
83           port = mkOption {
84             type = types.port;
85             default = 3000;
86             example = 80;
87             description = ''
88               Port to listen on.
89             '';
90           };
91           path = mkOption {
92             type = with types; nullOr path;
93             default = null;
94             example = "/run/hedgedoc/hedgedoc.sock";
95             description = ''
96               Path to UNIX domain socket to listen on
98               ::: {.note}
99                 If specified, {option}`host` and {option}`port` will be ignored.
100               :::
101             '';
102           };
103           protocolUseSSL = mkOption {
104             type = types.bool;
105             default = false;
106             example = true;
107             description = ''
108               Use `https://` for all links.
110               This is useful if you are trying to run hedgedoc behind
111               a reverse proxy.
113               ::: {.note}
114                 Only applied if {option}`domain` is set.
115               :::
116             '';
117           };
118           allowOrigin = mkOption {
119             type = with types; listOf str;
120             default = with cfg.settings; [ host ] ++ lib.optionals (domain != null) [ domain ];
121             defaultText = literalExpression ''
122               with config.services.hedgedoc.settings; [ host ] ++ lib.optionals (domain != null) [ domain ]
123             '';
124             example = [
125               "localhost"
126               "hedgedoc.org"
127             ];
128             description = ''
129               List of domains to whitelist.
130             '';
131           };
132           db = mkOption {
133             type = types.attrs;
134             default = {
135               dialect = "sqlite";
136               storage = "/var/lib/${name}/db.sqlite";
137             };
138             defaultText = literalExpression ''
139               {
140                 dialect = "sqlite";
141                 storage = "/var/lib/hedgedoc/db.sqlite";
142               }
143             '';
144             example = literalExpression ''
145               db = {
146                 username = "hedgedoc";
147                 database = "hedgedoc";
148                 host = "localhost:5432";
149                 # or via socket
150                 # host = "/run/postgresql";
151                 dialect = "postgresql";
152               };
153             '';
154             description = ''
155               Specify the configuration for sequelize.
156               HedgeDoc supports `mysql`, `postgres`, `sqlite` and `mssql`.
157               See <https://sequelize.readthedocs.io/en/v3/>
158               for more information.
160               ::: {.note}
161                 The relevant parts will be overriden if you set {option}`dbURL`.
162               :::
163             '';
164           };
165           useSSL = mkOption {
166             type = types.bool;
167             default = false;
168             description = ''
169               Enable to use SSL server.
171               ::: {.note}
172                 This will also enable {option}`protocolUseSSL`.
174                 It will also require you to set the following:
176                 - {option}`sslKeyPath`
177                 - {option}`sslCertPath`
178                 - {option}`sslCAPath`
179                 - {option}`dhParamPath`
180               :::
181             '';
182           };
183           uploadsPath = mkOption {
184             type = types.path;
185             default = "/var/lib/${name}/uploads";
186             defaultText = "/var/lib/hedgedoc/uploads";
187             description = ''
188               Directory for storing uploaded images.
189             '';
190           };
192           # Declared because we change the default to false.
193           allowGravatar = mkOption {
194             type = types.bool;
195             default = false;
196             example = true;
197             description = ''
198               Whether to enable [Libravatar](https://wiki.libravatar.org/) as
199               profile picture source on your instance.
201               Despite the naming of the setting, Hedgedoc replaced Gravatar
202               with Libravatar in [CodiMD 1.4.0](https://hedgedoc.org/releases/1.4.0/)
203             '';
204           };
205         };
206       };
208       description = ''
209         HedgeDoc configuration, see
210         <https://docs.hedgedoc.org/configuration/>
211         for documentation.
212       '';
213     };
215     environmentFile = mkOption {
216       type = with types; nullOr path;
217       default = null;
218       example = "/var/lib/hedgedoc/hedgedoc.env";
219       description = ''
220         Environment file as defined in {manpage}`systemd.exec(5)`.
222         Secrets may be passed to the service without adding them to the world-readable
223         Nix store, by specifying placeholder variables as the option value in Nix and
224         setting these variables accordingly in the environment file.
226         ```
227           # snippet of HedgeDoc-related config
228           services.hedgedoc.settings.dbURL = "postgres://hedgedoc:\''${DB_PASSWORD}@db-host:5432/hedgedocdb";
229           services.hedgedoc.settings.minio.secretKey = "$MINIO_SECRET_KEY";
230         ```
232         ```
233           # content of the environment file
234           DB_PASSWORD=verysecretdbpassword
235           MINIO_SECRET_KEY=verysecretminiokey
236         ```
238         Note that this file needs to be available on the host on which
239         `HedgeDoc` is running.
240       '';
241     };
242   };
244   config = lib.mkIf cfg.enable {
245     users.groups.${name} = { };
246     users.users.${name} = {
247       description = "HedgeDoc service user";
248       group = name;
249       isSystemUser = true;
250     };
252     services.hedgedoc.settings = {
253       defaultNotePath = lib.mkDefault "${cfg.package}/share/hedgedoc/public/default.md";
254       docsPath = lib.mkDefault "${cfg.package}/share/hedgedoc/public/docs";
255       viewPath = lib.mkDefault "${cfg.package}/share/hedgedoc/public/views";
256     };
258     systemd.services.hedgedoc = {
259       description = "HedgeDoc Service";
260       documentation = [ "https://docs.hedgedoc.org/" ];
261       wantedBy = [ "multi-user.target" ];
262       after = [ "networking.target" ];
263       preStart =
264         let
265           configFile = settingsFormat.generate "hedgedoc-config.json" {
266             production = cfg.settings;
267           };
268         in
269         ''
270           ${pkgs.envsubst}/bin/envsubst \
271             -o /run/${name}/config.json \
272             -i ${configFile}
273           ${pkgs.coreutils}/bin/mkdir -p ${cfg.settings.uploadsPath}
274         '';
275       serviceConfig = {
276         User = name;
277         Group = name;
279         Restart = "always";
280         ExecStart = lib.getExe cfg.package;
281         RuntimeDirectory = [ name ];
282         StateDirectory = [ name ];
283         WorkingDirectory = "/run/${name}";
284         ReadWritePaths = [
285           "-${cfg.settings.uploadsPath}"
286         ] ++ lib.optionals (cfg.settings.db ? "storage") [ "-${cfg.settings.db.storage}" ];
287         EnvironmentFile = lib.mkIf (cfg.environmentFile != null) [ cfg.environmentFile ];
288         Environment = [
289           "CMD_CONFIG_FILE=/run/${name}/config.json"
290           "NODE_ENV=production"
291         ];
293         # Hardening
294         AmbientCapabilities = "";
295         CapabilityBoundingSet = "";
296         LockPersonality = true;
297         NoNewPrivileges = true;
298         PrivateDevices = true;
299         PrivateMounts = true;
300         PrivateTmp = true;
301         PrivateUsers = true;
302         ProcSubset = "pid";
303         ProtectClock = true;
304         ProtectControlGroups = true;
305         ProtectHome = true;
306         ProtectHostname = true;
307         ProtectKernelLogs = true;
308         ProtectKernelModules = true;
309         ProtectKernelTunables = true;
310         ProtectProc = "invisible";
311         ProtectSystem = "strict";
312         RemoveIPC = true;
313         RestrictAddressFamilies = [
314           "AF_INET"
315           "AF_INET6"
316           # Required for connecting to database sockets,
317           # and listening to unix socket at `cfg.settings.path`
318           "AF_UNIX"
319         ];
320         RestrictNamespaces = true;
321         RestrictRealtime = true;
322         RestrictSUIDSGID = true;
323         SocketBindAllow = lib.mkIf (cfg.settings.path == null) cfg.settings.port;
324         SocketBindDeny = "any";
325         SystemCallArchitectures = "native";
326         SystemCallFilter = [
327           "@system-service"
328           "~@privileged @obsolete"
329           "@pkey"
330         ];
331         UMask = "0007";
332       };
333     };
334   };