From 563680739a0d72824ead8a2edd466565755841ca Mon Sep 17 00:00:00 2001 From: Adambomb210 Date: Tue, 20 Oct 2026 18:23:58 -0400 Subject: [PATCH 1/4] feat: redirect forms.patchworklabs.org/ganymede to the Tally form Adds nixosModules.forms-alastor: a Traefik file-provider fragment on alastor that sends forms.patchworklabs.org/ to an external form with a 302. The first entry is /ganymede, the Project Ganymede application on Tally. Every other path gets Traefik's 404. Adding a form is one line in the `forms` map. Co-Authored-By: Claude Opus 5.5 --- flake.nix | 3 +++ hosts/alastor/forms.nix | 59 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 hosts/alastor/forms.nix diff --git a/flake.nix b/flake.nix index a2337a1..f27c4ff 100644 --- a/flake.nix +++ b/flake.nix @@ -73,6 +73,9 @@ # Quilt on alastor: instance settings, Traefik route and agenix # secrets. Needs alastor's Tailscale to reach seam. quilt-alastor = ./hosts/alastor/quilt.nix; + # forms.patchworklabs.org/ redirects to external forms, on + # alastor's Traefik. + forms-alastor = ./hosts/alastor/forms.nix; }; nixosConfigurations = nixpkgs.lib.genAttrs hosts mkHost; diff --git a/hosts/alastor/forms.nix b/hosts/alastor/forms.nix new file mode 100644 index 0000000..4409640 --- /dev/null +++ b/hosts/alastor/forms.nix @@ -0,0 +1,59 @@ +# Short links to external forms on forms.patchworklabs.org. Exported as +# nixosModules.forms-alastor. +# +# Each entry in `forms` sends https://forms.patchworklabs.org/ (with or +# without a trailing slash) to the form's URL with a temporary (302) redirect, +# so a form can move without browsers holding on to the old one. Any other +# path, the bare domain included, matches no router and gets Traefik's 404. +# To add a form, add a line to `forms`. +# +# What the importing host must provide: +# - Traefik with the file provider on /etc/traefik/conf.d and a `cloudflare` +# cert resolver and `hsts` middleware (infra's modules/traefik) +# +# DNS: `forms` in patchworklabsorg/dns, a proxied A record to alastor. +# +# To change any of this: merge here, then in infra run +# nix flake update patchwork-infra +# and deploy alastor. +{ lib, pkgs, ... }: +let + host = "forms.patchworklabs.org"; + + forms = { + # Project Ganymede application (Tally). + ganymede = "https://tally.so/r/Np2DyQ"; + }; + + # Router and middleware name for one form. + name = path: "forms-${path}"; +in +{ + environment.etc."traefik/conf.d/forms.toml".source = (pkgs.formats.toml { }).generate "forms.toml" { + http = { + routers = lib.mapAttrs' ( + path: _: + lib.nameValuePair (name path) { + rule = "Host(`${host}`) && (Path(`/${path}`) || Path(`/${path}/`))"; + entryPoints = [ "websecure" ]; + tls.certResolver = "cloudflare"; + middlewares = [ + "hsts" + (name path) + ]; + # Nothing behind it: the redirect middleware answers every request. + service = "noop@internal"; + } + ) forms; + middlewares = lib.mapAttrs' ( + path: url: + lib.nameValuePair (name path) { + redirectRegex = { + regex = ".*"; + replacement = url; + }; + } + ) forms; + }; + }; +} From cc1f69ee44c01465706631fb4dc73c01eba13e6c Mon Sep 17 00:00:00 2001 From: Jasper Mayone Date: Tue, 20 Oct 2026 18:35:10 -0400 Subject: [PATCH 2/4] refactor: make warp the redirect server, route through edge-alastor --- flake.nix | 3 -- hosts/alastor/edge.nix | 5 +++ hosts/alastor/forms.nix | 59 ---------------------------------- hosts/warp/configuration.nix | 1 + hosts/warp/redirects-nginx.nix | 25 ++++++++++++++ hosts/warp/redirects.nix | 16 +++++++++ 6 files changed, 47 insertions(+), 62 deletions(-) delete mode 100644 hosts/alastor/forms.nix create mode 100644 hosts/warp/redirects-nginx.nix create mode 100644 hosts/warp/redirects.nix diff --git a/flake.nix b/flake.nix index f27c4ff..a2337a1 100644 --- a/flake.nix +++ b/flake.nix @@ -73,9 +73,6 @@ # Quilt on alastor: instance settings, Traefik route and agenix # secrets. Needs alastor's Tailscale to reach seam. quilt-alastor = ./hosts/alastor/quilt.nix; - # forms.patchworklabs.org/ redirects to external forms, on - # alastor's Traefik. - forms-alastor = ./hosts/alastor/forms.nix; }; nixosConfigurations = nixpkgs.lib.genAttrs hosts mkHost; diff --git a/hosts/alastor/edge.nix b/hosts/alastor/edge.nix index 690ce34..35a070b 100644 --- a/hosts/alastor/edge.nix +++ b/hosts/alastor/edge.nix @@ -22,6 +22,11 @@ let backend = "warp"; hosts = [ "weave-staging.patchworklabs.org" ]; }; + # Every host in ../warp/redirects.nix. + redirects = { + backend = "warp"; + hosts = builtins.attrNames (import ../warp/redirects.nix); + }; }; in { diff --git a/hosts/alastor/forms.nix b/hosts/alastor/forms.nix deleted file mode 100644 index 4409640..0000000 --- a/hosts/alastor/forms.nix +++ /dev/null @@ -1,59 +0,0 @@ -# Short links to external forms on forms.patchworklabs.org. Exported as -# nixosModules.forms-alastor. -# -# Each entry in `forms` sends https://forms.patchworklabs.org/ (with or -# without a trailing slash) to the form's URL with a temporary (302) redirect, -# so a form can move without browsers holding on to the old one. Any other -# path, the bare domain included, matches no router and gets Traefik's 404. -# To add a form, add a line to `forms`. -# -# What the importing host must provide: -# - Traefik with the file provider on /etc/traefik/conf.d and a `cloudflare` -# cert resolver and `hsts` middleware (infra's modules/traefik) -# -# DNS: `forms` in patchworklabsorg/dns, a proxied A record to alastor. -# -# To change any of this: merge here, then in infra run -# nix flake update patchwork-infra -# and deploy alastor. -{ lib, pkgs, ... }: -let - host = "forms.patchworklabs.org"; - - forms = { - # Project Ganymede application (Tally). - ganymede = "https://tally.so/r/Np2DyQ"; - }; - - # Router and middleware name for one form. - name = path: "forms-${path}"; -in -{ - environment.etc."traefik/conf.d/forms.toml".source = (pkgs.formats.toml { }).generate "forms.toml" { - http = { - routers = lib.mapAttrs' ( - path: _: - lib.nameValuePair (name path) { - rule = "Host(`${host}`) && (Path(`/${path}`) || Path(`/${path}/`))"; - entryPoints = [ "websecure" ]; - tls.certResolver = "cloudflare"; - middlewares = [ - "hsts" - (name path) - ]; - # Nothing behind it: the redirect middleware answers every request. - service = "noop@internal"; - } - ) forms; - middlewares = lib.mapAttrs' ( - path: url: - lib.nameValuePair (name path) { - redirectRegex = { - regex = ".*"; - replacement = url; - }; - } - ) forms; - }; - }; -} diff --git a/hosts/warp/configuration.nix b/hosts/warp/configuration.nix index 96d712b..62cd188 100644 --- a/hosts/warp/configuration.nix +++ b/hosts/warp/configuration.nix @@ -14,6 +14,7 @@ # run at the same time, and warp is not yet a recipient of the Weave # secrets. See the header of ./weave.nix. # ./weave.nix + ./redirects-nginx.nix ]; networking.hostName = "warp"; diff --git a/hosts/warp/redirects-nginx.nix b/hosts/warp/redirects-nginx.nix new file mode 100644 index 0000000..2f76d79 --- /dev/null +++ b/hosts/warp/redirects-nginx.nix @@ -0,0 +1,25 @@ +# nginx virtual hosts for ./redirects.nix. alastor terminates TLS and adds +# HSTS (nixosModules.edge-alastor), then sends plain HTTP over the tailnet. +{ lib, ... }: +{ + services.nginx.virtualHosts = lib.mapAttrs' ( + host: paths: + lib.nameValuePair "redirect-${host}" { + serverName = host; + listen = [ + { + addr = "0.0.0.0"; + port = 80; + } + ]; + locations = + lib.concatMapAttrs (path: url: { + "= ${path}".return = "302 ${url}"; + "= ${path}/".return = "302 ${url}"; + }) paths + // { + "/".return = "404"; + }; + } + ) (import ./redirects.nix); +} diff --git a/hosts/warp/redirects.nix b/hosts/warp/redirects.nix new file mode 100644 index 0000000..ba94f06 --- /dev/null +++ b/hosts/warp/redirects.nix @@ -0,0 +1,16 @@ +# Redirects served from warp. Plain data, read by ./redirects-nginx.nix (nginx +# on warp) and by ../alastor/edge.nix (one alastor route per host to warp). +# +# Each host maps exact paths to target URLs. A path matches with or without a +# trailing slash and gets a temporary (302) redirect, so a target can move +# without browsers holding on to the old one. Any other path on the host, +# the bare domain included, gets a 404. +# +# To add a redirect: add a line here. For a new host, also add a proxied A +# record to alastor in patchworklabsorg/dns. +{ + "forms.patchworklabs.org" = { + # Project Ganymede application (Tally). + "/ganymede" = "https://tally.so/r/Np2DyQ"; + }; +} From eceaf46c92fb348a4b69bbea0e4be43866347497 Mon Sep 17 00:00:00 2001 From: Jasper Mayone Date: Wed, 21 Oct 2026 10:53:09 -0400 Subject: [PATCH 3/4] fix: route redirects from alastor without edge-alastor --- flake.nix | 3 +++ hosts/alastor/edge.nix | 5 ----- hosts/alastor/redirects.nix | 31 +++++++++++++++++++++++++++++++ 3 files changed, 34 insertions(+), 5 deletions(-) create mode 100644 hosts/alastor/redirects.nix diff --git a/flake.nix b/flake.nix index a2337a1..11a4815 100644 --- a/flake.nix +++ b/flake.nix @@ -73,6 +73,9 @@ # Quilt on alastor: instance settings, Traefik route and agenix # secrets. Needs alastor's Tailscale to reach seam. quilt-alastor = ./hosts/alastor/quilt.nix; + # alastor's Traefik route to the redirect server on warp + # (hosts/warp/redirects.nix). Independent of edge-alastor. + redirects-alastor = ./hosts/alastor/redirects.nix; }; nixosConfigurations = nixpkgs.lib.genAttrs hosts mkHost; diff --git a/hosts/alastor/edge.nix b/hosts/alastor/edge.nix index 35a070b..690ce34 100644 --- a/hosts/alastor/edge.nix +++ b/hosts/alastor/edge.nix @@ -22,11 +22,6 @@ let backend = "warp"; hosts = [ "weave-staging.patchworklabs.org" ]; }; - # Every host in ../warp/redirects.nix. - redirects = { - backend = "warp"; - hosts = builtins.attrNames (import ../warp/redirects.nix); - }; }; in { diff --git a/hosts/alastor/redirects.nix b/hosts/alastor/redirects.nix new file mode 100644 index 0000000..5135974 --- /dev/null +++ b/hosts/alastor/redirects.nix @@ -0,0 +1,31 @@ +# alastor's route to the redirect server on warp. Exported as +# nixosModules.redirects-alastor. Separate from edge-alastor, so the redirects +# can go live before the Weave cutover: importing edge-alastor also moves +# Weave's hosts to warp. +# +# alastor terminates TLS, adds HSTS and proxies plain HTTP over the tailnet to +# nginx on warp (../warp/redirects-nginx.nix). The hosts come from +# ../warp/redirects.nix, so a new path on an existing host needs no change +# here. A new host needs `nix flake update patchwork-infra` and an alastor +# deploy in jaspermayone/infra. +{ lib, pkgs, ... }: +let + tailnet = "wildebeest-stargazer.ts.net"; + hosts = builtins.attrNames (import ../warp/redirects.nix); +in +{ + environment.etc."traefik/conf.d/redirects.toml".source = + (pkgs.formats.toml { }).generate "redirects.toml" + { + http = { + routers.redirects = { + rule = lib.concatMapStringsSep " || " (h: "Host(`${h}`)") hosts; + entryPoints = [ "websecure" ]; + tls.certResolver = "cloudflare"; + middlewares = [ "hsts" ]; + service = "redirects"; + }; + services.redirects.loadBalancer.servers = [ { url = "http://warp.${tailnet}"; } ]; + }; + }; +} From 38938feedb7c4dea2bc92096a761279beaa69b7d Mon Sep 17 00:00:00 2001 From: Jasper Mayone Date: Wed, 21 Oct 2026 13:41:14 -0400 Subject: [PATCH 4/4] refactor: route the redirects through edge-alastor --- flake.nix | 3 --- hosts/alastor/edge.nix | 29 ++++++++++++++--------------- hosts/alastor/redirects.nix | 31 ------------------------------- 3 files changed, 14 insertions(+), 49 deletions(-) delete mode 100644 hosts/alastor/redirects.nix diff --git a/flake.nix b/flake.nix index 11a4815..a2337a1 100644 --- a/flake.nix +++ b/flake.nix @@ -73,9 +73,6 @@ # Quilt on alastor: instance settings, Traefik route and agenix # secrets. Needs alastor's Tailscale to reach seam. quilt-alastor = ./hosts/alastor/quilt.nix; - # alastor's Traefik route to the redirect server on warp - # (hosts/warp/redirects.nix). Independent of edge-alastor. - redirects-alastor = ./hosts/alastor/redirects.nix; }; nixosConfigurations = nixpkgs.lib.genAttrs hosts mkHost; diff --git a/hosts/alastor/edge.nix b/hosts/alastor/edge.nix index 690ce34..957391d 100644 --- a/hosts/alastor/edge.nix +++ b/hosts/alastor/edge.nix @@ -1,26 +1,25 @@ -# alastor as the public edge for Patchwork apps on lab hosts. Exported as -# nixosModules.edge-alastor. alastor terminates TLS and proxies plain HTTP -# over the tailnet to nginx on the app host. Same pattern as saker/Sentry in +# alastor as the public edge for Patchwork apps on the lab app VMs. Exported +# as nixosModules.edge-alastor. alastor terminates TLS and proxies plain HTTP +# over the tailnet to nginx on the app's VM. Same pattern as saker/Sentry in # jaspermayone/infra. # -# Import this INSTEAD of nixosModules.weave-alastor once Weave runs on warp. -# The router names stay `idp` and `weave-staging`. +# Weave stays on alastor (nixosModules.weave-alastor), so it has no route +# here. Import this module next to weave-alastor. One VM per app: add a route +# when the app runs on its VM, and remove the app's own alastor module in the +# same change in jaspermayone/infra. { lib, pkgs, ... }: let tailnet = "wildebeest-stargazer.ts.net"; + # = { backend = ""; hosts = [ "" ... ]; }; + # Quilt moves to batting and Krater runs on weft. Their routes come with + # those changes. routes = { - idp = { + # Every host in ../warp/redirects.nix. A new path on an existing host + # needs no change here. + redirects = { backend = "warp"; - hosts = [ - "idp.patchworklabs.org" - "weave.patchworklabs.org" - "slack.patchworklabs.org" - ]; - }; - weave-staging = { - backend = "warp"; - hosts = [ "weave-staging.patchworklabs.org" ]; + hosts = builtins.attrNames (import ../warp/redirects.nix); }; }; in diff --git a/hosts/alastor/redirects.nix b/hosts/alastor/redirects.nix deleted file mode 100644 index 5135974..0000000 --- a/hosts/alastor/redirects.nix +++ /dev/null @@ -1,31 +0,0 @@ -# alastor's route to the redirect server on warp. Exported as -# nixosModules.redirects-alastor. Separate from edge-alastor, so the redirects -# can go live before the Weave cutover: importing edge-alastor also moves -# Weave's hosts to warp. -# -# alastor terminates TLS, adds HSTS and proxies plain HTTP over the tailnet to -# nginx on warp (../warp/redirects-nginx.nix). The hosts come from -# ../warp/redirects.nix, so a new path on an existing host needs no change -# here. A new host needs `nix flake update patchwork-infra` and an alastor -# deploy in jaspermayone/infra. -{ lib, pkgs, ... }: -let - tailnet = "wildebeest-stargazer.ts.net"; - hosts = builtins.attrNames (import ../warp/redirects.nix); -in -{ - environment.etc."traefik/conf.d/redirects.toml".source = - (pkgs.formats.toml { }).generate "redirects.toml" - { - http = { - routers.redirects = { - rule = lib.concatMapStringsSep " || " (h: "Host(`${h}`)") hosts; - entryPoints = [ "websecure" ]; - tls.certResolver = "cloudflare"; - middlewares = [ "hsts" ]; - service = "redirects"; - }; - services.redirects.loadBalancer.servers = [ { url = "http://warp.${tailnet}"; } ]; - }; - }; -}