# PiCloud production Caddyfile.
#
# Set PICLOUD_DOMAIN and PICLOUD_ADMIN_EMAIL in the environment Caddy is
# started from (docker-compose.prod.yml passes them through). Caddy then
# obtains and renews a Let's Encrypt cert automatically for that domain.
#
# Audit 2026-06-11 (H-A1/2/3/5, M07-06/07/08/09): defense-in-depth
# headers. HSTS lands on every prod response. CSP, X-Frame-Options,
# Permissions-Policy, and Cache-Control: no-store land on the dashboard
# SPA and admin API. nosniff + Referrer-Policy land everywhere. User-
# route responses (catch-all `handle`) deliberately get NO CSP — user
# scripts own their own response headers by design.
# `?` operator = "default if missing" so downstream responses (e.g. the
# file-download endpoint with its own restrictive CSP) win.
{
	email {$PICLOUD_ADMIN_EMAIL}
}

{$PICLOUD_DOMAIN} {
	encode zstd gzip

	# Audit 2026-06-11 (H-1) — hard request-body ceiling at the proxy.
	# Without this, Caddy forwards bodies up to its multi-GB default to
	# picloud. 12 MB sits just above the orchestrator's 10 MiB user-route
	# read so legitimate large invokes pass; admin / email handlers are
	# bounded far tighter by their own Axum extractor limits.
	request_body {
		max_size 12MB
	}

	# Baseline headers on every response. HSTS is unconditional in prod.
	header {
		Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
		X-Content-Type-Options "nosniff"
		Referrer-Policy "no-referrer"
	}

	handle /healthz {
		reverse_proxy picloud:8080
	}
	handle /version {
		reverse_proxy picloud:8080
	}

	handle /api/v1/admin/* {
		header {
			?Content-Security-Policy "default-src 'none'; frame-ancestors 'none'; base-uri 'none'"
			?X-Frame-Options "DENY"
			?Cache-Control "no-store"
			?Permissions-Policy "geolocation=(), camera=(), microphone=(), payment=(), usb=(), accelerometer=(), gyroscope=()"
		}
		reverse_proxy picloud:8080
	}
	handle /api/v1/execute/* {
		reverse_proxy picloud:8080
	}

	handle /api/* {
		respond 404 {
			body "{\"error\":\"no such API version — see /version for supported routes\"}"
			close
		}
	}

	handle /admin/* {
		header {
			?Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'; frame-ancestors 'none'; base-uri 'none'; form-action 'self'"
			?X-Frame-Options "DENY"
			?Cache-Control "no-store"
			?Permissions-Policy "geolocation=(), camera=(), microphone=(), payment=(), usb=(), accelerometer=(), gyroscope=()"
		}
		reverse_proxy dashboard:80
	}
	handle /admin {
		header {
			?Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'; frame-ancestors 'none'; base-uri 'none'; form-action 'self'"
			?X-Frame-Options "DENY"
		}
		reverse_proxy dashboard:80
	}

	handle {
		reverse_proxy picloud:8080
	}

	log {
		output stdout
		format json
	}
}
