diff --git a/flake.nix b/flake.nix index 4e4010e..03795d6 100644 --- a/flake.nix +++ b/flake.nix @@ -48,6 +48,7 @@ ./modules/services/nostr-rs-relay.nix ./modules/services/podman.nix ./modules/services/static-sites + ./modules/services/webhook-deploy.nix ./modules/users/tobias.nix ]; }; diff --git a/modules/services/caddy.nix b/modules/services/caddy.nix index 477936b..e40f5b2 100644 --- a/modules/services/caddy.nix +++ b/modules/services/caddy.nix @@ -30,6 +30,9 @@ randogoth.com, www.randogoth.com { } sublunar.space, www.sublunar.space { + handle /hooks/* { + reverse_proxy localhost:9000 + } root * /var/www/sublunar.space/public encode zstd gzip file_server diff --git a/modules/services/webhook-deploy.nix b/modules/services/webhook-deploy.nix new file mode 100644 index 0000000..c30024b --- /dev/null +++ b/modules/services/webhook-deploy.nix @@ -0,0 +1,87 @@ +{ config, pkgs, lib, ... }: +let + sites = [ + { name = "sublunar"; input = "sublunar"; } + { name = "tobias-raayoni-last"; input = "flux_vision"; } + { name = "nmns"; input = "nfc_web"; } + ]; + + # Template hooks.json with a placeholder secret — real secret injected at + # service start time from the sops-managed file. + hooksTemplate = pkgs.writeText "webhook-hooks-template.json" (builtins.toJSON + (map (site: { + id = "rebuild-${site.name}"; + execute-command = "/run/current-system/sw/bin/systemctl"; + pass-arguments-to-command = [ + { source = "string"; name = "start"; } + { source = "string"; name = "--no-block"; } + { source = "string"; name = "update-${site.name}"; } + ]; + response-message = "Queued rebuild of ${site.name}"; + trigger-rule.match = { + type = "payload-hmac-sha256"; + secret = "placeholder"; + parameter = { source = "header"; name = "X-Gitea-Signature"; }; + }; + }) sites) + ); + + # Writes /run/webhook-deploy/hooks.json with the real secret at startup. + makeHooks = pkgs.writeShellScript "make-webhook-hooks" '' + set -euo pipefail + ${pkgs.jq}/bin/jq \ + --arg s "$(cat ${config.sops.secrets.webhook_secret.path})" \ + 'map(.["trigger-rule"].match.secret = $s)' \ + ${hooksTemplate} > /run/webhook-deploy/hooks.json + ''; + + updateScript = site: pkgs.writeShellScript "do-update-${site.name}" '' + set -euo pipefail + cd /etc/nixos + OLD=$(${pkgs.jq}/bin/jq -r '.nodes.${site.input}.locked.rev' flake.lock) + nix flake update ${site.input} + NEW=$(${pkgs.jq}/bin/jq -r '.nodes.${site.input}.locked.rev' flake.lock) + if [ "$OLD" = "$NEW" ]; then + echo "${site.name}: no change, skipping rebuild" + exit 0 + fi + echo "${site.name}: updated $OLD -> $NEW, rebuilding" + nixos-rebuild switch --flake /etc/nixos#bucur + ''; +in +{ + sops.secrets.webhook_secret = { + owner = "root"; + mode = "0400"; + }; + + # System-wide SSH client config so root can reach Codeberg via the deploy key. + programs.ssh.extraConfig = '' + Host codeberg.org + IdentityFile /etc/ssh/codeberg_id_ed25519 + StrictHostKeyChecking accept-new + ''; + + systemd.services = lib.listToAttrs + (map (site: lib.nameValuePair "update-${site.name}" { + description = "Pull and rebuild ${site.name}"; + serviceConfig = { + Type = "oneshot"; + ExecStart = updateScript site; + }; + }) sites) + // { + webhook-deploy = { + description = "Webhook server for Pelican site rebuilds"; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" "sops-nix.service" ]; + serviceConfig = { + Type = "simple"; + RuntimeDirectory = "webhook-deploy"; + ExecStartPre = "+${makeHooks}"; + ExecStart = "${pkgs.webhook}/bin/webhook -hooks /run/webhook-deploy/hooks.json -port 9000"; + Restart = "on-failure"; + }; + }; + }; +} diff --git a/secrets/bucur.yaml b/secrets/bucur.yaml index e4b85eb..4621a88 100644 --- a/secrets/bucur.yaml +++ b/secrets/bucur.yaml @@ -1,5 +1,6 @@ mtproto_secret: ENC[AES256_GCM,data:MyzyUCH8cLOG+GMtmV03daJ9sqwOw/ozN2yi/14SMlY=,iv:LLf2LIvX+jNjRdMHclHpsmTZa7wmVM3gD6RbqeCGmik=,tag:i36lVESyKJafsn14kSzh3g==,type:str] codeberg_id_ed25519: ENC[AES256_GCM,data:AZbFWfc80HVskenEVLYL9MOYrGhW7Cwzdqv8gS4+1JGfk+nC801SmMgElUdISo+3m/O54b8RbJtdVz2u99gL8WG6wgbK7xqf40q4HNvV5cFXp7L/ozUkT41uXjWsDIdnMwumOqL/vjXowmAay30kMOiD5M4DL0t6EmuOo1Rcdffe/HwCVd2Pz7ZpU+BSTQV/pDTK1X3taUCjCY3Ui0XrcSCY8G2tuX5pFQ6t+vLrdqy3UGhofYHQ+nZuoyEKQHCcvEhYvEgDPJtyy+gk96vZCDYyXb9am/5k0I+MoIsArpavp4P0r5dNDcTnuCHH+fVPTG4tOsG/Ztf1rFBCsqPiM0azNElI8FERSKLYipuDDD8GI46p43OalHw4pxmAHXNOs70PfIH25iFJ5AUb32v2A71LScJMNP/TFSSezuq6g5eTd/JrtpQnWwToH1eVrwcN/8YX3xflLZ2JO1q4fjCXrkYHyw004dV/HAq3u1XQDmfMviZiH80+hiVIgZLfMWv5IyzfuomqSnfOj6oJ6D+j,iv:37HiX60OnO/zZFs1v4VWQLxcaoC1byZ3N2ueYFh4BVQ=,tag:5+z2YlnA8+MEcQTTrYhPJA==,type:str] +webhook_secret: ENC[AES256_GCM,data:iLDVsnRCifIXdZzuTTDRT/XHQoW4O/C2Wc33cxn3QoiZNgVXJef50In8+mKPy6SXV05iJqe/0YdOXF3IsHbelA==,iv:Ve/jQx6xPeS3O5h67cideBpGEhJSCVgtDfIypQSmIMc=,tag:Lcp+buNI23SSXVW9Vsn1RQ==,type:str] sops: age: - recipient: age1647c5f46njylggj33h26t6twhq57n5qeaza04l845uy0fksv0ucsq82c3d @@ -11,7 +12,7 @@ sops: U0x1ZmtzLytSL1Y3cVo1ZVJ2Ymtob1EKTpgjSymQx14wLSuUhh5Txq++3DrLS4Bs x8vVtQgcXZgv1q0OMGup6DT4bkY6UiNbn9bAt4rHCaRPeb15x4ZKNw== -----END AGE ENCRYPTED FILE----- - lastmodified: "2026-02-05T14:18:42Z" - mac: ENC[AES256_GCM,data:sZTFVbWYhtYML6tZrivXYGm8ug+isUf7wi0VWwQ0Wx1Swany1RSkyQI7N0eFs58tVsY2KoizOShvLkC8UcpRvtog1M/OyZi0LypYkfMUD7Rth11AF2n3Tg4Q3VNH5DGcLWkzYleA4mj1mJnPyCQzKOykXSPocODAcKUyFYcP8uQ=,iv:ZxuNWYQesR6mtezi5yBRacPF4GC5NDsevaSlRJ+A6X4=,tag:htOCSco/V2/twoRRWeoBbA==,type:str] + lastmodified: "2026-05-14T10:35:18Z" + mac: ENC[AES256_GCM,data:aTYOA9JxUYYhlqN9Cgocxl50qiSvZ5wCVCgNf15yUNmxXhuaQW8FV1TNXBrJ++k6AQ7+Yerh6cDEu3wjXg38oNdVF60pU4xlMFsQZy337yUXIi0IwStuy7hwshDj6prnROg2a0s77MpSJQLPHF2yQpFv1pHW3aTC3HAiCXcKrL0=,iv:PYOYeHCa4IHE9Kx87ECyMNRtfgmaZ4QUg4rsvjiSgSI=,tag:dGBhf8RW/PRGbA6XziaSAQ==,type:str] unencrypted_suffix: _unencrypted version: 3.11.0