From df70d616648e33a3e44c8dc8cd2907dd17e9fbc7 Mon Sep 17 00:00:00 2001 From: lew Date: Tue, 7 Apr 2026 22:54:16 +0100 Subject: [PATCH 1/7] feat(site): add preview option schema --- modules/site.nix | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/modules/site.nix b/modules/site.nix index 1e73bcd..456e65e 100644 --- a/modules/site.nix +++ b/modules/site.nix @@ -85,6 +85,27 @@ let default = [ "forgejo.service" ]; description = "Systemd units to wait for before building."; }; + + preview = { + enable = lib.mkEnableOption "TinyAuth-protected preview of this site"; + + branch = mkOption { + type = types.str; + default = "preview"; + }; + + domain = mkOption { + type = types.str; + default = "0${name}.ily.rs"; + description = "Preview domain. Defaults to 0.ily.rs."; + }; + + port = mkOption { + type = types.nullOr types.port; + default = null; + description = "Port for preview Node.js server. Required when parent static = false."; + }; + }; }; }); From 6fe4bced45be6ce735e172432f68817a9abece28 Mon Sep 17 00:00:00 2001 From: lew Date: Tue, 7 Apr 2026 23:05:56 +0100 Subject: [PATCH 2/7] feat(site): generate Caddy virtualHosts for preview sites --- modules/site.nix | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/modules/site.nix b/modules/site.nix index 456e65e..957dcc3 100644 --- a/modules/site.nix +++ b/modules/site.nix @@ -110,6 +110,7 @@ let }); cfg = lib.filterAttrs (_: site: site.enable) config.services.site; + previewCfg = lib.filterAttrs (_: site: site.enable && site.preview.enable) config.services.site; webhookPort = 4323; @@ -134,7 +135,7 @@ in }; config = { - services.caddy.virtualHosts = mkMerge (mapAttrsToList (name: site: + services.caddy.virtualHosts = mkMerge ((mapAttrsToList (name: site: { ${site.domain}.extraConfig = if site.static then '' root * ${site.dataDir}/repo/${site.buildOutputDir} @@ -151,7 +152,35 @@ in redir https://${site.domain}{uri} permanent ''; }) site.redirectDomains) - ) cfg); + ) cfg) ++ (mapAttrsToList (name: site: + let previewDataDir = "/srv/${name}-preview"; in { + ${site.preview.domain}.extraConfig = if site.static then '' + @health path /health-ping + handle @health { + root * ${previewDataDir}/repo/${site.buildOutputDir} + try_files {path} /index.html + file_server + } + handle { + import tinyauth + root * ${previewDataDir}/repo/${site.buildOutputDir} + encode zstd gzip + try_files {path} /index.html + file_server + } + '' else '' + @health path /health-ping + handle @health { + reverse_proxy localhost:${toString site.preview.port} + } + handle { + import tinyauth + reverse_proxy localhost:${toString site.preview.port} + encode zstd gzip + } + ''; + } + ) previewCfg)); systemd.services = mkMerge ((mapAttrsToList (name: site: let h = siteHelpers name site; in { From 1bfd0b9f42f5f746c0dfaf56180b8d3c61fb37d6 Mon Sep 17 00:00:00 2001 From: lew Date: Tue, 7 Apr 2026 23:06:24 +0100 Subject: [PATCH 3/7] feat(site): generate systemd services for preview sites --- modules/site.nix | 68 +++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 65 insertions(+), 3 deletions(-) diff --git a/modules/site.nix b/modules/site.nix index 957dcc3..63120f4 100644 --- a/modules/site.nix +++ b/modules/site.nix @@ -234,7 +234,63 @@ in }; }; } - ) cfg) ++ [{ + ) cfg) ++ (mapAttrsToList (name: site: + let + h = siteHelpers name site; + previewDataDir = "/srv/${name}-preview"; + previewUser = "${name}-preview"; + in { + "${name}-preview-rebuild" = { + description = "Clone/pull and build preview of ${site.domain}"; + after = [ "network-online.target" ] ++ site.afterServices; + path = [ pkgs.nodejs pkgs.bash ] + ++ optional (site.packageManager == "pnpm") pkgs.pnpm; + environment = site.buildEnvironment; + wants = [ "network-online.target" ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = false; + ExecStartPre = "+${pkgs.writeShellScript "prepare-${name}-preview" '' + mkdir -p ${previewDataDir} + chown -R ${previewUser}:${previewUser} ${previewDataDir} + ''}"; + ExecStart = pkgs.writeShellScript "rebuild-${name}-preview" '' + set -euo pipefail + if [ ! -d ${previewDataDir}/repo/.git ]; then + ${pkgs.git}/bin/git clone ${site.repo} ${previewDataDir}/repo + fi + cd ${previewDataDir}/repo + ${pkgs.git}/bin/git fetch origin + ${pkgs.git}/bin/git reset --hard origin/${site.preview.branch} + ${h.installCmd} + ${h.pmBin} run build + ''; + ExecStartPost = lib.mkIf (!site.static) + "+/run/current-system/sw/bin/systemctl restart ${previewUser}"; + User = previewUser; + Group = previewUser; + }; + }; + } // lib.optionalAttrs (!site.static) { + ${previewUser} = { + description = "Preview of ${site.domain}"; + environment = { + HOST = "127.0.0.1"; + PORT = toString site.preview.port; + } // site.environment; + serviceConfig = { + Type = "simple"; + WorkingDirectory = "${previewDataDir}/repo"; + ExecStart = "${pkgs.nodejs}/bin/node ${site.entryPoint}"; + Restart = "on-failure"; + User = previewUser; + Group = previewUser; + ReadWritePaths = site.readWritePaths; + }; + }; + } + ) previewCfg) ++ [{ site-webhook = mkIf (cfg != {}) { description = "Webhook listener for site rebuilds"; after = [ "network.target" ]; @@ -242,13 +298,19 @@ in serviceConfig = { Type = "simple"; ExecStart = let - allHooks = mapAttrsToList (name: site: { + allHooks = (mapAttrsToList (name: site: { id = "${name}-rebuild"; execute-command = "/run/current-system/sw/bin/touch"; pass-arguments-to-command = [ { source = "string"; name = "/run/site-rebuild/${name}"; } ]; - }) cfg; + }) cfg) ++ (mapAttrsToList (name: site: { + id = "${name}-preview-rebuild"; + execute-command = "/run/current-system/sw/bin/touch"; + pass-arguments-to-command = [ + { source = "string"; name = "/run/site-rebuild/${name}-preview"; } + ]; + }) previewCfg); hooksFile = pkgs.writeText "site-hooks.json" (builtins.toJSON allHooks); in "${pkgs.webhook}/bin/webhook -hooks ${hooksFile} -port ${toString webhookPort} -verbose"; Restart = "always"; From 3cc870fe8f6af904252fe4bae5014089922e8387 Mon Sep 17 00:00:00 2001 From: lew Date: Tue, 7 Apr 2026 23:10:00 +0100 Subject: [PATCH 4/7] feat(site): add preview path triggers, users, and groups --- modules/site.nix | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/modules/site.nix b/modules/site.nix index 63120f4..f704ebc 100644 --- a/modules/site.nix +++ b/modules/site.nix @@ -320,7 +320,7 @@ in }; }]); - systemd.paths = mkMerge (mapAttrsToList (name: site: { + systemd.paths = mkMerge ((mapAttrsToList (name: site: { "${name}-rebuild-trigger" = { description = "Watch for ${name} rebuild trigger"; wantedBy = [ "multi-user.target" ]; @@ -329,18 +329,35 @@ in Unit = "${name}-rebuild.service"; }; }; - }) cfg); + }) cfg) ++ (mapAttrsToList (name: site: { + "${name}-preview-rebuild-trigger" = { + description = "Watch for ${name}-preview rebuild trigger"; + wantedBy = [ "multi-user.target" ]; + pathConfig = { + PathModified = "/run/site-rebuild/${name}-preview"; + Unit = "${name}-preview-rebuild.service"; + }; + }; + }) previewCfg)); - users.users = mkMerge (mapAttrsToList (name: site: { + users.users = mkMerge ((mapAttrsToList (name: site: { ${name} = { isSystemUser = true; group = name; home = site.dataDir; }; - }) cfg); + }) cfg) ++ (mapAttrsToList (name: site: { + "${name}-preview" = { + isSystemUser = true; + group = "${name}-preview"; + home = "/srv/${name}-preview"; + }; + }) previewCfg)); - users.groups = mkMerge (mapAttrsToList (name: _: { + users.groups = mkMerge ((mapAttrsToList (name: _: { ${name} = {}; - }) cfg); + }) cfg) ++ (mapAttrsToList (name: _: { + "${name}-preview" = {}; + }) previewCfg)); }; } From 160bb60ee63f26ecc904aa9225b3cbeeec7280f3 Mon Sep 17 00:00:00 2001 From: lew Date: Tue, 7 Apr 2026 23:16:48 +0100 Subject: [PATCH 5/7] fix: preview port assertion + simpler health-ping --- hosts/lab/dokuwiki.nix | 2 +- modules/site.nix | 11 +++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/hosts/lab/dokuwiki.nix b/hosts/lab/dokuwiki.nix index db94e8e..3c7ee21 100644 --- a/hosts/lab/dokuwiki.nix +++ b/hosts/lab/dokuwiki.nix @@ -4,7 +4,7 @@ extraConfig = '' @health path /health-ping handle @health { - reverse_proxy localhost:8070 + respond 200 } handle { diff --git a/modules/site.nix b/modules/site.nix index f704ebc..c983d9a 100644 --- a/modules/site.nix +++ b/modules/site.nix @@ -135,6 +135,11 @@ in }; config = { + assertions = mapAttrsToList (name: site: { + assertion = site.static || site.preview.port != null; + message = "services.site.${name}.preview.port is required when static = false and preview is enabled"; + }) previewCfg; + services.caddy.virtualHosts = mkMerge ((mapAttrsToList (name: site: { ${site.domain}.extraConfig = if site.static then '' @@ -157,9 +162,7 @@ in ${site.preview.domain}.extraConfig = if site.static then '' @health path /health-ping handle @health { - root * ${previewDataDir}/repo/${site.buildOutputDir} - try_files {path} /index.html - file_server + respond 200 } handle { import tinyauth @@ -171,7 +174,7 @@ in '' else '' @health path /health-ping handle @health { - reverse_proxy localhost:${toString site.preview.port} + respond 200 } handle { import tinyauth From c809de6e8023f5d3f5bed24ca2962a7e0f3ae9e5 Mon Sep 17 00:00:00 2001 From: lew Date: Tue, 7 Apr 2026 23:19:29 +0100 Subject: [PATCH 6/7] feat: enable penfield preview site + document preview options --- hosts/lab/sites.nix | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/hosts/lab/sites.nix b/hosts/lab/sites.nix index 3955272..75274d4 100644 --- a/hosts/lab/sites.nix +++ b/hosts/lab/sites.nix @@ -15,11 +15,16 @@ # dataDir — base directory for repo and data (default: /srv/) # readWritePaths — paths the server can write to at runtime (default: []) # afterServices — systemd units to wait for before building (default: ["forgejo.service"]) +# preview.enable — TinyAuth-protected preview of this site (default: false) +# preview.branch — branch for preview (default: "preview") +# preview.domain — preview domain (default: 0.ily.rs) +# preview.port — preview server port (required when static = false) # # remarks: # # - a listener is active on http://localhost:4323/hooks/${name}-rebuild for CD # Forgejo repo -> settings -> Webhooks -> Add webhook +# - preview webhook: http://localhost:4323/hooks/${name}-preview-rebuild { ... }: let @@ -48,6 +53,7 @@ in redirectDomains = [ "penfield.wynne.rs" ]; repo = "https://git.ily.rs/lew/penfield"; static = true; + preview.enable = true; }; services.site.record-generator = { From c45ed013ed72c377ed76e615302efbb4239e480e Mon Sep 17 00:00:00 2001 From: lew Date: Tue, 7 Apr 2026 23:19:58 +0100 Subject: [PATCH 7/7] feat: default preview branch to develop --- hosts/lab/sites.nix | 2 +- modules/site.nix | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/hosts/lab/sites.nix b/hosts/lab/sites.nix index 75274d4..89f1605 100644 --- a/hosts/lab/sites.nix +++ b/hosts/lab/sites.nix @@ -16,7 +16,7 @@ # readWritePaths — paths the server can write to at runtime (default: []) # afterServices — systemd units to wait for before building (default: ["forgejo.service"]) # preview.enable — TinyAuth-protected preview of this site (default: false) -# preview.branch — branch for preview (default: "preview") +# preview.branch — branch for preview (default: "develop") # preview.domain — preview domain (default: 0.ily.rs) # preview.port — preview server port (required when static = false) # diff --git a/modules/site.nix b/modules/site.nix index c983d9a..da00a3d 100644 --- a/modules/site.nix +++ b/modules/site.nix @@ -91,7 +91,7 @@ let branch = mkOption { type = types.str; - default = "preview"; + default = "develop"; }; domain = mkOption {