# Dream Server — LAN-facing reverse proxy (host-based routing)
#
# Each Dream Server surface gets its own subdomain on the device's mDNS
# hostname. That way every backend stays root-mounted (no Open WebUI
# subpath gymnastics, no React Router base-href games) and the
# `dream-session` cookie can be Domain-scoped to <device>.local so SSO
# works across all of them.
#
# Hostnames the proxy answers (all pointing at the device's LAN IP via
# dream-mdns or equivalent DNS/hosts records):
#
#   <device>.local          — bare hostname: 302 redirect to chat
#   chat.<device>.local     — Open WebUI (root-mounted)
#   dashboard.<device>.local— Dream Dashboard (operator UI)
#   auth.<device>.local     — dashboard-api: magic-link redemption
#                              + /api/auth/verify-session
#   api.<device>.local      — dashboard-api: /api/* admin surface
#   hermes.<device>.local   — hermes-proxy (only when enabled)
#
# Each block listens on the proxy's port (default :80) and matches on
# the Host header. We do NOT route by path on the bare hostname —
# trying to subpath-mount Open WebUI under /chat broke websockets,
# static asset paths, and OAuth callbacks (see audit on #1172).
#
# Security posture:
#   * Proxy is the LAN-facing surface; everything behind it stays
#     loopback-bound on the host.
#   * Trust is delegated to each backend's existing checks: dashboard-
#     api's API-key, Open WebUI's WEBUI_AUTH, hermes-proxy's forward_auth.
#   * For exposure beyond the trusted LAN, put this behind Tailscale.
#     Don't publish to the public internet without TLS + extra auth.

{
	# HTTP only by default. TLS via tailscale-cert / Caddy-issued certs
	# is documented in docs/DREAM-PROXY.md.
	auto_https off
	admin off
	# Use the placeholder so a Caddyfile with no DREAM_DEVICE_NAME still
	# parses; substitution happens at server start.
	log {
		output stdout
		format console
		level INFO
	}
}

# Variable: device-name segment used to build the per-service hostnames.
# Operators set DREAM_DEVICE_NAME in .env; the compose passes it through.
# Falls back to "dream" so the proxy comes up even if .env is incomplete.

# ---------------------------------------------------------------------
# Health endpoint — anonymous, served on every hostname for probe ease.
# ---------------------------------------------------------------------
:80 {
	@health path /health
	handle @health {
		respond "ok" 200
	}
	# Fall through to the host-matched blocks below.
}

# ---------------------------------------------------------------------
# chat.<device>.local → Open WebUI (root-mounted)
# ---------------------------------------------------------------------
http://chat.{$DREAM_DEVICE_NAME:dream}.local {
	reverse_proxy open-webui:8080 {
		header_up X-Forwarded-Proto {scheme}
		header_up X-Forwarded-Host {host}
		# Caddy auto-handles WebSocket upgrades; Open WebUI's streaming
		# endpoints work without extra config.
	}
}

# ---------------------------------------------------------------------
# dashboard.<device>.local → Dream Dashboard (operator UI)
# ---------------------------------------------------------------------
http://dashboard.{$DREAM_DEVICE_NAME:dream}.local {
	reverse_proxy dashboard:3001 {
		header_up X-Forwarded-Proto {scheme}
		header_up X-Forwarded-Host {host}
	}
}

# ---------------------------------------------------------------------
# auth.<device>.local → dashboard-api (magic-link redemption surface)
#
# This is the host where the dream-session cookie is SET by redemption.
# session_signer sets Domain=<device>.local on the cookie so it also
# reaches chat.<device>.local, hermes.<device>.local, etc.
# ---------------------------------------------------------------------
http://auth.{$DREAM_DEVICE_NAME:dream}.local {
	reverse_proxy dashboard-api:3002 {
		header_up X-Forwarded-Proto {scheme}
		header_up X-Forwarded-Host {host}
	}
}

# ---------------------------------------------------------------------
# api.<device>.local → dashboard-api (admin /api/* surface)
#
# Same upstream as auth.<device>.local; separated by hostname for
# operator-doc clarity ("invites/QR/etc. live at auth, admin status
# live at api"). Both call into the same FastAPI app; routing the
# whole hostname keeps the backend root-mounted.
# ---------------------------------------------------------------------
http://api.{$DREAM_DEVICE_NAME:dream}.local {
	reverse_proxy dashboard-api:3002 {
		header_up X-Forwarded-Proto {scheme}
		header_up X-Forwarded-Host {host}
	}
}

# ---------------------------------------------------------------------
# hermes.<device>.local → hermes-proxy (only effective when the
# hermes-proxy extension is enabled; the upstream resolves via Docker
# DNS and 502s when the container isn't running, which is correct).
# ---------------------------------------------------------------------
http://hermes.{$DREAM_DEVICE_NAME:dream}.local {
	reverse_proxy hermes-proxy:9120 {
		header_up X-Forwarded-Proto {scheme}
		header_up X-Forwarded-Host {host}
	}
}

# ---------------------------------------------------------------------
# Bare <device>.local — the friendliest URL. Redirect to chat (the
# surface most users want). Operators who'd rather land on dashboard
# can edit this block or front the proxy with their own routing.
# ---------------------------------------------------------------------
http://{$DREAM_DEVICE_NAME:dream}.local {
	redir http://chat.{$DREAM_DEVICE_NAME:dream}.local{uri} 302
}
