ci(smoke): pin curl to 127.0.0.1 via --resolve

The smoke step previously curled the public hostname unconditionally,
which routes the runner's request via DNS → router → back into the same
host. Many SOHO routers do not implement hairpin NAT (or do so only after
a firmware update), so the deploy may pass on day one and silently fail
on day 90.

--resolve "<host>:443:127.0.0.1" pins the hostname to the runner's
loopback while keeping SNI on the public name (so the cert validates
correctly and the Caddy vhost block matches). The smoke test now
verifies that the Caddy-on-the-same-host is serving the right
hostname end-to-end, with no router dependency.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-05-11 13:12:05 +02:00
parent f2ec81547b
commit fe1451f570
2 changed files with 23 additions and 12 deletions

View File

@@ -93,15 +93,22 @@ jobs:
- name: Smoke test deployed environment
# Healthchecks confirm containers are healthy; they do NOT confirm the
# public surface works. This step catches: Caddy not reloaded, DNS
# missing, HSTS header dropped, /actuator block bypassed.
# 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.
run: |
set -e
URL="https://staging.raddatz.cloud"
echo "Smoke test: $URL"
curl -fsS --max-time 10 "$URL/login" -o /dev/null
curl -fsS --max-time 10 -I "$URL/" | grep -qi 'strict-transport-security'
status=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 "$URL/actuator/health")
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)"
curl -fsS $RESOLVE --max-time 10 "$URL/login" -o /dev/null
curl -fsS $RESOLVE --max-time 10 -I "$URL/" | grep -qi 'strict-transport-security'
status=$(curl -s $RESOLVE -o /dev/null -w "%{http_code}" --max-time 10 "$URL/actuator/health")
[ "$status" = "404" ] || { echo "expected 404 from /actuator/health, got $status"; exit 1; }
echo "All smoke checks passed"

View File

@@ -92,13 +92,17 @@ 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.
run: |
set -e
URL="https://archiv.raddatz.cloud"
echo "Smoke test: $URL"
curl -fsS --max-time 10 "$URL/login" -o /dev/null
curl -fsS --max-time 10 -I "$URL/" | grep -qi 'strict-transport-security'
status=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 "$URL/actuator/health")
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)"
curl -fsS $RESOLVE --max-time 10 "$URL/login" -o /dev/null
curl -fsS $RESOLVE --max-time 10 -I "$URL/" | grep -qi 'strict-transport-security'
status=$(curl -s $RESOLVE -o /dev/null -w "%{http_code}" --max-time 10 "$URL/actuator/health")
[ "$status" = "404" ] || { echo "expected 404 from /actuator/health, got $status"; exit 1; }
echo "All smoke checks passed"