fix(gateway): redirect /foo → /foo/ for path routes

handle_path /foo/* matches /foo/ but not the bare /foo, so the no-trailing-
slash form fell through to the root catch-all (castle-app) — which then 404'd
because its router has no such route. Hitting http://gateway:9000/power-graph-app
served the dashboard's 404 instead of Power Graph; same for every proxied
service accessed without the slash.

The generator now emits 'redir /foo /foo/' for each path-prefix route (static,
proxy, remote), so the no-slash form canonicalizes to the slash form that
handle_path matches. Caddy's bare path matcher is exact, so /foo/bar is
unaffected. Host routes and the root '/' are unaffected.

Verified: /power-graph-app → 302 → Power Graph; caddy validate clean; core 94 /
api 52 green.
This commit is contained in:
2026-06-14 22:26:00 -07:00
parent 3100dff3a2
commit 32e2ac1f8e

View File

@@ -112,6 +112,16 @@ def generate_caddyfile_from_registry(
# named host doesn't pull the listener into TLS or try to bind :80/:443. # named host doesn't pull the listener into TLS or try to bind :80/:443.
lines = ["{", " auto_https off", "}", "", f":{gw_port} {{"] lines = ["{", " auto_https off", "}", "", f":{gw_port} {{"]
# Trailing-slash redirect: `handle_path /foo/*` doesn't match the bare `/foo`,
# so without this the no-slash form falls through to the root catch-all
# (serving the wrong app). Redirect /foo → /foo/ for each path-prefix route.
# (Caddy's bare path matcher is exact, so /foo/bar is unaffected.)
redirs = [r.address for r in routes if r.address.startswith("/") and r.address != "/"]
for prefix in redirs:
lines.append(f" redir {prefix} {prefix}/")
if redirs:
lines.append("")
root_static: GatewayRoute | None = None root_static: GatewayRoute | None = None
for r in routes: for r in routes:
if r.kind == "static" and r.address == "/": if r.kind == "static" and r.address == "/":