From 5884349336813251a80e8842f9b7efe012694b6d Mon Sep 17 00:00:00 2001 From: CarterPerez-dev Date: Sun, 17 May 2026 18:12:09 -0400 Subject: [PATCH] fix(canary): dev nginx routes /c/, /k/, /api/ to canary + manage_url defaults to base_url MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two tightly-coupled bugs visible after the PUBLIC_BASE_URL fix landed: 1. Trigger URLs now correctly show :22784 (the host-exposed nginx port), but opening one in the browser rendered the SPA's NotFound page instead of hitting the backend trigger handler. Root cause: dev.nginx only had `location /` → frontend_dev. No location block for `/c/`, `/k/`, or even `/api/`. Every request went to the SPA; only `/api/*` happened to work because vite's internal proxy caught it on its way through frontend_dev → canary. /c/* and /k/* (the trigger paths) just fell through to the SPA fallback. Fix: add `upstream canary { server canary:8080 }` to nginx.conf and three new location blocks in dev.nginx for /api/, /c/, /k/. Each one proxy_passes directly to the canary upstream with X-Real-IP + X-Forwarded-For + X-Forwarded-Proto set so middleware.RealIP() resolves the actual visitor IP rather than the docker bridge. Mirrors what spec §12.3 prescribes for prod nginx. 2. Manage URL in responses still showed :8080 even after PUBLIC_BASE_URL was set to :22784. Two separate config keys: canary.base_url (PUBLIC_BASE_URL → trigger_url) and canary.manage_url (CANARY_MANAGE_URL → manage_url). The latter has no env-name overlap with the spec's PUBLIC_BASE_URL, so it kept its default literal "http://localhost:8080". Fix: in config.Load(), after unmarshal, fall manage_url back to base_url when it's empty or still equals the bare default. Operators setting one env var (PUBLIC_BASE_URL) now get both URLs computed off it. Explicit CANARY_MANAGE_URL still wins if set — preserves the prod ability to serve manage at a different domain than triggers. Extracted the literal to a `defaultCanaryBaseURL` const so the default and the fallback sentinel reference the same string. After this + the prior PUBLIC_BASE_URL alias, a fresh token from the form should produce trigger_url + manage_url both pointing at http://localhost:${NGINX_HOST_PORT}/... — clickable, reachable, and triggers hitting /c/ now route to canary and record events. Operator action: just dev-down && just dev-up to rebuild nginx + canary containers with the new config. --- .../backend/internal/config/config.go | 11 +++++-- .../infra/nginx/dev.nginx | 30 +++++++++++++++++++ .../infra/nginx/nginx.conf | 5 ++++ 3 files changed, 44 insertions(+), 2 deletions(-) diff --git a/PROJECTS/beginner/canary-token-generator/backend/internal/config/config.go b/PROJECTS/beginner/canary-token-generator/backend/internal/config/config.go index 162ed398..31c0c237 100644 --- a/PROJECTS/beginner/canary-token-generator/backend/internal/config/config.go +++ b/PROJECTS/beginner/canary-token-generator/backend/internal/config/config.go @@ -14,6 +14,8 @@ import ( "github.com/knadh/koanf/v2" ) +const defaultCanaryBaseURL = "http://localhost:8080" + type Config struct { App AppConfig `koanf:"app"` Server ServerConfig `koanf:"server"` @@ -169,6 +171,11 @@ func Load(configPath string) (*Config, error) { return } + if cfg.Canary.ManageURL == "" || + cfg.Canary.ManageURL == defaultCanaryBaseURL { + cfg.Canary.ManageURL = cfg.Canary.BaseURL + } + if err := validate(cfg); err != nil { loadErr = fmt.Errorf("validate config: %w", err) return @@ -244,8 +251,8 @@ func loadDefaults(k *koanf.Koanf) error { "otel.sample_rate": 0.1, "otel.service_name": "canary-token-generator", - "canary.base_url": "http://localhost:8080", - "canary.manage_url": "http://localhost:8080", + "canary.base_url": defaultCanaryBaseURL, + "canary.manage_url": defaultCanaryBaseURL, "turnstile.secret_key": "", "turnstile.site_key": "", diff --git a/PROJECTS/beginner/canary-token-generator/infra/nginx/dev.nginx b/PROJECTS/beginner/canary-token-generator/infra/nginx/dev.nginx index b4e28a16..45c39dae 100644 --- a/PROJECTS/beginner/canary-token-generator/infra/nginx/dev.nginx +++ b/PROJECTS/beginner/canary-token-generator/infra/nginx/dev.nginx @@ -20,6 +20,36 @@ server { add_header Content-Type text/plain; } + location /api/ { + proxy_pass http://canary; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_read_timeout 30s; + } + + location /c/ { + proxy_pass http://canary; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_read_timeout 30s; + } + + location /k/ { + proxy_pass http://canary; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_read_timeout 30s; + } + location / { proxy_pass http://frontend_dev; proxy_http_version 1.1; diff --git a/PROJECTS/beginner/canary-token-generator/infra/nginx/nginx.conf b/PROJECTS/beginner/canary-token-generator/infra/nginx/nginx.conf index 6b3fc970..5988ea4d 100644 --- a/PROJECTS/beginner/canary-token-generator/infra/nginx/nginx.conf +++ b/PROJECTS/beginner/canary-token-generator/infra/nginx/nginx.conf @@ -29,6 +29,11 @@ http { keepalive 8; } + upstream canary { + server canary:8080; + keepalive 16; + } + limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s; limit_conn_zone $binary_remote_addr zone=conn_limit:10m; limit_req_status 429;