# Caddyfile for the Familienarchiv host.
#
# Caddy runs on the host (not in a container) and reverse-proxies into
# the docker compose stacks bound to 127.0.0.1.
#
# Naming convention for ports (also documented in docker-compose.prod.yml):
#   production: backend 8080, frontend 3000
#   staging:    backend 8081, frontend 3001
#   gitea:      3005
#
# Security headers and the /actuator block apply to both archive vhosts.
# X-Frame-Options is deliberately NOT set here: Spring Security configures
# frame-options SAMEORIGIN (for the in-app PDF preview iframe). Setting
# DENY in Caddy would conflict.

(security_headers) {
	header {
		Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
		X-Content-Type-Options "nosniff"
		Referrer-Policy "strict-origin-when-cross-origin"
		# Deny browser APIs the app does not use. Reduces blast radius of an
		# XSS landing in a privileged origin: a payload cannot silently turn
		# on the microphone or read geolocation.
		Permissions-Policy "camera=(), microphone=(), geolocation=()"
		# No Content-Security-Policy is set yet. When one is added, it MUST
		# include `script-src 'wasm-unsafe-eval'` and `worker-src 'self' blob:`
		# or the pdf.js WebAssembly image decoders (JBIG2/CCITTFax/JPEG2000)
		# and worker will be blocked and scanned PDFs render blank.
		# See #708 and docs/adr/028-pdfjs-wasm-decoders-and-csp-constraint.md.
		-Server
	}
}

(block_actuator) {
	# Defense in depth: even if management.endpoints.web.exposure.include grows
	# in application.yaml, /actuator/* is unreachable externally. The internal
	# Prometheus scrape (future) talks to the backend directly on the docker
	# network, not via Caddy.
	#
	# Why a `handle` block and not a top-level `respond @matcher`: each archive
	# vhost has a catch-all `handle { reverse_proxy ... }` that matches every
	# path including /actuator/*, and Caddy's `handle` blocks are mutually
	# exclusive. Without our own `handle /actuator/*` the catch-all wins, the
	# request is proxied to the backend, and Spring Security 302s to /login
	# instead of Caddy returning 404. See #512.
	handle /actuator/* {
		respond 404
	}
}

(access_log) {
	# JSON access log for fail2ban. The jail at infra/fail2ban/familienarchiv.conf
	# watches this file for 401 responses on /api/auth/login.
	# Caddy auto-creates /var/log/caddy/ when running as the `caddy` system user.
	log {
		output file /var/log/caddy/access.log {
			roll_size 10mb
			roll_keep 14
		}
		format json
	}
}

archiv.raddatz.cloud {
	import security_headers
	import block_actuator
	import access_log

	handle /api/* {
		reverse_proxy 127.0.0.1:8080
	}

	handle {
		reverse_proxy 127.0.0.1:3000
	}
}

staging.raddatz.cloud {
	import security_headers
	import block_actuator
	import access_log

	handle /api/* {
		reverse_proxy 127.0.0.1:8081
	}

	handle {
		reverse_proxy 127.0.0.1:3001
	}
}

git.raddatz.cloud {
	import security_headers
	reverse_proxy 127.0.0.1:3005
}

grafana.archiv.raddatz.cloud {
	import security_headers
	reverse_proxy 127.0.0.1:3003
}

glitchtip.archiv.raddatz.cloud {
	import security_headers
	reverse_proxy 127.0.0.1:3002
}
