Run the Caddy TLS ingress as a Docker container

Caddy previously ran as a host binary in /opt/caddy with a wgui-caddy
systemd/OpenRC unit. Only Caddy is containerized; the app still runs on the
host as root because it shells out to wg, ip and iptables.

- Add caddy-container-commands.ts as the single source of truth for the
  run/exec/validate/inspect argument arrays. Every value stays a separate
  argv element, the image is pinned to caddy:2.11.4-alpine, and the
  entrypoint is set explicitly because some mirrors publish the image with
  a null Entrypoint (which made Docker exec "run" as a binary).
- Rewrite install-caddy.sh: require_docker with distro-specific hints,
  pull + recreate the container, assert it is running and echo the daemon
  error instead of discarding it, normalize the admin socket path in
  existing Caddyfiles, and clean up a legacy native install only after the
  container is up so the ingress port is never unbound. Add
  --purge-legacy / --purge for hosts that want to drop the old install
  without installing the container.
- Move the admin socket to /var/lib/wgui/caddy/admin.sock so it lives
  inside the mounted Caddy dir and reloads work over docker exec.
- Validate candidate configs in a throwaway container, mounting the TLS
  dir read-only as well: provisioning the tls app loads every certificate
  referenced by a tls directive, so without that mount a route with a
  certificate failed validation and its Caddyfile was never committed.
- Report docker-based installed/version/service_active, skip
  validate/reload with a warning when the image is absent, and update the
  system setup tool check to look for docker.
- Issue certificates with a docker exec reload command and chown files to
  root:root, since the container reads them through a read-only mount.
- Update the Setup UI copy and docs (README, AGENTS.md).

Also drops the unused EmptyContent import and a stray debug log in the
client route form, and stops passing the unsupported cancel_href prop to
ClientRouteForm from the route add/edit pages.
This commit is contained in:
2026-10-21 14:34:16 +01:00
parent 2cc876b062
commit cbd5451ab7
22 changed files with 652 additions and 318 deletions
+33 -7
View File
@@ -49,9 +49,9 @@ The install script will:
5. Install app dependencies (`bun install`)
6. Generate `.env` with encryption secrets
7. Run WireGuard setup (if available)
8. Install Caddy (`/opt/caddy`)
8. Install Docker (if missing) and run Caddy as the `wgui-caddy` container
9. Install the `wg-quick` helper script
10. Set up systemd/OpenRC services for `wgui`, `wgui-caddy`, and certificate renewal
10. Set up systemd/OpenRC services for `wgui` and certificate renewal
The installer **never modifies your firewall** — it will print the commands to
temporarily allow the dashboard port so you can reach the first login, and then
@@ -86,13 +86,16 @@ Firewall rules are managed per host, automatically:
### TLS for Tunnel Services
Client routes expose internal services to WireGuard clients over HTTPS. The
data plane is a dedicated Caddy service (`wgui-caddy`) on `8443`; wg-ui owns
the route and certificate records and regenerates the Caddyfile. Caddy never
binds ports 80 or 443, and the host's existing proxy is left alone.
data plane is a dedicated Caddy container (`wgui-caddy`, host networking) on
`8443`; wg-ui owns the route and certificate records and regenerates the
Caddyfile. Caddy never binds ports 80 or 443, and the host's existing proxy is
left alone. Docker is required — the installer starts the daemon if it is not
running.
1. If Caddy isn't installed yet, open **Setup → TLS ingress** and click
**Install Caddy** (or use the Caddy action on **Setup → Maintenance**).
The installer sets up the binary, the `wgui-caddy` service and certificate support.
The installer pulls `caddy:2.11.4-alpine`, (re)creates the `wgui-caddy`
container, and sets up certificate support.
2. Open a route form from **Routes** (or a host's **Routes** panel) and set
the ACME email and staging toggle. No DNS provider token is needed:
@@ -117,7 +120,29 @@ binds ports 80 or 443, and the host's existing proxy is left alone.
Manual DNS-01 certificates are renewed from the route page because validation
needs your registrar. The route list shows expiry warnings. A successful
issuance reloads Caddy without restarting wg-ui. Check
`journalctl -u wgui-caddy` for reload failures.
`docker logs wgui-caddy` for reload failures.
#### Upgrading from the old native Caddy install
Earlier versions ran Caddy as a host binary in `/opt/caddy` with a `wgui-caddy`
systemd/OpenRC service. Re-running the installer migrates the host in place: it
pulls the image, starts the container, and only then removes the old service
and binary. The Caddyfile is rewritten to use the new admin socket path
(`/var/lib/wgui/caddy/admin.sock`); certificates in `/var/lib/wgui/tls` are
untouched.
To remove a leftover native install without installing the container:
```bash
sudo bash src/scripts/install-caddy.sh --purge-legacy
```
Add `--purge` to also remove the container, the image and the certificate
renewal job (the Caddyfile and certificates are always kept):
```bash
sudo bash src/scripts/install-caddy.sh --purge-legacy --purge
```
### Manual (Development)
@@ -142,6 +167,7 @@ The `.env` file (gitignored) controls runtime behavior. It is auto-generated dur
| `ENCRYPTION_KEY` | Encryption key for sensitive data | Auto-generated |
| `ENCRYPTION_SALT` | Encryption salt for sensitive data | Auto-generated |
| `DATA_DIR` | Application data directory | `$INSTALL_DIR/.data` |
| `CADDY_VERSION` | Caddy image tag (`caddy:<version>-alpine`)| `2.11.4` |
| `CADDY_HTTPS_PORT`| Caddy ingress HTTPS port (never 80/443) | `8443` |
| `CADDY_HTTP_PORT` | Caddy plain HTTP port (never 80) | `8081` |
| `CADDY_INGRESS_ENABLED` | Set to `0` to skip Caddy config sync | `1` |