gateway: serve the dashboard at wild-pc.<domain>, 404 unrouted subdomains

The dashboard's deployment kept the pre-rename name `wildpc`, so it was
published at wildpc.<domain> while everything else had moved to Wild PC
naming. Rename the deployment and its program to `wild-pc` (the subdomain
is the deployment name) and follow the `_DASHBOARD` constant in both the
Caddyfile generator and doctor — that constant also drives the `:9000`
redirect and doctor's `wildpc program build` hint, which is why the
program is renamed alongside the deployment.

Browsing the old name exposed a second problem: in acme mode every route
lives inside one `*.<domain>` wildcard site, so an unrouted host still
matches the site and completes its TLS handshake. Caddy then ran off the
end of the block and returned a bare 200 with an empty body — a typo'd or
renamed subdomain looked *up* in a browser while rendering nothing. Each
wildcard site now ends with an unmatched `handle` that responds 404.

The API subdomain is untouched: the dashboard derives its API origin by
stripping the first label, so wild-pc.<domain> still resolves to
wildpc-api.<domain>, and the WebSocket origin gate matches on
gateway.domain rather than a hostname.

Also seed the bootstrap dashboard deployment with the `requires:
wildpc-api` block the live deployment already carried, so a fresh install
declares the dependency.
This commit is contained in:
2026-08-02 16:37:04 -07:00
parent b48535745b
commit 15957677ff
9 changed files with 49 additions and 11 deletions

View File

@@ -101,7 +101,16 @@ class TestAcmeMode:
def test_port_9000_redirects_to_dashboard(self) -> None:
cf = generate_caddyfile_from_registry(_acme({"api": _dep(9020, expose=True, name="api")}))
assert ":9000 {" in cf
assert "redir https://wildpc.example.com{uri}" in cf
assert "redir https://wild-pc.example.com{uri}" in cf
def test_unmatched_host_404s_instead_of_empty_200(self) -> None:
"""A host that matches `*.<domain>` but no route must 404. Without the
trailing catch-all Caddy falls off the end of the site block and returns
an empty 200, so a typo'd subdomain looks up but renders nothing."""
cf = generate_caddyfile_from_registry(_acme({"api": _dep(9020, expose=True, name="api")}))
wildcard = cf.split("*.example.com {", 1)[1].split("\n}", 1)[0]
assert "handle {" in wildcard
assert 'respond "no wildpc route for this host" 404' in wildcard
def test_no_path_routes(self) -> None:
cf = generate_caddyfile_from_registry(_acme({"api": _dep(9020, expose=True, name="api")}))