# Caddyfile — PRODUCTION carnet.health (copie de référence de /etc/caddy/Caddyfile).
#
# ⚠ CE FICHIER N'EST PAS DÉPLOYÉ AUTOMATIQUEMENT. deploy-prod.sh pousse l'app, le site et le
# serveur de synchro, jamais la configuration Caddy. Toute modification se fait à la main :
#     scp deploy/Caddyfile debian@$(dig +short A carnet.health | tail -1):/tmp/Caddyfile.neuf
#     ssh … 'sudo cp /tmp/Caddyfile.neuf /etc/caddy/Caddyfile && caddy validate --config /etc/caddy/Caddyfile'
#     ssh … 'sudo systemctl reload caddy'
# …après avoir remis le vrai hash bcrypt de dev.carnet.health (caviardé ici, voir plus bas).
#
# HISTORIQUE : jusqu'au 13.08.2026 ce fichier décrivait un serveur IMAGINAIRE — un vhost <DOMAINE>
# générique, jamais déployé, dont les commentaires avouaient eux-mêmes que « la prod ne sert PAS
# cet en-tête aujourd'hui ». Une configuration versionnée qui a divergé du réel est pire que pas
# de configuration : on la lit en croyant savoir. D'où cette copie fidèle, à tenir à jour À LA MAIN
# en même temps que le VPS.
#
# EN-TÊTES DE SÉCURITÉ (13.08.2026). Revue déclenchée par un scan externe (Web Check) : les
# « ports 21/23/25/5900 ouverts » qu'il signalait sont des FAUX POSITIFS (le scan passait par un
# réseau intermédiaire qui accepte toute connexion TCP ; `ss -tlnp` sur la machine ne montre que
# 22, 80, 443 et 8091). En revanche ses griefs sur les en-têtes étaient VRAIS, et la revue a
# trouvé pire que ce qu'il disait :
#   · app.carnet.health n'avait NI X-Frame-Options NI Referrer-Policy (la vitrine, si) ;
#   · la CSP de l'app vit dans une balise <meta> — or `frame-ancestors` est IGNORÉ en meta.
#     Le coffre était donc encadrable en iframe : clickjacking possible sur un écran qui
#     déverrouille des dossiers de soins. C'est le vrai défaut de cette revue.
#
# La CSP posée ici pour l'app est EXACTEMENT celle que l'app sert déjà en meta (donc zéro nouveau
# blocage : elle tourne avec depuis le premier jour), plus `frame-ancestors 'none'` qui ne
# fonctionne qu'en en-tête. 'wasm-unsafe-eval' est REQUIS par libsodium ; sans lui le coffre ne
# déverrouille plus. 'unsafe-inline' n'est toléré QUE pour les styles (attributs style=).

(entetes-communs) {
	# includeSubDomains : app, sync et dev sont tous en HTTPS — aucun sous-domaine en clair.
	# `preload` volontairement ABSENT : c'est un engagement quasi irréversible (retrait long et
	# manuel auprès des navigateurs), à décider explicitement, pas à glisser dans une correction.
	Strict-Transport-Security "max-age=31536000; includeSubDomains"
	X-Content-Type-Options nosniff
	Referrer-Policy strict-origin-when-cross-origin
	X-Frame-Options DENY
	# Aucune de ces API n'est utilisée : on les refuse pour tout le monde, y compris un script
	# étranger qui aurait franchi la CSP.
	Permissions-Policy "camera=(self), microphone=(), geolocation=(), payment=(), usb=(), interest-cohort=()"
	Cross-Origin-Opener-Policy same-origin
	Cross-Origin-Resource-Policy same-origin
	# Le numéro de version de Caddy n'apprend rien d'utile à une visiteuse, seulement à un attaquant.
	-Server
}

# CACHE de l'app. Les fichiers /assets/* portent un hash Vite dans leur nom : leur contenu ne
# changera JAMAIS (nouveau build = nouveau nom), d'où un an de cache immuable — zéro requête de
# revalidation au démarrage, ce qui se sent en 4G dans une cage d'escalier. Tout le RESTE
# (index.html, sw.js, manifeste) revalide à chaque fois : une page servie depuis un cache périmé,
# c'est une infirmière restée sur la version d'hier sans le savoir.
# Les deux matchers sont EXCLUSIFS l'un de l'autre — écrire un `Cache-Control` global puis tenter
# de le surcharger sur /assets/* ne marche PAS (essayé le 13.08.2026 : le global gagne, quel que
# soit l'ordre des directives).
(cache-app) {
	@assets path /assets/*
	@pages not path /assets/*
	header @assets Cache-Control "public, max-age=31536000, immutable"
	header @pages Cache-Control "no-cache"
}

carnet.health {
	root * /var/www/site
	encode gzip zstd
	file_server
	header {
		import entetes-communs
		# La vitrine porte des scripts et des styles INLINE (page statique unique) : 'unsafe-inline'
		# est nécessaire tant qu'ils ne sont pas extraits ou hachés. Le reste ferme l'essentiel :
		# rien ne part vers un domaine tiers, aucun objet, aucun encadrement.
		Cache-Control "no-cache"
		Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'"
	}
}

app.carnet.health {
	root * /var/www/app
	encode gzip zstd
	handle /assets/* {
		file_server
	}
	handle {
		try_files {path} /index.html
		file_server
	}
	header {
		import entetes-communs
		# IDENTIQUE à la <meta> servie par l'app (index.html) — toute divergence ferait tomber la
		# règle la plus stricte des deux et casserait un écran. Le seul ajout est frame-ancestors.
		# camera=(self) : la prise de photo de plaie se fait dans l'app.
		Content-Security-Policy "default-src 'self'; script-src 'self' 'wasm-unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self' https://sync.utocracy.org https://sync.carnet.health http://127.0.0.1:9277; worker-src 'self'; manifest-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'"
	}
	import cache-app
}

sync.carnet.health {
	# H1 (21.08.2026) : IPv4 EXPLICITE, jamais `localhost`. Le serveur de synchro n'écoute plus que sur
	# 127.0.0.1 (index.mjs) ; or `localhost` peut résoudre en ::1 (IPv6) — Caddy proxifierait alors une
	# adresse sur laquelle personne n'écoute et TOUTE la synchro tomberait. Les deux bouts en IPv4.
	reverse_proxy 127.0.0.1:8091
	header {
		import entetes-communs
		# API pure : aucune page, donc aucune ressource à partager avec une autre origine.
		Cache-Control "no-store"
		Content-Security-Policy "default-src 'none'; frame-ancestors 'none'; base-uri 'none'"
	}
}

# PRÉVERSION — chantiers en cours (drapeau de build VITE_CABINET). Calqué sur app.carnet.health,
# plus deux protections : mot de passe (du code inachevé ne doit pas être navigable) et interdiction
# d'indexation. Coffre isolé par origine : les données saisies ici ne touchent jamais celles de app.
# Déployé par ./scripts/deploy-prod.sh dev (racine /var/www/app-dev, releases préfixées dev-).
dev.carnet.health {
	basic_auth {
		# Le hash réel vit UNIQUEMENT sur le VPS (/etc/caddy/Caddyfile) : ce fichier-ci part
		# dans l'archive de source publiée sous AGPLv3, un hash bcrypt n'a rien à y faire.
		# Le régénérer : caddy hash-password
		dd <HASH-BCRYPT-VOIR-VPS>
	}
	root * /var/www/app-dev
	encode gzip zstd
	handle /assets/* {
		file_server
	}
	handle {
		try_files {path} /index.html
		file_server
	}
	header {
		import entetes-communs
		X-Robots-Tag "noindex, nofollow"
		Content-Security-Policy "default-src 'self'; script-src 'self' 'wasm-unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self' https://sync.utocracy.org https://sync.carnet.health http://127.0.0.1:9277; worker-src 'self'; manifest-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'"
	}
	import cache-app
}
