From 3056311c24faafa2544a3502cf56b224b3f9dd59 Mon Sep 17 00:00:00 2001 From: Marcel Date: Tue, 12 May 2026 09:08:20 +0200 Subject: [PATCH] fix(ci): resolve smoke test host via bridge gateway, not 127.0.0.1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Job containers run in bridge network mode (runner-config.yaml). Inside a bridge-networked container 127.0.0.1 is the container's own loopback; Caddy on the host is unreachable there, causing an immediate ECONNREFUSED. Use the Docker bridge gateway IP instead — the host's docker0 interface where Caddy (bound on 0.0.0.0:443) is reachable from the container. Co-Authored-By: Claude Sonnet 4.6 --- .gitea/workflows/nightly.yml | 16 ++++++++++------ .gitea/workflows/release.yml | 9 +++++---- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/.gitea/workflows/nightly.yml b/.gitea/workflows/nightly.yml index 490a91fc..a16a0797 100644 --- a/.gitea/workflows/nightly.yml +++ b/.gitea/workflows/nightly.yml @@ -158,16 +158,20 @@ jobs: # public surface works. This step catches: Caddy not reloaded, HSTS # header dropped, /actuator block bypassed. # - # --resolve pins staging.raddatz.cloud to the runner's loopback so we - # do NOT depend on the host router doing hairpin NAT (many SOHO - # routers do not, or do so only after a firmware update). SNI still - # uses the public hostname so the cert validates correctly. + # --resolve pins staging.raddatz.cloud to the Docker bridge gateway IP + # (the host) so we do NOT depend on hairpin NAT on the host router. + # 127.0.0.1 cannot be used: job containers run in bridge network mode + # (runner-config.yaml), so 127.0.0.1 is the container's loopback, not + # the host's. The bridge gateway IS the host; Caddy binds 0.0.0.0:443 + # and is therefore reachable from the container via that IP. + # SNI still uses the public hostname so the TLS cert validates correctly. run: | set -e HOST="staging.raddatz.cloud" URL="https://$HOST" - RESOLVE="--resolve $HOST:443:127.0.0.1" - echo "Smoke test: $URL (pinned to 127.0.0.1)" + HOST_IP=$(ip route show default | awk '/default/ {print $3}') + RESOLVE="--resolve $HOST:443:$HOST_IP" + echo "Smoke test: $URL (pinned to $HOST_IP via bridge gateway)" curl -fsS $RESOLVE --max-time 10 "$URL/login" -o /dev/null # Pin the preload-list-eligible HSTS value, not just header presence: # a degraded `max-age=1` or a dropped `includeSubDomains; preload` must diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index 8d355da2..4dafb0e3 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -107,14 +107,15 @@ jobs: - name: Smoke test deployed environment # See nightly.yml — same three checks, against the prod vhost. - # --resolve pins archiv.raddatz.cloud to the runner's loopback so - # the smoke test does NOT depend on hairpin NAT on the host router. + # --resolve pins to the bridge gateway IP (the host), not 127.0.0.1 + # — see nightly.yml for the full network topology explanation. run: | set -e HOST="archiv.raddatz.cloud" URL="https://$HOST" - RESOLVE="--resolve $HOST:443:127.0.0.1" - echo "Smoke test: $URL (pinned to 127.0.0.1)" + HOST_IP=$(ip route show default | awk '/default/ {print $3}') + RESOLVE="--resolve $HOST:443:$HOST_IP" + echo "Smoke test: $URL (pinned to $HOST_IP via bridge gateway)" curl -fsS $RESOLVE --max-time 10 "$URL/login" -o /dev/null # Pin the preload-list-eligible HSTS value, not just header presence: # a degraded `max-age=1` or a dropped `includeSubDomains; preload` must