diff --git a/secrets/mcp.yaml b/secrets/mcp.yaml index 3a3548f..df6e7c2 100644 --- a/secrets/mcp.yaml +++ b/secrets/mcp.yaml @@ -14,6 +14,8 @@ offen: traefik: oauth2-client-secret: ENC[AES256_GCM,data:gV9/yBCqWPcNG/m7S0PRE3TduKzqRD1ii3RGGjNprQM=,iv:jmwBYWhPQJMZWHZine6Eb+7fdW44QOvkK52LQ6ISK4s=,tag:yNWRJ1IdPcxn6e0DXQe7Cw==,type:str] oauth2-plugin-secret: ENC[AES256_GCM,data:sArqwKHAdW35o5kD7DGfXSYCXFUXqvKQdoVnXutsNLw=,iv:qWf597QS3BqkVQkeAb99HbpDB0kUhdD+qKdpUPZEB0o=,tag:vXnb93npaklItWkMZ+/M9Q==,type:str] +deploy-key: + terakoda.com: ENC[AES256_GCM,data:STOAUPihw2KfndKm/XV5evihrTy/TQrbtVh7EpEyVE6Z1FsJd3UljcjhTmp/Z3nSpq4LCiezmaxISTnQDIP/NzPfou309SLl2QvD7deFhurMsYbeWJw62RP0ClBfteBaVxeqlH/pksoE1cJaZFxv/KxXYYoxzCUzeXC31GQv6Mft+/FnA1rsVp2n0Ay73hMVjMY0ml2csybLOuuKyxEq3nImhLFvtr4jJhVmxnN2L+bs0a+GohjC98HwITJD2OsrJwSpW4cv2v1GqeJCr2om5SgplwvjkiHJrg/WLO8N6BuVDOy0yx9Vbf1cAwkzPd3gBeKd5po+baJwRFAXpB03KvG/w6Yz/4ewo4X78IhwtLvTl876e/i/7K17ILvc5JJrKe9lmEuNUaItRPWYypEHrkge/PXSAvPIqRnAEi3jfOfVXWygZPerS3hs7bBE/Lem1U7/MUcK+pfXnZDgbWVsRuFhZhxasFGa7cG+gBUZsHWbyXi2e/koFUqUTR0HU0q0zF1xw/8jthPPGoIJ/0tP,iv:99AI3rnNjt9XqXJHnQ3DAEFm90h465ymjNWEpsWvRnM=,tag:96dnIojTXXONozgYDFwcBA==,type:str] sops: age: - recipient: age1yvdzvuvu5wqztcx6ll2xk6x547uuyqy735tjjdd7zftkz53jsf9qf5ahue @@ -34,7 +36,7 @@ sops: by9aNFY4dXNxaWxnTXFTQS9reHhuQWMKh5rZ93nFtBV9EpFVRp+E+GXZ6xzVy2Jw vFh4deGcAb60q4odSaeWfk1Dr7L9Ua69oK9omjbCNUt+P7Kwlfca7Q== -----END AGE ENCRYPTED FILE----- - lastmodified: "2025-05-30T17:07:50Z" - mac: ENC[AES256_GCM,data:R6erzXvuG/viLywni4klvHfJF0xLYw2rNsdN1lugUgqwvH4HuxbBelyr8Co7ePhSoVEI1628futZi+yYAoNnWPbThtKG4G/WUc8Sm9gCVxAqMeeCL1pLbcizlJx5L+Of61m7hUgb3MhcPYkC8HEI4rxkgfEG0FQW6rRy7g8CzNU=,iv:Ou+WXiNUVkcbdDy87+1jPsAzpBvHCPsLfDPrRSWkTKM=,tag:Jqfpvg+vTmhWSvfAuikteA==,type:str] + lastmodified: "2025-06-04T22:56:20Z" + mac: ENC[AES256_GCM,data:EOPjNLAQRvi2FgmYwHST1eZDj1lMT4+Nwi5YS8yJI7w2Y8pkBiKx1JqMzNW7DSmwIf8J7TCmK+7bmJPF+WyLPous8B920zbn9Rt8ttLpSRBOHCReH9k3FwYAtAkYYCMB2oeDkpWjTnU2xeUh/FqOkRInw98sy3EO0HPEtXdPrng=,iv:17nuB8ders0PI92BrWX3mwuxqDafckM9Reu+wiRo5/0=,tag:mirzSqpscIrDp7vZwX0+NQ==,type:str] unencrypted_suffix: _unencrypted version: 3.10.2 diff --git a/system/hosts/mcp/containers.nix b/system/hosts/mcp/containers.nix index 8830df2..cd3d65d 100644 --- a/system/hosts/mcp/containers.nix +++ b/system/hosts/mcp/containers.nix @@ -3,6 +3,7 @@ { # Additional configuration imports = [ + # Docker containers ./containers/dm-companion.nix ./containers/freshrss.nix ./containers/gitea.nix @@ -21,6 +22,9 @@ ./containers/timetagger.nix ./containers/traefik.nix ./containers/users.nix + + # NixOS Containers + ./static-site-hooks.nix ]; # Enable common container config files in /etc/containers diff --git a/system/hosts/mcp/containers/public-homepage.nix b/system/hosts/mcp/containers/public-homepage.nix index e61fc0b..d572863 100644 --- a/system/hosts/mcp/containers/public-homepage.nix +++ b/system/hosts/mcp/containers/public-homepage.nix @@ -1,30 +1,59 @@ # Static websites { lib, config, ... }: let - inherit (import ./lib.nix config) terakoda havenisms blazestar; - mkStaticSite = domain: let - cleanDomain = lib.strings.stringAsChars (c: if c == "." then "-" else c) domain; - in { - "${cleanDomain}-static" = { - image = "nginx:alpine"; - autoStart = true; - volumes = [ - "/tank/web/${domain}/public:/usr/share/nginx/html:ro" - ]; - labels = { - "traefik.enable" = "true"; - "traefik.http.routers.${cleanDomain}.rule" = "Host(`${domain}`) || Host(`www.${domain}`)"; - "traefik.http.routers.${cleanDomain}.middlewares" = "${cleanDomain}-add-www@docker"; - "traefik.http.services.${cleanDomain}.loadbalancer.server.port" = "80"; - "traefik.http.middlewares.${cleanDomain}-add-www.redirectregex.regex" = "^https://${domain}/(.*)"; - "traefik.http.middlewares.${cleanDomain}-add-www.redirectregex.replacement" = "https://www.${domain}/\${1}"; - "traefik.http.middlewares.${cleanDomain}-add-www.redirectregex.permanent" = "true"; + inherit (import ./lib.nix config) + terakoda + havenisms + blazestar + ; + mkStaticSite = + host: + let + cleanHost = lib.strings.stringAsChars (c: if c == "." then "-" else c) host; + in + { + "${cleanHost}-static" = { + image = "nginx:alpine"; + autoStart = true; + volumes = [ + "/tank/web/${host}/public:/usr/share/nginx/html:ro" + ]; + labels = { + "traefik.enable" = "true"; + "traefik.http.routers.${cleanHost}.rule" = "Host(`${host}`) || Host(`www.${host}`)"; + "traefik.http.routers.${cleanHost}.middlewares" = "${cleanHost}-add-www@docker"; + "traefik.http.services.${cleanHost}.loadbalancer.server.port" = "80"; + "traefik.http.middlewares.${cleanHost}-add-www.redirectregex.regex" = "^https://${host}/(.*)"; + "traefik.http.middlewares.${cleanHost}-add-www.redirectregex.replacement" = + "https://www.${host}/\${1}"; + "traefik.http.middlewares.${cleanHost}-add-www.redirectregex.permanent" = "true"; + }; }; }; - }; -in { - virtualisation.oci-containers.containers = - mkStaticSite terakoda // - mkStaticSite havenisms // - mkStaticSite blazestar; +in +{ + virtualisation.oci-containers.containers = + mkStaticSite terakoda + // mkStaticSite havenisms + // mkStaticSite blazestar + // ( + let + host = "www2.terakoda.com"; + cleanHost = lib.strings.stringAsChars (c: if c == "." then "-" else c) host; + in + { + "${cleanHost}-static" = { + image = "nginx:alpine"; + autoStart = true; + volumes = [ + "/tank/web/www2.terakoda.com/dist:/usr/share/nginx/html:ro" + ]; + labels = { + "traefik.enable" = "true"; + "traefik.http.routers.${cleanHost}.rule" = "Host(`${host}`)"; + "traefik.http.services.${cleanHost}.loadbalancer.server.port" = "80"; + }; + }; + } + ); } diff --git a/system/hosts/mcp/static-site-hooks.nix b/system/hosts/mcp/static-site-hooks.nix new file mode 100644 index 0000000..193d9a8 --- /dev/null +++ b/system/hosts/mcp/static-site-hooks.nix @@ -0,0 +1,77 @@ +{ config, pkgs, ... }: +let + gitKnownHosts = pkgs.writeText "known_hosts" '' + [git.blazestar.net]:2222 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDSikNAZDAbdQ5TA6Eg95FBM3sdPfAfghG+n56akCal8XXV/vOnXgqfeDASfXVOu+PZqCHnpGTxsym7hf2naFC0enznhS2sqahdQKKcsHvSfyQxpYFYyB2Zp8YDbnbRNGl2SbnqOajzk1SxJrJ0fFXmfrRIMnGNz+uFtIqc+T52CM051nd5Gj3f9a8xCwg7hedvSCynobsW9IOCmCc9rZ99TRd+m0kO74pUbgVqLv/+aSuW40K1uCkKgyh6PQsmkZd5GY0URwoJvLZauZLSPxl6DEU6lYz8S/hPrTP/e6fOPZsavQBYC+3Q/akoFnY+qlKgWLQy/Om6hz0EfYuuzNPRhf1jaGKjHgEri1f3OMgXcRMvjovRgbbu0JRGANmN8FMe20S4AAvbxmsQdQci+QcXZPDPbcmT3XJv8e8p4HNQyLxHyh0u9dLBE2ccTv5gdf/6iZy6WXlYEf1UAKC2lExRuKBV3lrnuyHhOj+iL09gUMYFuIyHuX2Hsw9yKZbO8J2+STNIVQfAJ0Upa2cJ33a6RlOxGiHXi4UbZTPguNgQaQdM0CuklVTynBfWr1Hfd8c8hVtT+HLz+XOU2Nrmgq90/w7g7mo5JxXHkcfBlqlXKONTkDUG3KHbwKtQNVC6l3bhpvPc32Mys6e7JeWnrb1zXojopnPvoct54qDVlwc5xQ== + ''; + testHook = + with pkgs; + writeShellApplication { + name = "deploy-astro-app"; + runtimeInputs = [ + openssh + gitFull + nodejs_22 + bashNonInteractive + ]; + text = '' + set -e + id + pwd + + export GIT_SSH_COMMAND='ssh -v -o "UserKnownHostsFile ${gitKnownHosts}" -i "${ + config.sops.secrets."deploy-key/terakoda.com".path + }"' + + # Disable astro telemetry otherwise it will try to write to `~/.config/astro/config.json` + export ASTRO_TELEMETRY_DISABLED=1 + + # Fetch the repository and make sure we are reset to HEAD + git fetch origin main + git reset --hard + git checkout main + git reset --hard origin/main + + # Use a local cache with --cache .npm + npm ci --cache .npm + npm run build + ''; + }; +in +{ + # [ ] Make sure the hook can operate on that directory + # [ ] Run the build command + + sops.secrets = { + "deploy-key/terakoda.com" = { + restartUnits = [ "webhook.service" ]; + owner = config.users.users.webhook.name; + }; + }; + + services.webhook = { + enable = true; + verbose = true; + port = 9000; + openFirewall = true; + hooks = { + "deploy-www2-terakoda-com" = { + id = "deploy-www2-terakoda-com"; + http-methods = [ "POST" ]; + command-working-directory = "/tank/web/www2.terakoda.com"; + include-command-output-in-response = true; + include-command-output-in-response-on-error = true; + execute-command = "${testHook}/bin/deploy-astro-app"; + trigger-rule = { + match = { + type = "payload-mac-sha256"; + secret = "test123"; + parameter = { + source = "header"; + name = "X-Hub-Signature-256"; + }; + }; + }; + }; + }; + }; +}