# TLS — Let's Encrypt (OVH DNS-01) + host nginx ingress

Production serves real, publicly-trusted certs via the **host's own nginx**.
nginx terminates TLS and reverse-proxies every vhost to the caddy container
(published localhost-only on `${CADDY_HTTP_PORT}`) by Host header. caddy is the
internal router; nginx is the public TLS terminator. This is the only ingress
path — there are no compose overrides.

## Certs: one per name, via OVH DNS-01

`tooling/issue-letsencrypt.sh` issues a **separate** cert for the apex and each
subdomain (`${BASE_DOMAIN}`, `auth.…`, `pack.…`, plus the rest of
`LE_SUBDOMAINS`) using acme.sh + the OVH DNS-01 challenge — no inbound ports
needed, so it works regardless of public reachability.

### One-time setup

1. Install acme.sh:
   ```bash
   curl https://get.acme.sh | sh -s email=you@example.com
   ```
2. OVH API credentials → `.env` (see `.env.example` `OVH_*`, `LE_EMAIL`,
   `LE_SUBDOMAINS`). Create a token at <https://eu.api.ovh.com/createToken/> with
   `GET/POST/PUT/DELETE` on `/domain/zone/*`, or leave `OVH_CK` blank and run the
   script once — acme.sh prints an authorization URL; visit it, then paste the
   consumer key into `OVH_CK` and re-run.

### Issue

```bash
tooling/issue-letsencrypt.sh               # real certs
LE_STAGING=1 tooling/issue-letsencrypt.sh  # dry run against the LE staging CA
```

Certs install to `certs/<name>/{cert.pem,key.pem}` (gitignored). acme.sh adds a
cron for renewal; `--reloadcmd` reloads nginx automatically on renew. Override
the reload with `RELOAD_CMD=...` if nginx isn't systemd-managed.

## nginx

1. Bring the stack up (caddy publishes on `127.0.0.1:${CADDY_HTTP_PORT}`):
   ```bash
   docker compose up -d --build
   ```
2. Render + install the vhost (`nginx/ulicraft-caddy.conf.tmpl`):
   ```bash
   tooling/render-nginx.sh --install
   ```
   This substitutes `$BASE_DOMAIN $APP_DIR $CADDY_HTTP_PORT`, writes to
   `sites-available`, symlinks `sites-enabled`, runs `nginx -t`, and reloads
   nginx. nginx then terminates TLS per vhost (certs under `$APP_DIR/certs`) and
   reverse-proxies each Host to caddy on `127.0.0.1:${CADDY_HTTP_PORT}`. Ensure
   `www-data` can read `$APP_DIR/certs`.

## DNS

Point the public DNS for `${BASE_DOMAIN}` and every subdomain
(`auth.` `pack.` `avatar.` `status.` `distribution.` `www.`) at the host running
nginx. DNS is configured **outside this repo**. The Minecraft container reaches
the stack's own names via caddy's `mcnet` aliases, not public DNS.

## Why DNS-01 (not HTTP-01)

DNS-01 only needs the OVH API, so it issues regardless of public reachability —
and `ulicraft.net` is a real OVH-managed domain, which LE requires (it never
issues for `.lan`/`.local`). HTTP-01/TLS-ALPN-01 would need public inbound on
80/443. The JVM trusts the resulting Let's Encrypt certs with no CA import.
