From cbd5451ab7bda6a4eb8eb9fd2a07cc65fc169f20 Mon Sep 17 00:00:00 2001 From: Benjamin Toby Date: Wed, 21 Oct 2026 14:34:16 +0100 Subject: [PATCH] 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. --- AGENTS.md | 4 +- README.md | 40 +- .../general/client-route-form/index.tsx | 44 +-- .../acme/fix-certificate-permissions.ts | 8 +- .../backend/acme/issue-certificate.ts | 12 +- .../backend/caddy/build-caddyfile.test.ts | 2 +- .../caddy/caddy-container-commands.test.ts | 116 ++++++ .../backend/caddy/caddy-container-commands.ts | 141 +++++++ .../backend/caddy/caddy-image-installed.ts | 20 + .../backend/caddy/grab-caddy-status.ts | 66 ++-- .../backend/caddy/reload-caddy-service.ts | 77 ++-- .../backend/caddy/sync-caddy-ingress.ts | 31 +- .../backend/setup/grab-system-setup-status.ts | 6 +- .../hosts/[host_id]/routes/add/index.tsx | 5 +- .../routes/edit/[route_id]/index.tsx | 1 - .../setup/(partials)/setup-caddy-action.tsx | 4 +- .../(sections)/setup-actions-section.tsx | 2 +- .../tls/(sections)/tls-status-section.tsx | 14 +- src/pages/api/admin/install-caddy.ts | 8 +- src/scripts/install-caddy.sh | 349 +++++++++++------- src/scripts/install-wg-ui.sh | 9 +- src/utils/grab-dir-names.ts | 11 +- 22 files changed, 652 insertions(+), 318 deletions(-) create mode 100644 src/functions/backend/caddy/caddy-container-commands.test.ts create mode 100644 src/functions/backend/caddy/caddy-container-commands.ts create mode 100644 src/functions/backend/caddy/caddy-image-installed.ts diff --git a/AGENTS.md b/AGENTS.md index ada69a8..083fa1e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -26,7 +26,7 @@ When building, refactoring, styling, or auditing UI components and layouts, acti ## Entrypoint -`src/server.ts` — runs `bunextInit()`, starts `Bun.serve` on `0.0.0.0` (port from `SiteData.ServerPort`), serves `/media/*` via `handleMediaServer`, then runs `syncWireguardHosts()` to bring host tunnels in line with the DB. +`src/server.ts` — runs `bunextInit()`, starts `Bun.serve` on `0.0.0.0` (port from `SiteData.ServerPort`), serves `/media/*` via `handleMediaServer`, then runs `syncWireguardHosts()` to bring host tunnels in line with the DB. Caddy's data plane runs as the `wgui-caddy` Docker container (host networking, `/var/lib/wgui/caddy` and `/var/lib/wgui/tls` bind-mounted); reloads go through `docker exec`, and the admin socket lives at `/var/lib/wgui/caddy/admin.sock`. On-disk state: WireGuard keys/configs/iptables scripts live under `/var/lib/wgui` (`grabDirNames()` in `src/utils/grab-dir-names.ts`); uploaded media under `DATA_DIR` (default `./.data`). @@ -82,7 +82,7 @@ Server functions use `index.server.ts` alongside `index.tsx` (e.g. `single-docum - **`src/hooks/`** — `use-admin-crud-get` (with `refetch`), `use-client-rules-map`, `use-confirm`, `use-toast`, `use-theme`, `use-fetch-get`, `use-form-init`, `use-users-get`. - **`src/utils/`** — Cookies, crypto, dir names, IP/CIDR/port/interface validators, WireGuard interface naming. Most have `.test.ts` files. - **`src/dict/`**, **`src/data/`** — Constants (`app-data.ts`, `site-data.ts`), user types, client rules, links. -- **`src/scripts/`** — Shell scripts: `install-wg-ui.sh`, `install-caddy.sh` (idempotent Caddy/acme ingress installer used by the UI and the full installer), `setup-wireguard.sh`, `wg-quick-manage.sh`. +- **`src/scripts/`** — Shell scripts: `install-wg-ui.sh`, `install-caddy.sh` (idempotent installer that pulls the `caddy:-alpine` image and (re)creates the `wgui-caddy` Docker container plus acme.sh renewal; used by the UI and the full installer), `setup-wireguard.sh`, `wg-quick-manage.sh`. - **`src/components/general/`** — Shared app components, all built from twui. Reach for these before writing page markup: - Page frame: `page-header` (title, meta, primary action + overflow menu), `page-body`, `panel` (the one card style), `form-section`, `form-actions` (submit is always rendered). - Text & status: `muted-text`, `mono-text` (IPs, keys, paths), `section-label`, `status-dot`, `inline-alert`, `empty-state`, `stat-tile`, `key-value-list`, `copy-button`, `copyable-value`. diff --git a/README.md b/README.md index dc0c42c..3689503 100644 --- a/README.md +++ b/README.md @@ -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:-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` | diff --git a/src/components/general/client-route-form/index.tsx b/src/components/general/client-route-form/index.tsx index 7966ba3..5b2b67e 100644 --- a/src/components/general/client-route-form/index.tsx +++ b/src/components/general/client-route-form/index.tsx @@ -20,7 +20,6 @@ import { AppContext } from "@/src/pages/__root"; import { useAdminCrudGet } from "@/src/hooks/use-admin-crud-get"; import Select from "../../twui/form/Select"; import adminCrudHandler from "@/src/functions/frontend/admin-crud-handler"; -import EmptyContent from "../../twui/elements/EmptyContent"; import twuiNumberfy from "../../twui/utils/numberfy"; type Props = { @@ -36,7 +35,9 @@ export default function ClientRouteForm({ const { user, pageProps } = useContext(AppContext); const [host_id, set_host_id] = useState( - passed_host_id || pageProps?.host?.id || pageProps?.hosts?.[0]?.id, + typeof passed_host_id == "number" + ? passed_host_id + : pageProps?.host?.id || pageProps?.hosts?.[0]?.id, ); const is_host_id_present = typeof host_id == "number"; @@ -65,12 +66,6 @@ export default function ClientRouteForm({ return ; } - if (!pageProps?.hosts?.[0]) { - return ( - - ); - } - const cancel_href = is_host_id_present ? `/admin/hosts/${host_id}/routes` : `/admin/routes`; @@ -101,20 +96,25 @@ export default function ClientRouteForm({ /> ) : null} - - ({ + value: String(h.id), + default: h.id == host_id, + title: h.name, + })) || [] + } + changeHandler={(value) => { + set_host_id(twuiNumberfy(value)); + }} + /> + + ) : null} { + test("pins the container name and image", () => { + expect(CADDY_CONTAINER_NAME).toBe("wgui-caddy"); + expect(CADDY_IMAGE).toBe( + `caddy:${process.env.CADDY_VERSION || "2.11.4"}-alpine`, + ); + }); + + test("runs with host networking and mounts the caddy dir and certs", () => { + const args = grabCaddyRunArgs(); + + expect(args.slice(0, 3)).toEqual(["run", "-d", "--name"]); + expect(args).toContain("--network"); + expect(args[args.indexOf("--network") + 1]).toBe("host"); + expect(args).toContain(`${WGUI_LIB_CADDY_DIR}:${WGUI_LIB_CADDY_DIR}`); + expect(args).toContain(`${WGUI_LIB_TLS_DIR}:${WGUI_LIB_TLS_DIR}:ro`); + expect(args).toContain("--restart"); + expect(args[args.indexOf("--restart") + 1]).toBe("unless-stopped"); + expect(args).toContain(CADDY_IMAGE); + // The entrypoint must be explicit: mirrors exist that publish the image + // with a null Entrypoint, which would exec "run" as a binary. + expect(args).toContain("--entrypoint"); + expect(args[args.indexOf("--entrypoint") + 1]).toBe("caddy"); + expect(args[args.indexOf("--entrypoint") + 2]).toBe(CADDY_IMAGE); + expect(args.slice(-5)).toEqual([ + "run", + "--config", + WGUI_LIB_CADDY_CONFIG_FILE, + "--adapter", + "caddyfile", + ]); + }); + + test("reloads through docker exec with the unix admin socket", () => { + const args = grabCaddyReloadArgs(); + + expect(args.slice(0, 4)).toEqual([ + "exec", + CADDY_CONTAINER_NAME, + "caddy", + "reload", + ]); + expect(args).toContain("--config"); + expect(args[args.indexOf("--config") + 1]).toBe( + WGUI_LIB_CADDY_CONFIG_FILE, + ); + expect(args[args.indexOf("--address") + 1]).toBe( + `unix/${WGUI_LIB_CADDY_DIR}/admin.sock`, + ); + }); + + test("restart fallback targets the container", () => { + expect(grabCaddyRestartArgs()).toEqual([ + "restart", + CADDY_CONTAINER_NAME, + ]); + }); + + test("validates in a throwaway container without networking", () => { + const config_path = `${WGUI_LIB_CADDY_DIR}/Caddyfile.tmp`; + const args = grabCaddyValidateArgs({ config_path }); + + expect(args.slice(0, 2)).toEqual(["run", "--rm"]); + expect(args).toContain("--network"); + expect(args[args.indexOf("--network") + 1]).toBe("none"); + expect(args).toContain("--entrypoint"); + expect(args[args.indexOf("--entrypoint") + 1]).toBe("caddy"); + expect(args).toContain(`${WGUI_LIB_CADDY_DIR}:${WGUI_LIB_CADDY_DIR}`); + // validate provisions the tls app, so route certificates must be + // visible or every config with a `tls` directive fails validation. + expect(args).toContain(`${WGUI_LIB_TLS_DIR}:${WGUI_LIB_TLS_DIR}:ro`); + expect(args).toContain(CADDY_IMAGE); + expect(args.slice(-5)).toEqual([ + "validate", + "--config", + config_path, + "--adapter", + "caddyfile", + ]); + }); + + test("inspect reads the running flag", () => { + expect(grabCaddyInspectArgs()).toEqual([ + "inspect", + "--format", + "{{.State.Running}}", + CADDY_CONTAINER_NAME, + ]); + }); + + test("the acme reload command mirrors the exec args", () => { + const command = grabCaddyReloadCommandString(); + + expect(command).toBe( + ["docker", ...grabCaddyReloadArgs()].join(" "), + ); + expect(command).not.toContain("systemctl"); + expect(command).not.toContain("rc-service"); + }); +}); diff --git a/src/functions/backend/caddy/caddy-container-commands.ts b/src/functions/backend/caddy/caddy-container-commands.ts new file mode 100644 index 0000000..dd4fd9a --- /dev/null +++ b/src/functions/backend/caddy/caddy-container-commands.ts @@ -0,0 +1,141 @@ +import grabDirNames from "@/src/utils/grab-dir-names"; + +/** + * Caddy runs as a Docker container instead of a host binary. Every argument is + * kept as its own array element — nothing here is interpolated into a shell + * string, except `grabCaddyReloadCommandString()` which is handed to acme.sh. + * + * The container uses host networking and mounts the Caddy state directory at + * the exact same path as the host, so the paths below are valid on both sides. + */ + +const { + WGUI_LIB_CADDY_DIR, + WGUI_LIB_CADDY_CONFIG_FILE, + WGUI_LIB_CADDY_ADMIN_SOCKET, + WGUI_LIB_TLS_DIR, +} = grabDirNames(); + +export const CADDY_CONTAINER_NAME = `wgui-caddy`; +export const CADDY_VERSION = process.env.CADDY_VERSION || `2.11.4`; +export const CADDY_IMAGE = `caddy:${CADDY_VERSION}-alpine`; + +export const CADDY_CONFIG_FILE_IN_CONTAINER = WGUI_LIB_CADDY_CONFIG_FILE; +export const CADDY_ADMIN_SOCKET_IN_CONTAINER = WGUI_LIB_CADDY_ADMIN_SOCKET; + +function volumeArgs() { + return [ + `-v`, + `${WGUI_LIB_CADDY_DIR}:${WGUI_LIB_CADDY_DIR}`, + `-v`, + `${WGUI_LIB_TLS_DIR}:${WGUI_LIB_TLS_DIR}:ro`, + ]; +} + +export function grabCaddyRunArgs() { + return [ + `run`, + `-d`, + `--name`, + CADDY_CONTAINER_NAME, + `--restart`, + `unless-stopped`, + `--network`, + `host`, + ...volumeArgs(), + // Set the entrypoint explicitly instead of trusting the image metadata: + // some mirrors publish the image with a null Entrypoint, which would + // make Docker exec the first arg ("run") as a binary. + `--entrypoint`, + `caddy`, + CADDY_IMAGE, + `run`, + `--config`, + CADDY_CONFIG_FILE_IN_CONTAINER, + `--adapter`, + `caddyfile`, + ]; +} + +export function grabCaddyReloadArgs() { + return [ + `exec`, + CADDY_CONTAINER_NAME, + `caddy`, + `reload`, + `--config`, + CADDY_CONFIG_FILE_IN_CONTAINER, + `--adapter`, + `caddyfile`, + `--address`, + `unix/${CADDY_ADMIN_SOCKET_IN_CONTAINER}`, + ]; +} + +export function grabCaddyRestartArgs() { + return [`restart`, CADDY_CONTAINER_NAME]; +} + +/** + * Validate a candidate Caddyfile in a throwaway container so a bad config can + * never take the running ingress down. The config must live inside the mounted + * Caddy directory for the container to see it. + * + * The TLS directory is mounted too: `caddy validate` provisions the tls app, + * which loads every certificate referenced by a `tls` directive, and a route + * whose certificate file is missing makes validation fail. + */ +export function grabCaddyValidateArgs({ config_path }: { config_path: string }) { + return [ + `run`, + `--rm`, + `--network`, + `none`, + `--entrypoint`, + `caddy`, + ...volumeArgs(), + CADDY_IMAGE, + `validate`, + `--config`, + config_path, + `--adapter`, + `caddyfile`, + ]; +} + +export function grabCaddyInspectArgs() { + return [ + `inspect`, + `--format`, + `{{.State.Running}}`, + CADDY_CONTAINER_NAME, + ]; +} + +export function grabCaddyImageInspectArgs() { + return [`image`, `inspect`, CADDY_IMAGE]; +} + +export function grabCaddyExecVersionArgs() { + return [`exec`, CADDY_CONTAINER_NAME, `caddy`, `version`]; +} + +/** + * acme.sh runs `--reloadcmd` through a shell, so this one is a string. All + * values come from the constants above (no user input). + */ +export function grabCaddyReloadCommandString() { + return [ + `docker`, + `exec`, + CADDY_CONTAINER_NAME, + `caddy`, + `reload`, + `--config`, + CADDY_CONFIG_FILE_IN_CONTAINER, + `--adapter`, + `caddyfile`, + `--address`, + `unix/${CADDY_ADMIN_SOCKET_IN_CONTAINER}`, + ].join(` `); +} diff --git a/src/functions/backend/caddy/caddy-image-installed.ts b/src/functions/backend/caddy/caddy-image-installed.ts new file mode 100644 index 0000000..449233e --- /dev/null +++ b/src/functions/backend/caddy/caddy-image-installed.ts @@ -0,0 +1,20 @@ +import { execFileSync } from "node:child_process"; +import { grabCaddyImageInspectArgs } from "./caddy-container-commands"; + +/** + * True when the Caddy image has been pulled. Until then the generated config + * cannot be validated or served (e.g. development machines without Docker). + */ +export default function caddyImageInstalled() { + try { + execFileSync("docker", grabCaddyImageInspectArgs(), { + encoding: "utf-8", + stdio: "pipe", + timeout: 5000, + }); + + return true; + } catch { + return false; + } +} diff --git a/src/functions/backend/caddy/grab-caddy-status.ts b/src/functions/backend/caddy/grab-caddy-status.ts index a5600b0..3379fc6 100644 --- a/src/functions/backend/caddy/grab-caddy-status.ts +++ b/src/functions/backend/caddy/grab-caddy-status.ts @@ -2,10 +2,15 @@ import { execFileSync } from "node:child_process"; import { existsSync, readFileSync } from "node:fs"; import grabDirNames from "@/src/utils/grab-dir-names"; import { TlsData } from "@/src/data/tls-data"; -import grabSystemSetupStatus from "@/src/functions/backend/setup/grab-system-setup-status"; +import caddyImageInstalled from "./caddy-image-installed"; +import { + CADDY_CONTAINER_NAME, + CADDY_IMAGE, + grabCaddyExecVersionArgs, + grabCaddyInspectArgs, +} from "./caddy-container-commands"; const { - WGUI_CADDY_BINARY, WGUI_LIB_CADDY_CONFIG_FILE, WGUI_LIB_CADDY_STATE_FILE, WGUI_ACME_SCRIPT, @@ -30,9 +35,9 @@ function readState() { } } -function version() { +function docker({ args }: { args: string[] }) { try { - return execFileSync(WGUI_CADDY_BINARY, ["version"], { + return execFileSync("docker", args, { encoding: "utf-8", stdio: "pipe", timeout: 5000, @@ -42,38 +47,28 @@ function version() { } } -function serviceActive() { - const setup_status = grabSystemSetupStatus(); +function version() { + const running = docker({ args: grabCaddyExecVersionArgs() }); - try { - if (setup_status.init_system == "systemd") { - return ( - execFileSync( - "systemctl", - ["is-active", "wgui-caddy"], - { - encoding: "utf-8", - stdio: "pipe", - timeout: 5000, - }, - ).trim() == "active" - ); - } - - if (setup_status.init_system == "openrc") { - execFileSync("rc-service", ["wgui-caddy", "status"], { - encoding: "utf-8", - stdio: "pipe", - timeout: 5000, - }); - - return true; - } - } catch { - return false; + if (running) { + return running; } - return null; + if (!caddyImageInstalled()) { + return null; + } + + return CADDY_IMAGE; +} + +function serviceActive() { + const running = docker({ args: grabCaddyInspectArgs() }); + + if (running == null) { + return null; + } + + return running == "true"; } type Settings = { @@ -85,7 +80,10 @@ export default function grabCaddyStatus(settings?: Settings) { const state = readState(); return { - installed: existsSync(WGUI_CADDY_BINARY), + runtime: "docker" as const, + container_name: CADDY_CONTAINER_NAME, + image: CADDY_IMAGE, + installed: caddyImageInstalled(), service_active: serviceActive(), acme_installed: existsSync(WGUI_ACME_SCRIPT), acme_env_exists: existsSync(WGUI_ACME_ENV_FILE), diff --git a/src/functions/backend/caddy/reload-caddy-service.ts b/src/functions/backend/caddy/reload-caddy-service.ts index 56d46fa..bd37761 100644 --- a/src/functions/backend/caddy/reload-caddy-service.ts +++ b/src/functions/backend/caddy/reload-caddy-service.ts @@ -1,11 +1,12 @@ import { execFileSync } from "node:child_process"; -import grabSystemSetupStatus from "@/src/functions/backend/setup/grab-system-setup-status"; +import { + grabCaddyReloadArgs, + grabCaddyRestartArgs, +} from "./caddy-container-commands"; -const CADDY_SERVICE_NAME = "wgui-caddy"; - -function run({ command, args }: { command: string; args: string[] }) { +function run({ args }: { args: string[] }) { try { - const output = execFileSync(command, args, { + const output = execFileSync("docker", args, { encoding: "utf-8", stdio: "pipe", timeout: 30000, @@ -29,58 +30,24 @@ function run({ command, args }: { command: string; args: string[] }) { } } -function caddyServiceIsActive({ - init_system, -}: { - init_system: ReturnType["init_system"]; -}) { - if (init_system == "systemd") { - const status = run({ - command: "systemctl", - args: ["is-active", CADDY_SERVICE_NAME], - }); - - return status.success && status.msg == "active"; - } - - if (init_system == "openrc") { - return run({ - command: "rc-service", - args: [CADDY_SERVICE_NAME, "status"], - }).success; - } - - return false; -} - +/** + * Reload the Caddy container's config over the admin socket. If the container + * is missing (e.g. it was recreated or removed by hand), fall back to a restart + * so a fresh container picks up the new Caddyfile. + */ export default function reloadCaddyService() { - const setup_status = grabSystemSetupStatus(); - const service_active = caddyServiceIsActive({ - init_system: setup_status.init_system, - }); + const reload = run({ args: grabCaddyReloadArgs() }); - if (setup_status.is_dev && !service_active) { - return { - success: true as const, - msg: `Development mode — Caddy service is not running, reload skipped`, - }; + if (reload.success) { + return reload; } - switch (setup_status.init_system) { - case "systemd": - return run({ - command: "systemctl", - args: ["reload", CADDY_SERVICE_NAME], - }); - case "openrc": - return run({ - command: "rc-service", - args: [CADDY_SERVICE_NAME, "reload"], - }); - default: - return { - success: false as const, - msg: `No supported init system found for wgui-caddy`, - }; - } + const restart = run({ args: grabCaddyRestartArgs() }); + + return restart.success + ? { + success: true as const, + msg: `Caddy container restarted`, + } + : reload; } diff --git a/src/functions/backend/caddy/sync-caddy-ingress.ts b/src/functions/backend/caddy/sync-caddy-ingress.ts index 8ed45a8..fa95188 100644 --- a/src/functions/backend/caddy/sync-caddy-ingress.ts +++ b/src/functions/backend/caddy/sync-caddy-ingress.ts @@ -18,6 +18,8 @@ import type { TableType } from "@/src/types"; import grabDirNames from "@/src/utils/grab-dir-names"; import { TlsData } from "@/src/data/tls-data"; import buildCaddyfile from "./build-caddyfile"; +import caddyImageInstalled from "./caddy-image-installed"; +import { CADDY_IMAGE, grabCaddyValidateArgs } from "./caddy-container-commands"; import reloadCaddyService from "./reload-caddy-service"; import applyCaddyIngressFirewallRule from "./apply-caddy-ingress-firewall-rule"; import grabCaddySettings from "./grab-caddy-settings"; @@ -33,7 +35,6 @@ type Params = { }; const { - WGUI_CADDY_BINARY, WGUI_LIB_CADDY_DIR, WGUI_LIB_CADDY_CONFIG_FILE, WGUI_LIB_CADDY_STATE_FILE, @@ -155,29 +156,23 @@ async function doSync({ reason }: Params): Promise { writeFileSync(tmp_path, built.config, { mode: 0o644 }); - if (existsSync(WGUI_CADDY_BINARY)) { + // Without the image the Caddyfile can be neither validated nor served — + // write it and let the installer/Setup page take over. + if (caddyImageInstalled()) { try { execFileSync( - WGUI_CADDY_BINARY, - [ - "validate", - "--config", - tmp_path, - "--adapter", - "caddyfile", - ], + "docker", + grabCaddyValidateArgs({ config_path: tmp_path }), { encoding: "utf-8", stdio: "pipe", - timeout: 30000, + timeout: 60000, }, ); } catch (error: any) { rmSync(tmp_path, { force: true }); const msg = `Caddy rejected the generated config: ${ - error?.stderr - ? String(error.stderr).trim() - : error.message + error?.stderr ? String(error.stderr).trim() : error.message }`; writeState({ warnings: built.warnings, last_error: msg }); @@ -188,6 +183,10 @@ async function doSync({ reason }: Params): Promise { warnings: built.warnings, }; } + } else { + built.warnings.push( + `Caddy image ${CADDY_IMAGE} is not installed — config not validated or reloaded`, + ); } if (existsSync(WGUI_LIB_CADDY_CONFIG_FILE)) { @@ -196,7 +195,9 @@ async function doSync({ reason }: Params): Promise { renameSync(tmp_path, WGUI_LIB_CADDY_CONFIG_FILE); - const reload = reloadCaddyService(); + const reload = caddyImageInstalled() + ? reloadCaddyService() + : { success: true as const, msg: `skipped, image not installed` }; const warnings = [...built.warnings]; if (!reload.success) { diff --git a/src/functions/backend/setup/grab-system-setup-status.ts b/src/functions/backend/setup/grab-system-setup-status.ts index 96141e3..ff1b0fe 100644 --- a/src/functions/backend/setup/grab-system-setup-status.ts +++ b/src/functions/backend/setup/grab-system-setup-status.ts @@ -85,9 +85,9 @@ const TOOL_CHECKS: { version_command: "bun --version", }, { - name: "caddy", - command: "/opt/caddy/caddy", - version_command: "/opt/caddy/caddy version", + name: "caddy (docker)", + command: "docker", + version_command: "docker --version", }, ]; diff --git a/src/pages/admin/hosts/[host_id]/routes/add/index.tsx b/src/pages/admin/hosts/[host_id]/routes/add/index.tsx index 0210635..ad6f56e 100644 --- a/src/pages/admin/hosts/[host_id]/routes/add/index.tsx +++ b/src/pages/admin/hosts/[host_id]/routes/add/index.tsx @@ -22,10 +22,7 @@ export default function AddClientRoutePage() { }} /> - + ); diff --git a/src/pages/admin/hosts/[host_id]/routes/edit/[route_id]/index.tsx b/src/pages/admin/hosts/[host_id]/routes/edit/[route_id]/index.tsx index bdd10a2..82899b6 100644 --- a/src/pages/admin/hosts/[host_id]/routes/edit/[route_id]/index.tsx +++ b/src/pages/admin/hosts/[host_id]/routes/edit/[route_id]/index.tsx @@ -28,7 +28,6 @@ export default function EditClientRoutePage() { ) : ( } button_title={installed ? "Repair Caddy" : "Install Caddy"} confirm_params={{ title: installed ? "Repair the Caddy ingress?" : "Install Caddy?", message: - "This installs Caddy and the wgui-caddy service. Existing routes and certificates are preserved.", + "This pulls the Caddy image and restarts the wgui-caddy container. Existing routes and certificates are preserved.", confirm_label: installed ? "Repair Caddy" : "Install Caddy", }} on_run={async () => { diff --git a/src/pages/admin/setup/(sections)/setup-actions-section.tsx b/src/pages/admin/setup/(sections)/setup-actions-section.tsx index c8e4425..e836822 100644 --- a/src/pages/admin/setup/(sections)/setup-actions-section.tsx +++ b/src/pages/admin/setup/(sections)/setup-actions-section.tsx @@ -26,7 +26,7 @@ export default function SetupActionsSection({ setup }: Props) { .every((tool) => tool.installed); const have_caddy = Boolean( - setup_status.tools.find((tool) => tool.command == "/opt/caddy/caddy") + setup_status.tools.find((tool) => tool.name.startsWith("caddy")) ?.installed, ); diff --git a/src/pages/admin/setup/tls/(sections)/tls-status-section.tsx b/src/pages/admin/setup/tls/(sections)/tls-status-section.tsx index f26a1ad..5850317 100644 --- a/src/pages/admin/setup/tls/(sections)/tls-status-section.tsx +++ b/src/pages/admin/setup/tls/(sections)/tls-status-section.tsx @@ -18,7 +18,7 @@ export default function TlsStatusSection({ tls }: Props) { return ( ) : null} {caddy?.state?.last_error ? ( @@ -94,6 +94,14 @@ export default function TlsStatusSection({ tls }: Props) { ? `${caddy.https_port}/tcp` : "—", }, + { + key: "runtime", + label: "Runtime", + value: caddy + ? `docker · ${caddy.container_name} · ${caddy.image}` + : "—", + mono: true, + }, { key: "config", label: "Caddyfile", diff --git a/src/pages/api/admin/install-caddy.ts b/src/pages/api/admin/install-caddy.ts index 0ed6763..ba563ec 100644 --- a/src/pages/api/admin/install-caddy.ts +++ b/src/pages/api/admin/install-caddy.ts @@ -11,12 +11,7 @@ import type { const INSTALL_CADDY_SCRIPT = `install-caddy.sh`; const INSTALL_CADDY_TIMEOUT_MS = 20 * 60 * 1000; -const { - WGUI_LIB_DIR, - WGUI_CADDY_BIN_DIR, - WGUI_ACME_HOME, - WGUI_ACME_ENV_FILE, -} = grabDirNames(); +const { WGUI_LIB_DIR, WGUI_ACME_HOME, WGUI_ACME_ENV_FILE } = grabDirNames(); export const handler: BunextAPIRouteHandler = async ({ req, @@ -37,7 +32,6 @@ export const handler: BunextAPIRouteHandler = async ({ env: { WGUI_LIB_DIR, CADDY_VERSION: process.env.CADDY_VERSION, - CADDY_BIN_DIR: WGUI_CADDY_BIN_DIR, CADDY_HTTPS_PORT: String(TlsData.CaddyHttpsPort), CADDY_HTTP_PORT: String(TlsData.CaddyHttpPort), ACME_HOME: WGUI_ACME_HOME, diff --git a/src/scripts/install-caddy.sh b/src/scripts/install-caddy.sh index 00e0d1a..87c8f76 100755 --- a/src/scripts/install-caddy.sh +++ b/src/scripts/install-caddy.sh @@ -1,6 +1,7 @@ #!/bin/bash # Install or repair the Caddy TLS ingress used by client routes. +# Caddy runs as a Docker container (host networking) instead of a host binary. # Safe to re-run: it preserves the existing Caddyfile and certificate data. set -euo pipefail @@ -10,14 +11,15 @@ fail() { echo "error: $*" >&2; exit 1; } WGUI_LIB_DIR="${WGUI_LIB_DIR:-/var/lib/wgui}" CADDY_VERSION="${CADDY_VERSION:-2.11.4}" -CADDY_BIN_DIR="${CADDY_BIN_DIR:-/opt/caddy}" -CADDY_BIN="$CADDY_BIN_DIR/caddy" -CADDY_USER="${CADDY_USER:-wgui-caddy}" +CADDY_IMAGE="caddy:${CADDY_VERSION}-alpine" +CADDY_CONTAINER_NAME="wgui-caddy" +CADDY_DIR="$WGUI_LIB_DIR/caddy" +CADDY_CONFIG_FILE="$CADDY_DIR/Caddyfile" +CADDY_ADMIN_SOCKET="$CADDY_DIR/admin.sock" CADDY_HTTPS_PORT="${CADDY_HTTPS_PORT:-8443}" CADDY_HTTP_PORT="${CADDY_HTTP_PORT:-8081}" ACME_HOME="${ACME_HOME:-/opt/acme.sh}" ACME_ENV_FILE="${ACME_ENV_FILE:-/etc/wgui/acme.env}" -SERVICE_NAME="wgui-caddy" require_root() { if [ "$(id -u)" -ne 0 ]; then @@ -45,19 +47,32 @@ detect_init_system() { fi } -caddy_arch() { - case "$(uname -m)" in - x86_64 | amd64) echo "amd64" ;; - aarch64 | arm64 | armv8l | armv8) echo "arm64" ;; - armv7l | armv7) echo "armv7" ;; - armv6l | armv6) echo "armv6" ;; - *) fail "unsupported architecture for Caddy: $(uname -m)" ;; +docker_install_hint() { + local distro + distro="$(detect_distro)" + + case "$distro" in + debian | ubuntu | linuxmint | raspbian) + echo "apt-get install -y docker.io" + ;; + fedora | rhel | centos | rocky | almalinux | ol) + echo "dnf install -y docker" + ;; + arch | manjaro | endeavouros) + echo "pacman -S --noconfirm docker" + ;; + alpine) + echo "apk add docker" + ;; + *) + echo "install Docker from https://docs.docker.com/engine/install/" + ;; esac } install_deps() { - local missing=false local tool + local missing=false for tool in curl tar git openssl; do if ! command -v "$tool" >/dev/null 2>&1; then @@ -97,50 +112,36 @@ install_deps() { esac } -install_caddy() { - if [ -x "$CADDY_BIN" ]; then - if [ ! -e /usr/local/bin/caddy ]; then - ln -sf "$CADDY_BIN" /usr/local/bin/caddy - fi - log "caddy: $($CADDY_BIN version)" - return +require_docker() { + if ! command -v docker >/dev/null 2>&1; then + fail "docker is required for the Caddy ingress — install it with: $(docker_install_hint)" fi - local arch - arch="$(caddy_arch)" - local archive="caddy_${CADDY_VERSION}_linux_${arch}.tar.gz" - local base_url="https://github.com/caddyserver/caddy/releases/download/v${CADDY_VERSION}" - local tmp_dir - tmp_dir="$(mktemp -d)" + local init_system + init_system="$(detect_init_system)" - log "installing Caddy ${CADDY_VERSION} (${arch}) to ${CADDY_BIN_DIR} ..." - mkdir -p "$CADDY_BIN_DIR" + if ! docker info >/dev/null 2>&1; then + log "starting the docker daemon ..." - curl -fsSL -o "$tmp_dir/$archive" "$base_url/$archive" - curl -fsSL -o "$tmp_dir/checksums.txt" "$base_url/caddy_${CADDY_VERSION}_checksums.txt" - - if command -v sha512sum >/dev/null 2>&1; then - (cd "$tmp_dir" && grep " $archive\$" checksums.txt | sha512sum -c -) - elif command -v openssl >/dev/null 2>&1; then - local expected actual - expected="$(grep " $archive\$" "$tmp_dir/checksums.txt" | awk '{print $1}')" - actual="$(openssl dgst -sha512 "$tmp_dir/$archive" | awk '{print $NF}')" - - if [ -z "$expected" ] || [ "$expected" != "$actual" ]; then - fail "Caddy checksum verification failed for $archive" - fi - else - fail "no sha512 verification tool available (install coreutils or openssl)" + case "$init_system" in + systemd) + systemctl enable --now docker + ;; + openrc) + rc-update add docker default + rc-service docker start + ;; + *) + fail "docker is installed but not running — start it manually and re-run this script" + ;; + esac fi - tar -xzf "$tmp_dir/$archive" -C "$CADDY_BIN_DIR" caddy - chmod 755 "$CADDY_BIN" - if [ ! -e /usr/local/bin/caddy ]; then - ln -sf "$CADDY_BIN" /usr/local/bin/caddy + if ! docker info >/dev/null 2>&1; then + fail "docker is not running — start it and re-run this script" fi - rm -rf "$tmp_dir" - log "caddy: $($CADDY_BIN version)" + log "docker: $(docker --version)" } install_acme_sh() { @@ -156,29 +157,15 @@ install_acme_sh() { chmod 700 "$ACME_HOME/acme.sh" } -setup_caddy() { - if ! id -u "$CADDY_USER" >/dev/null 2>&1; then - log "creating service user $CADDY_USER ..." - if command -v useradd >/dev/null 2>&1; then - useradd --system --user-group --home-dir "$WGUI_LIB_DIR/caddy" --shell /usr/sbin/nologin "$CADDY_USER" - else - adduser -S -H -h "$WGUI_LIB_DIR/caddy" -s /sbin/nologin "$CADDY_USER" - fi - fi - - if ! id -g "$CADDY_USER" >/dev/null 2>&1 && command -v groupadd >/dev/null 2>&1; then - groupadd --system "$CADDY_USER" - fi - - mkdir -p "$WGUI_LIB_DIR/caddy/data" "$WGUI_LIB_DIR/tls" +setup_caddy_dirs() { + # The official Caddy image runs as root, so the mounted state directory is + # root-owned; nothing else on the host needs access to it. + mkdir -p "$CADDY_DIR/data" "$WGUI_LIB_DIR/tls" /etc/wgui chmod 755 "$WGUI_LIB_DIR" - chown root:"$CADDY_USER" "$WGUI_LIB_DIR/tls" + chmod 750 "$CADDY_DIR" + chmod 750 "$CADDY_DIR/data" chmod 750 "$WGUI_LIB_DIR/tls" - chown -R "$CADDY_USER:$CADDY_USER" "$WGUI_LIB_DIR/caddy" - chmod 750 "$WGUI_LIB_DIR/caddy" - chmod 750 "$WGUI_LIB_DIR/caddy/data" - - mkdir -p /etc/wgui + chown -R root:root "$CADDY_DIR" "$WGUI_LIB_DIR/tls" chmod 700 /etc/wgui if [ ! -f "$ACME_ENV_FILE" ]; then @@ -194,89 +181,134 @@ EOF chown root:root "$ACME_ENV_FILE" fi - if [ ! -f "$WGUI_LIB_DIR/caddy/Caddyfile" ]; then + if [ ! -f "$CADDY_CONFIG_FILE" ]; then log "writing initial Caddyfile ..." - cat > "$WGUI_LIB_DIR/caddy/Caddyfile" < "$CADDY_CONFIG_FILE" < "$unit" < "$tmp" + + chmod 640 "$tmp" + chown root:root "$tmp" + mv "$tmp" "$CADDY_CONFIG_FILE" } -install_openrc_unit() { - local init_script="/etc/init.d/$SERVICE_NAME" - log "writing openrc init script $init_script ..." - cat > "$init_script" </dev/null 2>&1; then + log "removing the existing $CADDY_CONTAINER_NAME container ..." + docker rm -f "$CADDY_CONTAINER_NAME" >/dev/null + fi + + log "starting the $CADDY_CONTAINER_NAME container ..." + local run_output="" + + # The entrypoint is set explicitly: some mirrors publish the image with a + # null Entrypoint, which would make Docker exec the first arg as a binary. + if ! run_output="$(docker run -d \ + --name "$CADDY_CONTAINER_NAME" \ + --restart unless-stopped \ + --network host \ + -v "$CADDY_DIR:$CADDY_DIR" \ + -v "$WGUI_LIB_DIR/tls:$WGUI_LIB_DIR/tls:ro" \ + --entrypoint caddy \ + "$CADDY_IMAGE" \ + run --config "$CADDY_CONFIG_FILE" --adapter caddyfile 2>&1)"; then + fail "could not start the $CADDY_CONTAINER_NAME container: $run_output" + fi + + local running + running="$(docker inspect --format '{{.State.Running}}' "$CADDY_CONTAINER_NAME" 2>/dev/null || echo false)" + + if [ "$running" != "true" ]; then + docker logs --tail 50 "$CADDY_CONTAINER_NAME" >&2 || true + fail "the $CADDY_CONTAINER_NAME container failed to start" + fi + + log "caddy: $(docker exec "$CADDY_CONTAINER_NAME" caddy version 2>/dev/null || echo "$CADDY_IMAGE")" } -reload() { - ebegin "Reloading Caddy" - "$CADDY_BIN" reload --config "$WGUI_LIB_DIR/caddy/Caddyfile" --adapter caddyfile --address unix//run/wgui-caddy/admin.sock - eend \$? -} +# Remove a native Caddy install left over from earlier versions so it does not +# keep holding the ingress ports. +cleanup_legacy_local_install() { + local init_system + init_system="$(detect_init_system)" + local legacy_unit="/etc/systemd/system/wgui-caddy.service" + local legacy_openrc="/etc/init.d/wgui-caddy" -depend() { - need net -} -EOF - chmod +x "$init_script" - rc-update add "$SERVICE_NAME" default - rc-service "$SERVICE_NAME" restart + if [ -f "$legacy_unit" ]; then + log "removing the legacy wgui-caddy systemd unit ..." + systemctl disable --now wgui-caddy >/dev/null 2>&1 || true + rm -f "$legacy_unit" + systemctl daemon-reload >/dev/null 2>&1 || true + fi + + if [ -f "$legacy_openrc" ]; then + log "removing the legacy wgui-caddy init script ..." + rc-service wgui-caddy stop >/dev/null 2>&1 || true + rc-update del wgui-caddy default >/dev/null 2>&1 || true + rm -f "$legacy_openrc" + fi + + if [ -d /opt/caddy ]; then + log "removing the legacy Caddy binary from /opt/caddy ..." + rm -rf /opt/caddy + fi + + if [ -L /usr/local/bin/caddy ]; then + local target + target="$(readlink /usr/local/bin/caddy || echo '')" + + if [ "$target" = "/opt/caddy/caddy" ]; then + rm -f /usr/local/bin/caddy + fi + fi + + # The legacy admin socket lived in /run and would otherwise be stale. + rm -rf /run/wgui-caddy + + if [ "$init_system" = "unknown" ]; then + log "warning: no supported init system found — the container still starts on boot via docker" + fi } install_systemd_timer() { @@ -341,27 +373,66 @@ EOF chmod 755 "$script" } +PURGE_LEGACY=false +PURGE_ALL=false + +for arg in "$@"; do + case "$arg" in + --purge-legacy) PURGE_LEGACY=true ;; + --purge) PURGE_ALL=true ;; + *) fail "unknown argument: $arg" ;; + esac +done + require_root + +# --purge-legacy: remove a leftover native install (binary, service, socket) +# without touching Docker, the Caddyfile or any certificate. +if [ "$PURGE_LEGACY" = true ]; then + log "purging the legacy native Caddy install ..." + cleanup_legacy_local_install + + if [ "$PURGE_ALL" = true ]; then + log "purging the Caddy container, images and renewal job ..." + docker rm -f "$CADDY_CONTAINER_NAME" >/dev/null 2>&1 || true + docker rmi "$CADDY_IMAGE" >/dev/null 2>&1 || true + rm -f /etc/systemd/system/wgui-acme-renew.service \ + /etc/systemd/system/wgui-acme-renew.timer \ + /etc/periodic/daily/wgui-acme-renew \ + /etc/cron.daily/wgui-acme-renew + systemctl daemon-reload >/dev/null 2>&1 || true + log "kept: $CADDY_DIR (Caddyfile + certificates) and $WGUI_LIB_DIR/tls" + fi + + log "legacy Caddy cleanup complete." + exit 0 +fi + install_deps -install_caddy +require_docker install_acme_sh -setup_caddy +setup_caddy_dirs +normalize_caddyfile INIT_SYSTEM="$(detect_init_system)" case "$INIT_SYSTEM" in systemd) - install_systemd_unit install_systemd_timer ;; openrc) - install_openrc_unit install_openrc_timer ;; *) - fail "no supported init system found — start Caddy manually with: $CADDY_BIN run --config $WGUI_LIB_DIR/caddy/Caddyfile --adapter caddyfile" + log "warning: no supported init system found — set up certificate renewal manually" ;; esac -log "Caddy TLS ingress is installed and running on port $CADDY_HTTPS_PORT." +# Start the container before removing the legacy service so the ingress port is +# never left unbound. +install_container +cleanup_legacy_local_install + +log "Caddy TLS ingress container '$CADDY_CONTAINER_NAME' is running on port $CADDY_HTTPS_PORT." +log "Inspect it with: docker logs $CADDY_CONTAINER_NAME" log "Set the ACME email on each route form; certificate issuance uses manual DNS-01 from the route page." diff --git a/src/scripts/install-wg-ui.sh b/src/scripts/install-wg-ui.sh index 2d2b1c0..247a0fd 100755 --- a/src/scripts/install-wg-ui.sh +++ b/src/scripts/install-wg-ui.sh @@ -21,9 +21,6 @@ BUN_INSTALL_DIR="${BUN_INSTALL_DIR:-/opt/bun}" BUN_VERSION="${BUN_VERSION:-1.4.3}" BUN_BIN="/usr/local/bin/bun" CADDY_VERSION="${CADDY_VERSION:-2.11.4}" -CADDY_BIN_DIR="${CADDY_BIN_DIR:-/opt/caddy}" -CADDY_BIN="$CADDY_BIN_DIR/caddy" -CADDY_USER="${CADDY_USER:-wgui-caddy}" CADDY_HTTPS_PORT="${CADDY_HTTPS_PORT:-8443}" CADDY_HTTP_PORT="${CADDY_HTTP_PORT:-8081}" ACME_HOME="${ACME_HOME:-/opt/acme.sh}" @@ -132,11 +129,9 @@ install_caddy_stack() { fail "missing $install_script — cannot install the Caddy TLS ingress" fi - log "installing/repairing the Caddy TLS ingress ..." + log "installing/repairing the Caddy TLS ingress container ..." WGUI_LIB_DIR="$WGUI_LIB_DIR" \ CADDY_VERSION="$CADDY_VERSION" \ - CADDY_BIN_DIR="$CADDY_BIN_DIR" \ - CADDY_USER="$CADDY_USER" \ CADDY_HTTPS_PORT="$CADDY_HTTPS_PORT" \ CADDY_HTTP_PORT="$CADDY_HTTP_PORT" \ ACME_HOME="$ACME_HOME" \ @@ -240,6 +235,6 @@ else fi log "open http://$(hostname -I 2>/dev/null | awk '{print $1}' || echo localhost):$PORT in your browser" if [ "$DEV_MODE" = false ]; then - log "tls ingress: wgui-caddy on https://:$CADDY_HTTPS_PORT (never binds 80/443)" + log "tls ingress: wgui-caddy container on https://:$CADDY_HTTPS_PORT (never binds 80/443); inspect with: docker logs wgui-caddy" log "open a route page to issue certificates with manual DNS-01" fi \ No newline at end of file diff --git a/src/utils/grab-dir-names.ts b/src/utils/grab-dir-names.ts index 5081105..5d0fe26 100644 --- a/src/utils/grab-dir-names.ts +++ b/src/utils/grab-dir-names.ts @@ -45,9 +45,12 @@ export default function grabDirNames(params?: Params) { `state.json`, ); const WGUI_LIB_CADDY_DATA_DIR = path.join(WGUI_LIB_CADDY_DIR, `data`); - const WGUI_LIB_CADDY_ADMIN_SOCKET = `/run/wgui-caddy/admin.sock`; - const WGUI_CADDY_BIN_DIR = `/opt/caddy`; - const WGUI_CADDY_BINARY = path.join(WGUI_CADDY_BIN_DIR, `caddy`); + // Caddy runs in a container that mounts the Caddy dir at the same path, so + // the admin socket lives next to the Caddyfile. + const WGUI_LIB_CADDY_ADMIN_SOCKET = path.join( + WGUI_LIB_CADDY_DIR, + `admin.sock`, + ); const WGUI_ACME_HOME = `/opt/acme.sh`; const WGUI_ACME_SCRIPT = path.join(WGUI_ACME_HOME, `acme.sh`); const WGUI_ACME_ENV_FILE = `/etc/wgui/acme.env`; @@ -87,8 +90,6 @@ export default function grabDirNames(params?: Params) { WGUI_LIB_CADDY_STATE_FILE, WGUI_LIB_CADDY_DATA_DIR, WGUI_LIB_CADDY_ADMIN_SOCKET, - WGUI_CADDY_BIN_DIR, - WGUI_CADDY_BINARY, WGUI_ACME_HOME, WGUI_ACME_SCRIPT, WGUI_ACME_ENV_FILE,