From 8ea706181d6d20fc46ce10ebc722b43a52ca7006 Mon Sep 17 00:00:00 2001 From: Jodson Graves Date: Mon, 13 Jul 2026 13:08:55 -0400 Subject: [PATCH] ops(deploy): CLOUDFLARED_DIR preflight + fold frontend into core stack Two hardening fixes after the 2026-07-13 redeploy outage. 1. redeploy.sh gains a CLOUDFLARED_DIR preflight. The M5 audit replaced a hardcoded home path with ${CLOUDFLARED_DIR:-./deploy/cloudflared}; when .env lacks CLOUDFLARED_DIR the recreate mounted the wrong (empty) dir and cloudflared crash-looped on "tunnel credentials file not found", taking soholink.org down for ~3 minutes. The preflight aborts the deploy if CLOUDFLARED_DIR is unset or holds no *.json credential, before any container is touched. 2. The frontend edge moves from the docker-compose.frontend.yml overlay into the core stack. It served soholink.org (cloudflared -> frontend:80) from a separate overlay container that the core lifecycle did not manage; a docker compose down/up on the core stack would drop the homepage. Now it is a first-class service, cloudflared depends_on it, and redeploy.sh builds it alongside portal/orchestrator. Overlay file deleted. docker compose config parses; redeploy.sh passes sh -n. Signed-off-by: Jodson Graves --- deploy/redeploy.sh | 19 ++++++++++++++++++- docker-compose.frontend.yml | 27 --------------------------- docker-compose.yml | 19 +++++++++++++++++++ 3 files changed, 37 insertions(+), 28 deletions(-) delete mode 100644 docker-compose.frontend.yml diff --git a/deploy/redeploy.sh b/deploy/redeploy.sh index 1b0154a..fcb4331 100644 --- a/deploy/redeploy.sh +++ b/deploy/redeploy.sh @@ -37,6 +37,23 @@ if [ "$NON_SUCCESS" != "0" ]; then fi echo "CI green for ${HEAD_SHA}. Proceeding with deploy." -docker compose up -d --build portal orchestrator +# Preflight: cloudflared reads its tunnel credentials from CLOUDFLARED_DIR (audit +# M5 replaced a hardcoded home path with this var, defaulting to ./deploy/cloudflared +# which does NOT hold real creds). If it is unset or holds no credential JSON, the +# recreate below crash-loops the tunnel on "tunnel credentials file not found" and +# the public site goes down. Fail fast instead — this exact gap caused a ~3-minute +# outage on 2026-07-13. +CLOUDFLARED_DIR_VAL=$(grep '^CLOUDFLARED_DIR=' .env 2>/dev/null | cut -d= -f2- || true) +if [ -z "$CLOUDFLARED_DIR_VAL" ]; then + echo "ERROR: CLOUDFLARED_DIR is not set in .env. cloudflared would not find its tunnel credentials and the tunnel would crash-loop, taking soholink.org down. Set it to the credential dir (the one holding .json) and re-run. Aborting." >&2 + exit 1 +fi +if ! ls "$CLOUDFLARED_DIR_VAL"/*.json >/dev/null 2>&1; then + echo "ERROR: no tunnel-credential JSON found in CLOUDFLARED_DIR=$CLOUDFLARED_DIR_VAL. Aborting before the tunnel would crash-loop." >&2 + exit 1 +fi +echo "Preflight OK: tunnel credentials present in $CLOUDFLARED_DIR_VAL." + +docker compose up -d --build portal orchestrator frontend docker compose up -d --force-recreate cloudflared echo "Deploy complete." diff --git a/docker-compose.frontend.yml b/docker-compose.frontend.yml deleted file mode 100644 index cffa100..0000000 --- a/docker-compose.frontend.yml +++ /dev/null @@ -1,27 +0,0 @@ -# Public frontend for the SoHoLINK site — overlay, not part of the core stack. -# -# Serves the static landing page and reverse-proxies application routes to the -# portal backend over the existing compose network. Kept as a separate file so -# it never disturbs the running core services; bind is localhost-only until a -# reviewer decides the public (cloudflared) routing. -# -# docker compose -f docker-compose.frontend.yml up -d -# -# Requires the core stack's network to exist (soholink_default). - -services: - frontend: - image: nginx:alpine - restart: unless-stopped - volumes: - - ./deploy/frontend/html:/usr/share/nginx/html:ro - - ./deploy/frontend/nginx.conf:/etc/nginx/conf.d/default.conf:ro - ports: - - "127.0.0.1:8088:80" - networks: - - soholink - -networks: - soholink: - external: true - name: soholink_default diff --git a/docker-compose.yml b/docker-compose.yml index 37ac219..b5f9b0f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -143,6 +143,24 @@ services: portal: condition: service_healthy + # Public frontend edge: serves the static landing page at the site root and + # reverse-proxies every other path to the portal backend (deploy/frontend/). + # cloudflared's soholink.org public-hostname points at frontend:80. Folded + # into the core stack (was docker-compose.frontend.yml) so it is managed and + # started with the rest — the live homepage no longer depends on a separate + # overlay container staying up. + frontend: + image: nginx:alpine + restart: unless-stopped + volumes: + - ./deploy/frontend/html:/usr/share/nginx/html:ro + - ./deploy/frontend/nginx.conf:/etc/nginx/conf.d/default.conf:ro + ports: + - "127.0.0.1:8088:80" + depends_on: + portal: + condition: service_healthy + cloudflared: image: cloudflare/cloudflared:latest restart: unless-stopped @@ -154,6 +172,7 @@ services: - ${CLOUDFLARED_DIR:-./deploy/cloudflared}:/etc/cloudflared depends_on: - nginx + - frontend volumes: postgres_data: