feat: add tls.acme mode — nginx inside container manages Let's Encrypt

per mara's suggestion: instead of bind-mounting operator certs, let
nginx handle ACME directly inside the gateway container.

- tls.acme.enable: lets nginx obtain + renew via HTTP-01 challenge
- tls.acme.email: ACME account contact (required when enable=true)
- security.acme in container config when acme.enable
- hasTls includes acme.enable → https, httpsPort listen, firewall
- mutual exclusion assertions: acme vs selfSignedTls vs certDir
- docs/gateway.md: four-mode TLS table + ACME section

typical setup:
  selfSignedTls = false; openFirewall = true;
  tls.acme = { enable = true; email = "admin@example.com"; };
This commit is contained in:
atlas 2026-06-03 16:42:41 +02:00 committed by mara
commit fb93cbf5c2
2 changed files with 135 additions and 22 deletions

View file

@ -160,13 +160,37 @@ frontend-side derivation.
## TLS modes
Three modes:
Four modes:
| mode | config | cert source | `.well-known` scheme |
|---|---|---|---|
| self-signed (default) | `selfSignedTls = true` | auto-generated RSA-4096, 10-year | `https` |
| ACME (Let's Encrypt) | `selfSignedTls = false` + `tls.acme.enable = true` | nginx inside container via HTTP-01 | `https` |
| operator cert | `selfSignedTls = false` + `tls.certDir` set | bind-mounted from host | `https` |
| http-only | `selfSignedTls = false`, `tls.certDir = null` | none | `http` |
| http-only | `selfSignedTls = false`, no `tls.certDir`, no `tls.acme` | none | `http` |
### ACME / Let's Encrypt (`tls.acme`)
Simplest production path for operators with a public domain:
```nix
services.hyperhive.gateway = {
selfSignedTls = false;
openFirewall = true;
tls.acme = {
enable = true;
email = "admin@example.com";
};
};
```
nginx inside the gateway container obtains and auto-renews certs via the ACME HTTP-01 challenge on `port` (default 80). The gateway container shares the host network namespace (`privateNetwork = false`) so outbound ACME requests work without any extra routing. Certs are stored inside the container's persistent state dir (`/var/lib/acme/` inside `hive-gateway`; survives restarts because `ephemeral = false`).
**Requirements**: `services.hyperhive.domain` must be publicly DNS-resolvable to this host, and `openFirewall = true` so Let's Encrypt can reach `/.well-known/acme-challenge/`. Each active vhost (main domain, `forge.<domain>`, `matrix.<domain>`) gets its own cert via separate ACME challenges.
Mutual exclusion: `selfSignedTls = true` or `tls.certDir` set together with `tls.acme.enable = true` fails an assertion.
**Swarm peers**: CA-signed certs are trusted by default — remote hives need no `certFingerprint` in `swarm.peers`.
### Self-signed TLS (`selfSignedTls`)