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