diff --git a/services/dns-updater/README.md b/services/dns-updater/README.md index c2d7469..9fb7f6f 100644 --- a/services/dns-updater/README.md +++ b/services/dns-updater/README.md @@ -53,21 +53,25 @@ Generate node tokens with `openssl rand -hex 24`. See `env.example`. ## Deploy -Runs on the **ct-forge droplet** behind the existing Caddy, on a shared `edge` -docker network. +Runs on the **ct-forge droplet** (`134.199.243.61`). The droplet terminates TLS +with a **host Caddy** (`/etc/caddy/Caddyfile`, systemd) that reverse-proxies to +localhost ports. The container publishes `127.0.0.1:8090`; Caddy proxies +`dns.ct.uvlava.com` → it. ```bash -# one-time: put secrets on the droplet -ssh root@forge.ct.uvlava.com 'mkdir -p /opt/dns-updater' -scp env.example root@forge.ct.uvlava.com:/opt/dns-updater/.env # then edit + fill +# one-time: put secrets on the droplet (use the IP; forge.ct won't resolve +# until uvlava.com DNSSEC is removed) +ssh root@134.199.243.61 'mkdir -p /opt/dns-updater' +scp env.example root@134.199.243.61:/opt/dns-updater/.env # then edit + fill -./deploy.sh # rsync + build + start + wire Caddy vhost + reload +./deploy.sh # rsync + build + start + wire host Caddy vhost + reload ``` -`deploy.sh` is idempotent: ensures the `edge` network, attaches Caddy, appends -the `dns.ct.uvlava.com` vhost to `/opt/forge/Caddyfile` if missing, reloads -Caddy, and health-checks the container. (The same wiring is declared in -`terraform/do/cloud-init/forge.yaml` for clean reprovisions.) +`deploy.sh` is idempotent: builds/starts the container, appends the +`dns.ct.uvlava.com` vhost to `/etc/caddy/Caddyfile` if missing, `caddy validate`s +before reloading, and health-checks on loopback. The LE cert is auto-issued by +Caddy via HTTP-01 **once `dns.ct.uvlava.com` resolves publicly** (i.e. after the +uvlava.com DNSSEC DS is removed at joker). ## One-time registrar setup (manual, at joker.com) diff --git a/services/dns-updater/compose.yml b/services/dns-updater/compose.yml index 1b1074f..1a7c07e 100644 --- a/services/dns-updater/compose.yml +++ b/services/dns-updater/compose.yml @@ -1,9 +1,9 @@ # dns-updater stack on the ct-forge droplet. # -# Joins the external "edge" network that the forge Caddy is attached to, so -# Caddy reaches this service by name (reverse_proxy dns-updater:8090). The -# `edge` network + the dns.ct.uvlava.com vhost are added to the forge stack in -# terraform/do/cloud-init/forge.yaml. +# The live forge droplet terminates TLS with a HOST Caddy (/etc/caddy/Caddyfile, +# systemd), not a Caddy container. So this service publishes a loopback-only port +# and the host Caddy reverse-proxies dns.ct.uvlava.com -> 127.0.0.1:8090. +# deploy.sh wires the vhost + reloads Caddy. # # Secrets (.env, gitignored): DO_TOKEN, DNS_UPDATER_TOKENS. See env.example. services: @@ -17,10 +17,6 @@ services: - DNS_DOMAIN=${DNS_DOMAIN:-uvlava.com} - PORT=8090 - TRUST_PROXY=true - networks: - - edge - -networks: - edge: - external: true - name: edge + # Loopback-only: reachable by the host Caddy, never directly from the internet. + ports: + - "127.0.0.1:8090:8090" diff --git a/services/dns-updater/deploy.sh b/services/dns-updater/deploy.sh index a9e0c3d..5c953f1 100755 --- a/services/dns-updater/deploy.sh +++ b/services/dns-updater/deploy.sh @@ -1,16 +1,17 @@ #!/usr/bin/env bash # Deploy dns-updater to the ct-forge droplet. # -# Rsyncs this service dir to /opt/dns-updater on the forge droplet, ensures the -# shared `edge` docker network exists and the forge Caddy is attached to it, -# then (re)builds and starts the container. The .env (secrets) must already be -# placed on the droplet at /opt/dns-updater/.env (it is gitignored and never -# rsynced over an existing one). +# Rsyncs this service dir to /opt/dns-updater, (re)builds + starts the container +# (published loopback-only on 127.0.0.1:8090), then wires the HOST Caddy +# (/etc/caddy/Caddyfile, systemd) to reverse-proxy dns.ct.uvlava.com -> :8090 +# and reloads it. The .env (secrets) must already be on the droplet at +# /opt/dns-updater/.env (gitignored; never rsynced over an existing one). # -# Usage: ./deploy.sh [user@host] (default: root@forge.ct.uvlava.com) +# Usage: ./deploy.sh [user@host] (default: root@134.199.243.61 — forge.ct +# won't resolve until uvlava.com DNSSEC is removed; use the IP). set -euo pipefail -TARGET="${1:-root@forge.ct.uvlava.com}" +TARGET="${1:-root@134.199.243.61}" REMOTE_DIR="/opt/dns-updater" HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" @@ -22,16 +23,6 @@ rsync -az --delete \ --exclude '.git' \ "${HERE}/" "${TARGET}:${REMOTE_DIR}/" -echo "==> Ensure shared 'edge' network + forge Caddy attached" -ssh "${TARGET}" bash -s <<'REMOTE' -set -euo pipefail -docker network inspect edge >/dev/null 2>&1 || docker network create edge -# Attach the forge Caddy container to edge (idempotent). -if docker ps --format '{{.Names}}' | grep -qx caddy; then - docker network connect edge caddy 2>/dev/null || true -fi -REMOTE - echo "==> Verify .env present (secrets) on droplet" ssh "${TARGET}" "test -f ${REMOTE_DIR}/.env" || { echo "ERROR: ${REMOTE_DIR}/.env missing on droplet. Copy from env.example and fill DO_TOKEN + DNS_UPDATER_TOKENS." >&2 @@ -41,25 +32,28 @@ ssh "${TARGET}" "test -f ${REMOTE_DIR}/.env" || { echo "==> Build + start dns-updater" ssh "${TARGET}" "cd ${REMOTE_DIR} && docker compose up -d --build" -echo "==> Ensure dns.ct.uvlava.com vhost in the live forge Caddyfile + reload" +echo "==> Ensure dns.ct.uvlava.com vhost in the host Caddyfile + reload" ssh "${TARGET}" bash -s <<'REMOTE' set -euo pipefail -CF=/opt/forge/Caddyfile +CF=/etc/caddy/Caddyfile if [ -f "$CF" ] && ! grep -q "dns.ct.uvlava.com" "$CF"; then cat >>"$CF" <<'VHOST' # dyndns2 updater for region-mobile nodes (services/dns-updater). dns.ct.uvlava.com { - reverse_proxy dns-updater:8090 + reverse_proxy 127.0.0.1:8090 } VHOST echo "vhost appended" +else + echo "vhost already present" fi -# Reload Caddy in place (no downtime for the other vhosts). -docker exec caddy caddy reload --config /etc/caddy/Caddyfile 2>/dev/null \ - || docker restart caddy +# Validate before reloading so a bad edit never takes Caddy down. +caddy validate --config "$CF" >/dev/null 2>&1 || { echo "Caddyfile validate FAILED — not reloading" >&2; exit 1; } +systemctl reload caddy || systemctl restart caddy REMOTE -echo "==> Health check" -ssh "${TARGET}" "docker run --rm --network edge curlimages/curl:latest -fsS http://dns-updater:8090/healthz" && echo -echo "==> Done. Verify https://dns.ct.uvlava.com/healthz once the A record + LE cert are live." +echo "==> Health check (loopback on droplet)" +ssh "${TARGET}" "curl -fsS http://127.0.0.1:8090/healthz" && echo +echo "==> Done. https://dns.ct.uvlava.com/healthz will work once uvlava.com DNSSEC" +echo " is removed (Caddy then auto-issues the LE cert via HTTP-01)." diff --git a/terraform/do/cloud-init/forge.yaml b/terraform/do/cloud-init/forge.yaml index d6a68f3..90792db 100644 --- a/terraform/do/cloud-init/forge.yaml +++ b/terraform/do/cloud-init/forge.yaml @@ -73,15 +73,6 @@ write_files: - ./caddy-data:/data - ./caddy-config:/config - ./Caddyfile:/etc/caddy/Caddyfile - # Also join the shared "edge" net so Caddy can reach the separately - # deployed dns-updater (services/dns-updater) as dns-updater:8090. - networks: - - default - - edge - - networks: - edge: - name: edge - path: /opt/forge/Caddyfile permissions: "0644" @@ -102,11 +93,6 @@ write_files: swift.ct.uvlava.com { reverse_proxy forgejo:3000 } - # dyndns2 updater for region-mobile nodes (see services/dns-updater). - # Container deployed separately; reachable over the shared "edge" network. - dns.ct.uvlava.com { - reverse_proxy dns-updater:8090 - } runcmd: # Install Docker engine + compose plugin.