{
	servers {
		# Address-based rules and QUIC early data do not mix: a decision taken
		# on 0-RTT data can answer 425, and some browsers do not retry — an
		# intermittent lockout from the console, which is the worst kind.
		protocols h1 h2
	}
	# Replace: this address receives the certificate-expiry notices for THIS
	# installation. Left as the copied original, they reach whoever wrote the
	# file instead of whoever runs the server.
	email you@example.com
}

# Reference reverse-proxy configuration.
#
# Copy to /etc/caddy/Caddyfile, replace the hostnames, then:
#   sudo bash deploy/install-agent.sh
# which replaces the console's allowlist line with an import generated from the
# list the owner keeps in the console itself.
#
# The rule this file exists to enforce: only the marketing site, the customer
# portal and the status page are public. The console and the websocket endpoint
# must not merely refuse — they must be indistinguishable from a hostname that
# serves nothing at all. An empty 404 is itself information: it says something
# terminates TLS here and chose not to answer.



# ── Customer portal ──────────────────────────────────────────────────────────
app.example.com {
	reverse_proxy 127.0.0.1:8080
}

# ── Stripe webhook, and nothing else ─────────────────────────────────────────
# The application answers the whole site on every hostname, so without this the
# login and registration pages are served here too. Narrowed to the one request
# this hostname exists for; everything else never reaches PHP.
api.example.com {
	@webhook {
		method POST
		path /webhooks/stripe
	}
	handle @webhook {
		reverse_proxy 127.0.0.1:8080
	}
	handle {
		respond 404
	}
}

# ── Websockets (Reverb) ──────────────────────────────────────────────────────
ws.example.com {
	# Only an actual websocket upgrade. A browser opening this name has no
	# business here, and answering it — even with a 404 — advertises that
	# something is listening.
	@websocket {
		header Connection *Upgrade*
		header Upgrade websocket
	}
	handle @websocket {
		reverse_proxy 127.0.0.1:8081
	}
	handle {
		abort
	}
}

# ── Operator console ─────────────────────────────────────────────────────────
# The network-level lock. The hostname checks inside the application are
# defence in depth — a Host header is chosen by the caller, so they cannot
# decide WHO gets through. This can.
#
# 10.66.0.0/24 is the WireGuard subnet. 62.178.50.33 is temporary, so the first
# VPN access can be created at all; it comes out once that is done.
admin.example.com {
	import /etc/caddy/clupilot-console-allow.conf
	handle @allowed {
		reverse_proxy 127.0.0.1:8080
	}
	# Nothing at all for anyone else. An empty 404 still says "something
	# terminates TLS here and chose not to answer" — which is itself the
	# information worth hiding. Closing the connection is what a host that does
	# not serve this name looks like.
	handle {
		abort
	}
}
www.example.com {
	reverse_proxy 127.0.0.1:8080
}

status.example.com {
	reverse_proxy 127.0.0.1:8080
}

# ── Von der Konsole verwaltete Hostnamen ─────────────────────────────────────
# Erzeugt von `clupilot-host-step apply-proxy-hosts` aus der Liste, die der
# Betreiber in der Konsole pflegt. Damit lässt sich ein Name hinzufügen, ohne
# sich auf den Server zu melden — der Handgriff, der `files.clupilot.com`
# gekostet hat.
#
# BEIDES setzt `sudo bash deploy/install-agent.sh` selbst — die Datei und die
# Import-Zeile unten. Von Hand eintragen muss man nichts, und man sollte es auch
# nicht: die Datei MUSS existieren, bevor irgendetwas sie importiert, denn ein
# Import ins Leere macht die GANZE Proxy-Konfiguration ungültig und nimmt die
# öffentliche Website mit. Eine Anleitung wird in der falschen Reihenfolge
# befolgt, ein Skript nicht.
#
# Der Installer prüft nach dem Eintragen mit `caddy validate` und nimmt die
# Zeile wieder heraus, wenn Caddy sie ablehnt.
#
# EIN NAME GEHÖRT AN EINE STELLE. Steht ein Hostname schon von Hand in dieser
# Datei, lässt die Erzeugung ihn aus — zwei Blöcke für denselben Namen lehnt
# Caddy ab, und zwar die ganze Konfiguration.
import /etc/caddy/clupilot-proxy-hosts.conf

# ── Dateien zum Herunterladen ────────────────────────────────────────────────
# FILES_HOST aus der .env. Zwei Dinge liegen hier, und beide müssen von überall
# erreichbar sein:
#
#   - Rechtsdokumente (AGB, AV, TOM). Ihre Adressen stehen in Verträgen und auf
#     Rechnungen und müssen Jahre halten.
#   - Das Bootstrap-Archiv für die Host-Übernahme. Es wird von einem Server im
#     RETTUNGSSYSTEM geholt — einer Maschine ohne Tunnel, mit einer öffentlichen
#     Adresse, die in keiner Freigabeliste steht. Genau deshalb steht hier KEIN
#     `import clupilot-console-allow.conf`: die Sperre der Konsole wäre hier die
#     Sperre gegen den einzigen, der es braucht.
#
# Der Schutz sitzt in der Anwendung, nicht im Proxy: ohne gültigen Einmal-Code
# antwortet das Archiv mit 404, und die Dokumente sind ohnehin öffentlich.
#
# Auch KEIN `http://… { abort }` wie bei admin und ws weiter unten. Dieser Name
# soll bekannt sein — und ein abgewürgtes Port 80 nimmt der ACME-Prüfung den
# Weg, wenn Caddys eigener Handler einmal nicht als Erster drankommt.
files.example.com {
	reverse_proxy 127.0.0.1:8080
}

# Plain HTTP for the two hidden names. Without this Caddy answers port 80 with
# an automatic redirect to https, which announces that both hostnames are
# served here — the one thing this file exists to avoid. The ACME challenge is
# unaffected: Caddy's own challenge handler runs ahead of these routes.
http://admin.example.com, http://ws.example.com {
	abort
}
