{ config, lib, pkgs, ... }: with lib; let cfg = config.services.stargazer; routesFormat = pkgs.formats.ini { }; globalFile = pkgs.writeText "global.ini" '' listen = ${concatStringsSep " " cfg.listen} connection-logging = ${boolToString cfg.connectionLogging} log-ip = ${boolToString cfg.ipLog} log-ip-partial = ${boolToString cfg.ipLogPartial} request-timeout = ${toString cfg.requestTimeout} response-timeout = ${toString cfg.responseTimeout} [:tls] store = ${toString cfg.store} organization = ${cfg.certOrg} gen-certs = ${boolToString cfg.genCerts} regen-certs = ${boolToString cfg.regenCerts} ${optionalString (cfg.certLifetime != "") "cert-lifetime = ${cfg.certLifetime}"} ''; routesFile = routesFormat.generate "router.ini" cfg.routes; configFile = pkgs.runCommand "config.ini" { } '' cat ${globalFile} ${routesFile} > $out ''; in { options.services.stargazer = { enable = mkEnableOption (lib.mdDoc "Stargazer Gemini server"); listen = lib.mkOption { type = types.listOf types.str; default = [ "0.0.0.0" ] ++ optional config.networking.enableIPv6 "[::0]"; defaultText = literalExpression ''[ "0.0.0.0" ] ++ lib.optional config.networking.enableIPv6 "[::0]"''; example = literalExpression ''[ "10.0.0.12" "[2002:a00:1::]" ]''; description = lib.mdDoc '' Address and port to listen on. ''; }; connectionLogging = lib.mkOption { type = types.bool; default = true; description = lib.mdDoc "Whether or not to log connections to stdout."; }; ipLog = lib.mkOption { type = types.bool; default = false; description = lib.mdDoc "Log client IP addresses in the connection log."; }; ipLogPartial = lib.mkOption { type = types.bool; default = false; description = lib.mdDoc "Log partial client IP addresses in the connection log."; }; requestTimeout = lib.mkOption { type = types.int; default = 5; description = lib.mdDoc '' Number of seconds to wait for the client to send a complete request. Set to 0 to disable. ''; }; responseTimeout = lib.mkOption { type = types.int; default = 0; description = lib.mdDoc '' Number of seconds to wait for the client to send a complete request and for stargazer to finish sending the response. Set to 0 to disable. ''; }; store = lib.mkOption { type = types.path; default = /var/lib/gemini/certs; description = lib.mdDoc '' Path to the certificate store on disk. This should be a persistent directory writable by Stargazer. ''; }; certOrg = lib.mkOption { type = types.str; default = "stargazer"; description = lib.mdDoc '' The name of the organization responsible for the X.509 certificate's /O name. ''; }; genCerts = lib.mkOption { type = types.bool; default = true; description = lib.mdDoc '' Set to false to disable automatic certificate generation. Use if you want to provide your own certs. ''; }; regenCerts = lib.mkOption { type = types.bool; default = true; description = lib.mdDoc '' Set to false to turn off automatic regeneration of expired certificates. Use if you want to provide your own certs. ''; }; certLifetime = lib.mkOption { type = types.str; default = ""; description = lib.mdDoc '' How long certs generated by Stargazer should live for. Certs live forever by default. ''; example = literalExpression "\"1y\""; }; routes = lib.mkOption { type = routesFormat.type; default = { }; description = lib.mdDoc '' Routes that Stargazer should server. [Refer to upstream docs](https://git.sr.ht/~zethra/stargazer/tree/main/item/doc/stargazer.ini.5.txt) ''; example = literalExpression '' { "example.com" = { root = "/srv/gemini/example.com"; }; "example.com:/man" = { root = "/cgi-bin"; cgi = true; }; "other.org~(.*)" = { redirect = "gemini://example.com"; rewrite = "\1"; }; } ''; }; user = mkOption { type = types.str; default = "stargazer"; description = lib.mdDoc "User account under which stargazer runs."; }; group = mkOption { type = types.str; default = "stargazer"; description = lib.mdDoc "Group account under which stargazer runs."; }; }; config = mkIf cfg.enable { systemd.services.stargazer = { description = "Stargazer gemini server"; after = [ "network.target" ]; wantedBy = [ "multi-user.target" ]; serviceConfig = { ExecStart = "${pkgs.stargazer}/bin/stargazer ${configFile}"; Restart = "always"; # User and group User = cfg.user; Group = cfg.group; }; }; # Create default cert store system.activationScripts.makeStargazerCertDir = optionalAttrs (cfg.store == /var/lib/gemini/certs) '' mkdir -p /var/lib/gemini/certs chown -R ${cfg.user}:${cfg.group} /var/lib/gemini/certs ''; users.users = optionalAttrs (cfg.user == "stargazer") { stargazer = { group = cfg.group; isSystemUser = true; }; }; users.groups = optionalAttrs (cfg.group == "stargazer") { stargazer = { }; }; }; meta.maintainers = with lib.maintainers; [ gaykitty ]; }