diff --git a/dev2/edge/edge.json b/dev2/edge/edge.json index dd573c1..5010e44 100644 --- a/dev2/edge/edge.json +++ b/dev2/edge/edge.json @@ -8,7 +8,8 @@ { "dir": "/etc/letsencrypt/live/pit.world", "crt": "fullchain.pem", "key": "privkey.pem", "as": "pit.world" }, { "dir": "/etc/nginx/pit-proxy-certs" }, { "dir": "/etc/ssl/moshpit" }, - { "dir": "/etc/ssl/moshpit-riotcoder" } + { "dir": "/etc/ssl/moshpit-riotcoder" }, + { "dir": "/etc/ssl/pwa-pit-world", "crt": "fullchain.pem", "key": "privkey.pem", "as": "wildcard.pwa.pit.world" } ], "keep_on_nginx": { "//": "Names under a routed pattern that stay on nginx for now, with why.", @@ -28,6 +29,40 @@ "//": "group.call (moshcoder/group.call): no gzip, so the room page's server-sent events are not held back; retry holds Telnyx webhooks across a deploy instead of 502" }, { "hosts": ["*.*.pit.world", "*.*.*.pit.world"], "proxy": 8787, "max_body": "30MiB", "gzip": true, "//": "pit-proxy: every .pit.world (+ bbs./social.) relayed by the pit.world app; one Let's Encrypt certificate per name from pit-proxy-cert" }, + {"hosts": ["advis0r.pwa.pit.world"], "proxy": 3277, "max_body": "64MiB", "gzip": true, "//": ".pwa.pit.world: apps whose ICANN domain is being let go (2026-11-08), served from the same dev2 containers so the domain can expire. One wildcard certificate (*.pwa.pit.world, acme.sh DNS-01) covers them all; -supabase hosts are the self-hosted Supabase gateways the browser code calls; c0upons-bbs serves the board at /bbs."}, + {"hosts": ["agenticjobs.pwa.pit.world"], "proxy": 3214, "max_body": "64MiB", "gzip": true}, + {"hosts": ["aiornot.pwa.pit.world"], "proxy": 3269, "max_body": "64MiB", "gzip": true}, + {"hosts": ["avenasea.pwa.pit.world"], "proxy": 3224, "max_body": "64MiB", "gzip": true}, + {"hosts": ["bg0ne.pwa.pit.world"], "proxy": 3201, "max_body": "64MiB", "gzip": true}, + {"hosts": ["brisk.pwa.pit.world"], "proxy": 3238, "max_body": "64MiB", "gzip": true}, + {"hosts": ["c0ncerts.pwa.pit.world"], "proxy": 3278, "max_body": "64MiB", "gzip": true}, + {"hosts": ["c0upons.pwa.pit.world"], "proxy": 3231, "max_body": "64MiB", "gzip": true}, + {"hosts": ["cigarunderground.pwa.pit.world"], "proxy": 3203, "max_body": "64MiB", "gzip": true}, + {"hosts": ["d1sks.pwa.pit.world"], "proxy": 3207, "max_body": "64MiB", "gzip": true}, + {"hosts": ["d3vices.pwa.pit.world"], "proxy": 3242, "max_body": "64MiB", "gzip": true}, + {"hosts": ["datamart.pwa.pit.world"], "proxy": 3310, "max_body": "1MiB", "gzip": true}, + {"hosts": ["getprivacycode.pwa.pit.world"], "proxy": 3230, "max_body": "64MiB", "gzip": true}, + {"hosts": ["goingbroke.pwa.pit.world"], "proxy": 3221, "max_body": "64MiB", "gzip": true}, + {"hosts": ["icemap.pwa.pit.world"], "proxy": 3262, "max_body": "64MiB", "gzip": true}, + {"hosts": ["media2markdown.pwa.pit.world"], "proxy": 3217, "max_body": "64MiB", "gzip": true}, + {"hosts": ["naallo.pwa.pit.world"], "proxy": 3259, "max_body": "64MiB", "gzip": true}, + {"hosts": ["opensocial.pwa.pit.world"], "proxy": 3210, "max_body": "64MiB", "gzip": true}, + {"hosts": ["opensourcelegends.pwa.pit.world"], "proxy": 3232, "max_body": "64MiB", "gzip": true}, + {"hosts": ["recipepdfs.pwa.pit.world"], "proxy": 3263, "max_body": "64MiB", "gzip": true}, + {"hosts": ["ringhuman.pwa.pit.world"], "proxy": 3233, "max_body": "64MiB", "gzip": true}, + {"hosts": ["saasrow.pwa.pit.world"], "proxy": 3276, "max_body": "64MiB", "gzip": true}, + {"hosts": ["submitfleet.pwa.pit.world"], "proxy": 3380, "max_body": "8MiB", "gzip": true}, + {"hosts": ["sysops.pwa.pit.world"], "proxy": 3330, "max_body": "1MiB", "gzip": true}, + {"hosts": ["tleehealth.pwa.pit.world"], "proxy": 3295, "max_body": "25MiB", "gzip": true}, + {"hosts": ["tsbb.pwa.pit.world"], "proxy": 3255, "max_body": "64MiB", "gzip": true}, + {"hosts": ["typeheard.pwa.pit.world"], "proxy": 3294, "max_body": "1100MiB", "gzip": true}, + {"hosts": ["weedforcrypto.pwa.pit.world"], "proxy": 3243, "max_body": "64MiB", "gzip": true}, + {"hosts": ["brisk-supabase.pwa.pit.world"], "proxy": 8238, "max_body": "64MiB", "gzip": true}, + {"hosts": ["c0ncerts-supabase.pwa.pit.world"], "proxy": 8278, "max_body": "64MiB", "gzip": true}, + {"hosts": ["icemap-supabase.pwa.pit.world"], "proxy": 8262, "max_body": "64MiB", "gzip": true}, + {"hosts": ["saasrow-supabase.pwa.pit.world"], "proxy": 8276, "max_body": "64MiB", "gzip": true}, + {"hosts": ["weedforcrypto-supabase.pwa.pit.world"], "proxy": 8243, "max_body": "64MiB", "gzip": true}, + {"hosts": ["c0upons-bbs.pwa.pit.world"], "proxy": 3293, "max_body": "64MiB", "gzip": true}, { "hosts": ["built.indie"], "proxy": 8798, "max_body": "1MiB", "moshpit": true }, { "hosts": ["family.ancestry", "family.tree"], "proxy": 8792, "max_body": "30MiB", "moshpit": true }, { "hosts": ["pay.agent"], "proxy": 8796, "max_body": "64KiB", "moshpit": true }, diff --git a/dev2/edge/systemd/caddy-edge-certs.path b/dev2/edge/systemd/caddy-edge-certs.path index b66623e..e29fb50 100644 --- a/dev2/edge/systemd/caddy-edge-certs.path +++ b/dev2/edge/systemd/caddy-edge-certs.path @@ -8,6 +8,7 @@ Description=Watch dev2's certificate directories for Caddy PathChanged=/etc/nginx/pit-proxy-certs PathChanged=/etc/ssl/moshpit PathChanged=/etc/ssl/moshpit-riotcoder +PathChanged=/etc/ssl/pwa-pit-world PathChanged=/etc/letsencrypt/live/pit.world Unit=caddy-edge-certs.service diff --git a/test/dev2-edge.test.ts b/test/dev2-edge.test.ts index 55220f9..21c6e1d 100644 --- a/test/dev2-edge.test.ts +++ b/test/dev2-edge.test.ts @@ -184,6 +184,18 @@ describe('dev2-edge render', () => { expect(route).toContain('import edge_proxy 8787'); }); + it('serves retired-domain apps as .pwa.pit.world from their existing containers', () => { + const { stream, caddy } = render(); + expect(stream).toMatch(/\n\s+advis0r\.pwa\.pit\.world\s+127\.0\.0\.1:8453;/); + expect(stream).toMatch(/\n\s+saasrow-supabase\.pwa\.pit\.world\s+127\.0\.0\.1:8453;/); + const route = caddy.slice(caddy.indexOf('https://saasrow.pwa.pit.world {')); + expect(route).toContain('import edge_proxy 3276'); + // One single-label wildcard certificate covers every pwa host, so no host may nest deeper. + const hosts = [...caddy.matchAll(/^https:\/\/(\S+\.pwa\.pit\.world) \{/gm)].map((m) => m[1]!); + expect(hosts.length).toBeGreaterThan(20); + for (const h of hosts) expect(h.split('.').length).toBe(4); + }); + it('every name the stream sends to Caddy has a Caddy site', () => { const { stream } = render(); const routed = [...stream.matchAll(/^\s+(\S+)\s+127\.0\.0\.1:8453;/gm)].map((m) => m[1]!);