ai.cgxyserver.xyz {
	encode zstd gzip
	reverse_proxy new-api:3000 {
		flush_interval -1
	}
	header {
		-Server
		Strict-Transport-Security "max-age=31536000"
	}
	log {
		output stdout
		format console
	}
}

pay.cgxyserver.xyz {
	encode zstd gzip

	request_body {
		max_size 64KB
	}

	reverse_proxy 7y2epay:8080

	header {
		-Server
		Strict-Transport-Security "max-age=31536000; includeSubDomains"
		X-Content-Type-Options "nosniff"
		X-Frame-Options "DENY"
		Referrer-Policy "no-referrer"
		Permissions-Policy "geolocation=(), microphone=(), camera=(), payment=()"
		Content-Security-Policy "default-src 'none'; img-src 'self'; style-src 'self'; script-src 'self'; connect-src 'self'; form-action 'self'; frame-ancestors 'none'; base-uri 'none'"
		Cross-Origin-Resource-Policy "same-origin"
		X-Robots-Tag "noindex, nofollow"
	}

	log {
		output discard
	}
}

# ═══════════════════════════════════════════════════════════════════════════
# 云梦泽API — appended by infra/compose/api-vps-edge/deploy.sh.
#
# Everything ABOVE this banner is the pre-existing edge configuration and is
# kept byte-for-byte identical (drift is refused unless --force). The shared
# Caddy container is never recreated: the config file is rewritten in place
# (same inode, because it is a single-file bind mount) and swapped with
# `caddy reload` (graceful, no downtime for ai./pay.).
#
# api.yunmengze.xyz -> ymz-api-brand:8080 (HTML brand layer) -> ymz-api-app
# /_ymz/broker/*    -> ymz-api-broker:4191 (prefix stripped); only the store
#                      host's egress IP + a shared-secret header may pass.
# /_ymz/* (other)   -> 403.
# ═══════════════════════════════════════════════════════════════════════════
api.yunmengze.xyz {
	encode zstd gzip

	header {
		# Remove upstream/fingerprint headers (Server, NewAPI version, Via).
		-Server
		-X-New-Api-Version
		-Via
		Strict-Transport-Security "max-age=31536000; includeSubDomains"
		X-Content-Type-Options "nosniff"
		X-Frame-Options "DENY"
		Referrer-Policy "strict-origin-when-cross-origin"
		Permissions-Policy "geolocation=(), microphone=(), camera=(), payment=()"
		Cross-Origin-Opener-Policy "same-origin"
		# The SPA boot script + injected 云梦泽 theme need 'unsafe-inline'; blobs
		# are used for client-side exports. Logto avatars are the only remote
		# image origin; payment redirects are top-level navigations (not forms),
		# but epay may POST a form, hence the pay. origin in form-action.
		Content-Security-Policy "default-src 'self' blob:; img-src 'self' data: blob: https://auth.yunmengze.xyz; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline'; connect-src 'self'; font-src 'self' data:; frame-ancestors 'none'; base-uri 'self'; form-action 'self' https://pay.cgxyserver.xyz"
	}

	# ── internal broker surface (store -> broker) ──────────────────────────
	# handle_path strips the /_ymz/broker prefix, so the broker sees exactly
	# the PLAN §5 contract routes (/healthz, /internal/ensure, ...).
	# The edge enforces the source IP allow-list and that a secret header is
	# present; the broker validates the secret VALUE in constant time (the
	# shared Caddy container cannot hold an env secret without being recreated,
	# which is explicitly forbidden).
	handle_path /_ymz/broker/* {
		# All checks are `handle` blocks on purpose: Caddyfile sorts directives
		# by directive name, while `handle` blocks keep their written order, so
		# the source-IP allow-list is ALWAYS evaluated before anything else.
		@not_from_store not remote_ip 38.92.9.164
		handle @not_from_store {
			respond 403
		}
		# Contract: /healthz is unauthenticated (still source-IP gated) so the
		# store's deploy self-check can probe liveness without holding the secret.
		@healthz path /healthz
		handle @healthz {
			reverse_proxy ymz-api-broker:4191 {
				header_up X-Forwarded-For {remote_host}
				header_up X-Real-IP {remote_host}
			}
		}
		@no_secret not header_regexp X-Ymz-Secret ".{32,}"
		handle @no_secret {
			respond 401
		}
		handle {
			reverse_proxy ymz-api-broker:4191 {
				# Never forward a client-supplied forwarding chain.
				header_up X-Forwarded-For {remote_host}
				header_up X-Real-IP {remote_host}
			}
		}
	}
	handle /_ymz/* {
		respond 403
	}

	# ── everything else: brand sidecar (SPA + /api + /v1 + SSE) ────────────
	handle {
		reverse_proxy ymz-api-brand:8080 {
			# Streaming relays must not be buffered / timed out by the edge.
			flush_interval -1
			header_up X-Forwarded-For {remote_host}
			header_up X-Real-IP {remote_host}
		}
	}

	log {
		output stdout
		format console
	}
}
