Compare commits

...

7 commits

3 changed files with 150 additions and 12 deletions

View file

@ -4,7 +4,7 @@
extraConfig = '' extraConfig = ''
@health path /health-ping @health path /health-ping
handle @health { handle @health {
reverse_proxy localhost:8070 respond 200
} }
handle { handle {

View file

@ -15,11 +15,16 @@
# dataDir — base directory for repo and data (default: /srv/<name>) # dataDir — base directory for repo and data (default: /srv/<name>)
# readWritePaths — paths the server can write to at runtime (default: []) # readWritePaths — paths the server can write to at runtime (default: [])
# afterServices — systemd units to wait for before building (default: ["forgejo.service"]) # 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: "develop")
# preview.domain — preview domain (default: 0<name>.ily.rs)
# preview.port — preview server port (required when static = false)
# #
# remarks: # remarks:
# #
# - a listener is active on http://localhost:4323/hooks/${name}-rebuild for CD # - a listener is active on http://localhost:4323/hooks/${name}-rebuild for CD
# Forgejo repo -> settings -> Webhooks -> Add webhook # Forgejo repo -> settings -> Webhooks -> Add webhook
# - preview webhook: http://localhost:4323/hooks/${name}-preview-rebuild
{ ... }: { ... }:
let let
@ -48,6 +53,7 @@ in
redirectDomains = [ "penfield.wynne.rs" ]; redirectDomains = [ "penfield.wynne.rs" ];
repo = "https://git.ily.rs/lew/penfield"; repo = "https://git.ily.rs/lew/penfield";
static = true; static = true;
preview.enable = true;
}; };
services.site.record-generator = { services.site.record-generator = {

View file

@ -85,10 +85,32 @@ let
default = [ "forgejo.service" ]; default = [ "forgejo.service" ];
description = "Systemd units to wait for before building."; description = "Systemd units to wait for before building.";
}; };
preview = {
enable = lib.mkEnableOption "TinyAuth-protected preview of this site";
branch = mkOption {
type = types.str;
default = "develop";
};
domain = mkOption {
type = types.str;
default = "0${name}.ily.rs";
description = "Preview domain. Defaults to 0<name>.ily.rs.";
};
port = mkOption {
type = types.nullOr types.port;
default = null;
description = "Port for preview Node.js server. Required when parent static = false.";
};
};
}; };
}); });
cfg = lib.filterAttrs (_: site: site.enable) config.services.site; cfg = lib.filterAttrs (_: site: site.enable) config.services.site;
previewCfg = lib.filterAttrs (_: site: site.enable && site.preview.enable) config.services.site;
webhookPort = 4323; webhookPort = 4323;
@ -113,7 +135,12 @@ in
}; };
config = { config = {
services.caddy.virtualHosts = mkMerge (mapAttrsToList (name: site: 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 '' ${site.domain}.extraConfig = if site.static then ''
root * ${site.dataDir}/repo/${site.buildOutputDir} root * ${site.dataDir}/repo/${site.buildOutputDir}
@ -130,7 +157,33 @@ in
redir https://${site.domain}{uri} permanent redir https://${site.domain}{uri} permanent
''; '';
}) site.redirectDomains) }) 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 {
respond 200
}
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 {
respond 200
}
handle {
import tinyauth
reverse_proxy localhost:${toString site.preview.port}
encode zstd gzip
}
'';
}
) previewCfg));
systemd.services = mkMerge ((mapAttrsToList (name: site: systemd.services = mkMerge ((mapAttrsToList (name: site:
let h = siteHelpers name site; in { let h = siteHelpers name site; in {
@ -184,7 +237,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 != {}) { site-webhook = mkIf (cfg != {}) {
description = "Webhook listener for site rebuilds"; description = "Webhook listener for site rebuilds";
after = [ "network.target" ]; after = [ "network.target" ];
@ -192,13 +301,19 @@ in
serviceConfig = { serviceConfig = {
Type = "simple"; Type = "simple";
ExecStart = let ExecStart = let
allHooks = mapAttrsToList (name: site: { allHooks = (mapAttrsToList (name: site: {
id = "${name}-rebuild"; id = "${name}-rebuild";
execute-command = "/run/current-system/sw/bin/touch"; execute-command = "/run/current-system/sw/bin/touch";
pass-arguments-to-command = [ pass-arguments-to-command = [
{ source = "string"; name = "/run/site-rebuild/${name}"; } { 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); hooksFile = pkgs.writeText "site-hooks.json" (builtins.toJSON allHooks);
in "${pkgs.webhook}/bin/webhook -hooks ${hooksFile} -port ${toString webhookPort} -verbose"; in "${pkgs.webhook}/bin/webhook -hooks ${hooksFile} -port ${toString webhookPort} -verbose";
Restart = "always"; Restart = "always";
@ -208,7 +323,7 @@ in
}; };
}]); }]);
systemd.paths = mkMerge (mapAttrsToList (name: site: { systemd.paths = mkMerge ((mapAttrsToList (name: site: {
"${name}-rebuild-trigger" = { "${name}-rebuild-trigger" = {
description = "Watch for ${name} rebuild trigger"; description = "Watch for ${name} rebuild trigger";
wantedBy = [ "multi-user.target" ]; wantedBy = [ "multi-user.target" ];
@ -217,18 +332,35 @@ in
Unit = "${name}-rebuild.service"; 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} = { ${name} = {
isSystemUser = true; isSystemUser = true;
group = name; group = name;
home = site.dataDir; 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} = {}; ${name} = {};
}) cfg); }) cfg) ++ (mapAttrsToList (name: _: {
"${name}-preview" = {};
}) previewCfg));
}; };
} }