python312Packages.dissect-extfs: 3.11 -> 3.12
[NixPkgs.git] / nixos / modules / services / web-servers / stargazer.nix
blob249fd30bf6001f75cf752c098d4a647facb5a594
1 { config, lib, pkgs, ... }:
3 let
4   cfg = config.services.stargazer;
5   globalSection = ''
6     listen = ${lib.concatStringsSep " " cfg.listen}
7     connection-logging = ${lib.boolToString cfg.connectionLogging}
8     log-ip = ${lib.boolToString cfg.ipLog}
9     log-ip-partial = ${lib.boolToString cfg.ipLogPartial}
10     request-timeout = ${toString cfg.requestTimeout}
11     response-timeout = ${toString cfg.responseTimeout}
13     [:tls]
14     store = ${toString cfg.store}
15     organization = ${cfg.certOrg}
16     gen-certs = ${lib.boolToString cfg.genCerts}
17     regen-certs = ${lib.boolToString cfg.regenCerts}
18     ${lib.optionalString (cfg.certLifetime != "") "cert-lifetime = ${cfg.certLifetime}"}
20   '';
21   genINI = lib.generators.toINI { };
22   configFile = pkgs.writeText "config.ini" (lib.strings.concatStrings (
23     [ globalSection ] ++ (lib.lists.forEach cfg.routes (section:
24       let
25         name = section.route;
26         params = builtins.removeAttrs section [ "route" ];
27       in
28       genINI
29         {
30           "${name}" = params;
31         } + "\n"
32     ))
33   ));
36   options.services.stargazer = {
37     enable = lib.mkEnableOption "Stargazer Gemini server";
39     listen = lib.mkOption {
40       type = lib.types.listOf lib.types.str;
41       default = [ "0.0.0.0" ] ++ lib.optional config.networking.enableIPv6 "[::0]";
42       defaultText = lib.literalExpression ''[ "0.0.0.0" ] ++ lib.optional config.networking.enableIPv6 "[::0]"'';
43       example = lib.literalExpression ''[ "10.0.0.12" "[2002:a00:1::]" ]'';
44       description = ''
45         Address and port to listen on.
46       '';
47     };
49     connectionLogging = lib.mkOption {
50       type = lib.types.bool;
51       default = true;
52       description = "Whether or not to log connections to stdout.";
53     };
55     ipLog = lib.mkOption {
56       type = lib.types.bool;
57       default = false;
58       description = "Log client IP addresses in the connection log.";
59     };
61     ipLogPartial = lib.mkOption {
62       type = lib.types.bool;
63       default = false;
64       description = "Log partial client IP addresses in the connection log.";
65     };
67     requestTimeout = lib.mkOption {
68       type = lib.types.int;
69       default = 5;
70       description = ''
71         Number of seconds to wait for the client to send a complete
72         request. Set to 0 to disable.
73       '';
74     };
76     responseTimeout = lib.mkOption {
77       type = lib.types.int;
78       default = 0;
79       description = ''
80         Number of seconds to wait for the client to send a complete
81         request and for stargazer to finish sending the response.
82         Set to 0 to disable.
83       '';
84     };
86     allowCgiUser = lib.mkOption {
87       type = lib.types.bool;
88       default = false;
89       description = ''
90         When enabled, the stargazer process will be given `CAP_SETGID`
91         and `CAP_SETUID` so that it can run cgi processes as a different
92         user. This is required if the `cgi-user` option is used for a route.
93         Note that these capabilities could allow privilege escalation so be
94         careful. For that reason, this is disabled by default.
96         You will need to create the user mentioned `cgi-user` if it does not
97         already exist.
98       '';
99     };
101     store = lib.mkOption {
102       type = lib.types.path;
103       default = /var/lib/gemini/certs;
104       description = ''
105         Path to the certificate store on disk. This should be a
106         persistent directory writable by Stargazer.
107       '';
108     };
110     certOrg = lib.mkOption {
111       type = lib.types.str;
112       default = "stargazer";
113       description = ''
114         The name of the organization responsible for the X.509
115         certificate's /O name.
116       '';
117     };
119     genCerts = lib.mkOption {
120       type = lib.types.bool;
121       default = true;
122       description = ''
123         Set to false to disable automatic certificate generation.
124         Use if you want to provide your own certs.
125       '';
126     };
128     regenCerts = lib.mkOption {
129       type = lib.types.bool;
130       default = true;
131       description = ''
132         Set to false to turn off automatic regeneration of expired certificates.
133         Use if you want to provide your own certs.
134       '';
135     };
137     certLifetime = lib.mkOption {
138       type = lib.types.str;
139       default = "";
140       description = ''
141         How long certs generated by Stargazer should live for.
142         Certs live forever by default.
143       '';
144       example = lib.literalExpression "\"1y\"";
145     };
147     debugMode = lib.mkOption {
148       type = lib.types.bool;
149       default = false;
150       description = "Run Stargazer in debug mode.";
151     };
153     routes = lib.mkOption {
154       type = lib.types.listOf
155         (lib.types.submodule {
156           freeformType = with lib.types; attrsOf (nullOr
157             (oneOf [
158               bool
159               int
160               float
161               str
162             ]) // {
163             description = "INI atom (null, bool, int, float or string)";
164           });
165           options.route = lib.mkOption {
166             type = lib.types.str;
167             description = "Route section name";
168           };
169         });
170       default = [ ];
171       description = ''
172         Routes that Stargazer should server.
174         Expressed as a list of attribute sets. Each set must have a key `route`
175         that becomes the section name for that route in the stargazer ini cofig.
176         The remaining keys and values become the parameters for that route.
178         [Refer to upstream docs for other params](https://git.sr.ht/~zethra/stargazer/tree/main/item/doc/stargazer.ini.5.txt)
179       '';
180       example = lib.literalExpression ''
181         [
182           {
183             route = "example.com";
184             root = "/srv/gemini/example.com"
185           }
186           {
187             route = "example.com:/man";
188             root = "/cgi-bin";
189             cgi = true;
190           }
191           {
192             route = "other.org~(.*)";
193             redirect = "gemini://example.com";
194             rewrite = "\1";
195           }
196         ]
197       '';
198     };
200     user = lib.mkOption {
201       type = lib.types.str;
202       default = "stargazer";
203       description = "User account under which stargazer runs.";
204     };
206     group = lib.mkOption {
207       type = lib.types.str;
208       default = "stargazer";
209       description = "Group account under which stargazer runs.";
210     };
211   };
213   config = lib.mkIf cfg.enable {
214     systemd.services.stargazer = {
215       description = "Stargazer gemini server";
216       after = [ "network.target" ];
217       wantedBy = [ "multi-user.target" ];
218       serviceConfig = {
219         ExecStart = "${pkgs.stargazer}/bin/stargazer ${configFile} ${lib.optionalString cfg.debugMode "-D"}";
220         Restart = "always";
221         # User and group
222         User = cfg.user;
223         Group = cfg.group;
224         AmbientCapabilities = lib.mkIf cfg.allowCgiUser [
225           "CAP_SETGID"
226           "CAP_SETUID"
227         ];
229         # Hardening
230         UMask = "0077";
231         PrivateTmp = true;
232         ProtectHome = true;
233         ProtectSystem = "full";
234         ProtectClock = true;
235         ProtectHostname = true;
236         ProtectControlGroups = true;
237         ProtectKernelLogs = true;
238         ProtectKernelModules = true;
239         ProtectKernelTunables = true;
240         ProtectProc = "invisible";
241         PrivateDevices = true;
242         NoNewPrivileges = true;
243         RestrictSUIDSGID = true;
244         PrivateMounts = true;
245         MemoryDenyWriteExecute = true;
246         LockPersonality = true;
247         RestrictRealtime = true;
248         RemoveIPC = true;
249         CapabilityBoundingSet = [
250           "~CAP_SYS_PTRACE"
251           "~CAP_SYS_ADMIN"
252           "~CAP_SETPCAP"
253           "~CAP_SYS_TIME"
254           "~CAP_SYS_PACCT"
255           "~CAP_SYS_TTY_CONFIG "
256           "~CAP_SYS_CHROOT"
257           "~CAP_SYS_BOOT"
258           "~CAP_NET_ADMIN"
259         ] ++ lib.lists.optional (!cfg.allowCgiUser) [
260           "~CAP_SETGID"
261           "~CAP_SETUID"
262         ];
263         SystemCallArchitectures = "native";
264         SystemCallFilter = [ "~@cpu-emulation @debug @keyring @mount @obsolete" ]
265           ++ lib.lists.optional (!cfg.allowCgiUser) [ "@privileged @setuid" ];
266       };
267     };
269     # Create default cert store
270     systemd.tmpfiles.rules = lib.mkIf (cfg.store == /var/lib/gemini/certs) [
271       ''d /var/lib/gemini/certs - "${cfg.user}" "${cfg.group}" -''
272     ];
274     users.users = lib.optionalAttrs (cfg.user == "stargazer") {
275       stargazer = {
276         group = cfg.group;
277         isSystemUser = true;
278       };
279     };
281     users.groups = lib.optionalAttrs (cfg.group == "stargazer") {
282       stargazer = { };
283     };
284   };
286   meta.maintainers = with lib.maintainers; [ gaykitty ];