self: { config, lib, pkgs, ... }: let cfg = config.services.photogallery; pkg = self.packages.${pkgs.system}.default; in { options.services.photogallery = { enable = lib.mkEnableOption "photogallery static photo gallery"; contentDir = lib.mkOption { type = lib.types.str; default = "/var/lib/photogallery/content"; description = "Directory containing images and .toml sidecars."; }; outputDir = lib.mkOption { type = lib.types.str; default = "/var/lib/photogallery/public"; description = "Directory for the generated static site."; }; baseURL = lib.mkOption { type = lib.types.str; example = "https://photos.example.com"; description = "Public base URL, no trailing slash."; }; port = lib.mkOption { type = lib.types.port; default = 8080; description = "Upload API port. Always bound to 127.0.0.1."; }; nginx = { enable = lib.mkEnableOption "nginx virtual host for photogallery"; domain = lib.mkOption { type = lib.types.str; example = "photos.example.com"; description = "Domain for the nginx virtual host."; }; htpasswdFile = lib.mkOption { type = lib.types.path; example = "/etc/nginx/.htpasswd-gallery"; description = "Path to htpasswd file protecting the /upload endpoint."; }; enableACME = lib.mkOption { type = lib.types.bool; default = true; description = "Enable ACME/Let's Encrypt for TLS."; }; }; }; config = lib.mkIf cfg.enable (lib.mkMerge [ # ── Core service ────────────────────────────────────────────────────── { users.users.photogallery = { isSystemUser = true; group = "photogallery"; home = "/var/lib/photogallery"; createHome = true; description = "photogallery service user"; }; users.groups.photogallery = {}; systemd.tmpfiles.rules = [ "d ${cfg.contentDir} 0755 photogallery photogallery -" "d ${cfg.outputDir} 0755 photogallery photogallery -" ]; systemd.services.photogallery = { description = "photogallery static site generator"; after = [ "network.target" ]; wantedBy = [ "multi-user.target" ]; serviceConfig = { User = "photogallery"; Group = "photogallery"; ExecStart = lib.escapeShellArgs [ "${pkg}/bin/photogallery" "--content" cfg.contentDir "--output" cfg.outputDir "--base-url" cfg.baseURL "--port" (toString cfg.port) ]; Restart = "on-failure"; RestartSec = "5s"; # Hardening NoNewPrivileges = true; PrivateTmp = true; PrivateDevices = true; ProtectSystem = "strict"; ProtectHome = true; ReadWritePaths = [ cfg.contentDir cfg.outputDir ]; }; }; } # ── Optional nginx vhost ────────────────────────────────────────────── (lib.mkIf cfg.nginx.enable { services.nginx = { enable = true; virtualHosts.${cfg.nginx.domain} = { enableACME = cfg.nginx.enableACME; forceSSL = cfg.nginx.enableACME; root = cfg.outputDir; locations."/" = { tryFiles = "$uri $uri/ =404"; }; locations."/upload" = { proxyPass = "http://127.0.0.1:${toString cfg.port}"; basicAuth = "gallery"; basicAuthFile = cfg.nginx.htpasswdFile; }; }; }; }) ]); }