# Same-origin reverse proxy for the browser-native uniweb chat client.
#
# This is the self-contained fragment that exposes uniweb on magnus
# (organic-machine.com). It is merged into magnus's /etc/caddy/Caddyfile, which
# also hosts unrelated services; only this service's blocks are versioned here
# (the other vhosts carry basic-auth secrets that do not belong in git). The live
# file imports the shared (security_headers) snippet that is duplicated below so
# this fragment validates on its own.
#
# One origin fronts the whole app so the SPA and the bus share an origin: no CORS,
# and the unibus cluster node IPs stay hidden behind this proxy. Caddy obtains and
# renews the Let's Encrypt certificate automatically (the *.organic-machine.com
# wildcard A record points here).
#
#   /        -> the static SPA (uniweb web/dist) with a single-page-app fallback
#   /api/*   -> the signed HTTPS control plane (membershipd :8470), prefix stripped
#   /nats    -> the NATS-over-WebSocket data plane (:8485 magnus / :8480 peers)
#
# Upstreams speak TLS with the bus's own self-signed CA, so Caddy skips upstream
# verification (the hop is still encrypted). The control plane signs requests over
# the UNPREFIXED path, so /api MUST be stripped (handle_path) or signatures fail.
#
# The membershipd nodes must run with the same-origin host in --cors-origins (so
# the NATS WebSocket Origin check accepts it) and with --trusted-proxies naming
# this Caddy node (127.0.0.1,::1,135.125.201.30) so the per-IP rate limit keys on
# the real client behind the proxy instead of collapsing to the proxy's one IP.

(security_headers) {
	header {
		Strict-Transport-Security "max-age=31536000"
		X-Content-Type-Options "nosniff"
		X-Frame-Options "DENY"
		Referrer-Policy "no-referrer"
		-Server
	}
}

chat-c200aa64c3125ce8b5f068e0.organic-machine.com {
	import security_headers

	# Control plane: strip /api so /api/rooms reaches membershipd as /rooms (the
	# path the client signs). Prefer the local node; lb_try_duration retries the
	# next node within the request on a dial error (safe: a refused connection sent
	# no bytes, so even a POST cannot double-apply), and fail_duration plus the
	# active /healthz check take a down node out of rotation.
	handle_path /api/* {
		reverse_proxy https://127.0.0.1:8470 https://141.94.69.66:8470 https://51.91.100.142:8470 {
			transport http {
				tls_insecure_skip_verify
			}
			lb_policy first
			lb_try_duration 5s
			lb_try_interval 250ms
			fail_duration 10s
			health_uri /healthz
			health_interval 10s
			health_timeout 5s
		}
	}

	# Data plane: NATS over WebSocket. Strip /nats so the upgrade reaches the ws
	# listener at its root. Caddy proxies the WebSocket upgrade natively. The ws
	# listener speaks TLS on :8485 (magnus; :8480 is taken by unibus_admin there)
	# and :8480 on the peers. Passive failover only (an HTTP health probe would be
	# rejected by the NATS ws endpoint).
	handle_path /nats* {
		reverse_proxy https://127.0.0.1:8485 https://141.94.69.66:8480 https://51.91.100.142:8480 {
			transport http {
				tls_insecure_skip_verify
			}
			lb_policy first
			lb_try_duration 5s
			lb_try_interval 250ms
			fail_duration 30s
		}
	}

	# SPA: static files with a client-side-routing fallback to index.html.
	handle {
		root * /opt/uniweb/dist
		try_files {path} /index.html
		file_server
	}
}
