{
        # Five seconds to finish what is in flight when told to stop, then the
        # connections are closed. Caddy's default is to wait for every one, and
        # this server proxies the live-update stream, which never ends by
        # itself: with one browser tab open, "Restart now" in the admin panel
        # stopped PHP and then sat there, neither serving nor exiting, until
        # the tab was closed — and compose cannot restart a container that has
        # not exited. `docker stop` only ever got out of it by killing the
        # process after its own timeout. The hub has the same five seconds for
        # the same reason (BackgroundProcesses); browsers reconnect.
        grace_period 5s

        {$CADDY_GLOBAL_OPTIONS}
        frankenphp {
                {$FRANKENPHP_CONFIG}
        }
}
{$CADDY_EXTRA_CONFIG}
{$SERVER_NAME:localhost} {
        log {
                format filter {
                        wrap console
                        fields {
                                uri query {
                                        replace authorization REDACTED
                                }
                        }
                }
        }

        root public/

        encode zstd br gzip

        # The headers a plMail install gets whether or not anyone put a proxy in
        # front of it.
        #
        # None of these existed anywhere in the repository — not here, not in
        # config/, not in a listener — so the ones a running instance showed
        # were its operator's reverse proxy, and the README's own install
        # instructions are `docker compose up -d` and "open https://localhost".
        # An instance set up the documented way ran with no clickjacking
        # defence, no HSTS and a full Referer going out to every link a mail
        # points at. The operator had nothing telling them to fix that.
        #
        # `defer` so an application response that sets one of these itself wins:
        # the attachment and image-proxy routes send their own, stricter,
        # per-response policies and must not have them overwritten from here.
        header {
                defer

                # Two years, and includeSubDomains, is the usual answer. It is
                # only sent over HTTPS — Caddy omits it on plaintext — so an
                # instance reached over http on a LAN is unaffected rather than
                # locked out of its own hostname.
                Strict-Transport-Security "max-age=63072000; includeSubDomains"

                # SAMEORIGIN rather than DENY: the reading frame is our own
                # document embedding our own route, and DENY would break it.
                X-Frame-Options "SAMEORIGIN"
                X-Content-Type-Options "nosniff"

                # A mail client sends its users to other people's links all day.
                # Without this the full URL of the page they were on goes with
                # them; with it, the origin does, and only to https.
                Referrer-Policy "strict-origin-when-cross-origin"

                # Nothing here asks for any of them, and a compromised page
                # should not be able to start.
                Permissions-Policy "geolocation=(), microphone=(), camera=(), payment=(), usb=(), interest-cohort=()"

                # The version banner. Not a vulnerability, just free
                # reconnaissance.
                -Server
        }

        # Mercure hub — proxied to the container that runs it. `mercure` is the
        # name plMail's own compose files give it; MERCURE_UPSTREAM is for a
        # stack that names its containers by rules of its own.
        #
        # The subscriber cookie loses its `__Secure-` prefix on the way. The
        # browser holds the prefixed one on an https install and the bare one
        # otherwise, and which it is follows the public address, which can
        # change under a running hub. So the hub always reads the bare name
        # and this is where the two are made the same; see BackgroundProcesses.
        handle /.well-known/mercure* {
                reverse_proxy {$MERCURE_UPSTREAM:mercure:80} {
                        header_up Cookie "__Secure-mercure_access_token=" "mercure_access_token="
                }
        }

        php_server {
                try_files {path} index.php
        }
}
