From 32e2ac1f8efb29d4b3f03d1d2cc19ed67f9d2ae0 Mon Sep 17 00:00:00 2001 From: Paul Payne Date: Sun, 14 Jun 2026 22:26:00 -0700 Subject: [PATCH] =?UTF-8?q?fix(gateway):=20redirect=20/foo=20=E2=86=92=20/?= =?UTF-8?q?foo/=20for=20path=20routes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- core/src/castle_core/generators/caddyfile.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/core/src/castle_core/generators/caddyfile.py b/core/src/castle_core/generators/caddyfile.py index a48cce7..b48f59e 100644 --- a/core/src/castle_core/generators/caddyfile.py +++ b/core/src/castle_core/generators/caddyfile.py @@ -112,6 +112,16 @@ def generate_caddyfile_from_registry( # named host doesn't pull the listener into TLS or try to bind :80/:443. 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 for r in routes: if r.kind == "static" and r.address == "/":