diff --git a/bin/smoke.sh b/bin/smoke.sh index cc7306b..a34792b 100755 --- a/bin/smoke.sh +++ b/bin/smoke.sh @@ -6,37 +6,36 @@ # # What it covers: # 1. Frontend reachable (homepage HTTP code). -# 2. Mercure hub reachable (HEAD on /.well-known/mercure). -# 3. Container statuses on the host (none should be Restarting/Exited). -# 4. Recent CRITICAL / Fatal / 502 entries in php + worker + nginx logs. -# 5. Authenticated /api/devices round-trip via the testbot account. -# 6. Mercure publish→subscribe round-trip via a no-op PATCH. +# 2. Container statuses on the host (none should be Restarting/Exited). +# 3. Recent CRITICAL / Fatal / 5xx entries in php + worker + nginx logs. +# 4. Authenticated /api/devices round-trip via the testbot account. +# 5. Mercure publish→subscribe round-trip via a no-op PATCH. +# (This implicitly proves the hub is reachable end-to-end — no separate +# "hub up" check, because a HEAD against the SSE endpoint over HTTP/2 +# apparently leaves the connection in a state that breaks the next +# subscribe from the same source. Verified 2026-05-07.) # # Designed to fail fast — first red check exits 1. -set -uo pipefail +# NOTE: deliberately NOT using `set -uo pipefail`. With pipefail in effect, +# the backgrounded SSE subscriber in step 5 sometimes gets killed before it +# receives data. Reproduces inside this script, not in interactive shell. HOST="pictureframe.edholm.me" SSH_HOST="pictureframe" TESTBOT_EMAIL="testbot@example.com" TESTBOT_PASS="testpass123" -RED="\033[31m"; GREEN="\033[32m"; YELLOW="\033[33m"; RESET="\033[0m" +RED="\033[31m"; GREEN="\033[32m"; RESET="\033[0m" ok() { echo -e " ${GREEN}✓${RESET} $1"; } fail() { echo -e " ${RED}✗${RESET} $1"; exit 1; } -warn() { echo -e " ${YELLOW}!${RESET} $1"; } -echo "── 1/6 Frontend reachable ─────────────────────────────" +echo "── 1/5 Frontend reachable ─────────────────────────────" code=$(curl -sI -o /dev/null -w "%{http_code}" "https://$HOST/") [ "$code" = "302" ] || [ "$code" = "200" ] || fail "homepage returned $code (expected 302 redirect to login)" ok "homepage → $code" -echo "── 2/6 Mercure hub reachable ──────────────────────────" -code=$(curl -sI -o /dev/null -w "%{http_code}" "https://$HOST/.well-known/mercure?topic=ping") -[ "$code" = "200" ] || fail "mercure returned $code" -ok "mercure /.well-known/mercure → $code" - -echo "── 3/6 Container statuses ─────────────────────────────" +echo "── 2/5 Container statuses ─────────────────────────────" bad=$(ssh "$SSH_HOST" 'docker ps -a --filter name=pictureframe --format "{{.Names}}\t{{.Status}}"' \ | grep -viE "Up|healthy" || true) if [ -n "$bad" ]; then @@ -45,7 +44,7 @@ if [ -n "$bad" ]; then fi ok "all pictureframe-* containers Up" -echo "── 4/6 No recent CRITICAL/502 in logs ─────────────────" +echo "── 3/5 No recent CRITICAL/5xx in logs ─────────────────" hits=$(ssh "$SSH_HOST" ' for c in pictureframe-php-1 pictureframe-worker-1 pictureframe-nginx-1; do docker logs --since 5m "$c" 2>&1 | grep -iE "CRITICAL|Fatal|\" 5[0-9][0-9] |Error thrown" | sed "s|^|$c: |"; @@ -57,7 +56,7 @@ if [ -n "$hits" ]; then fi ok "no CRITICAL/5xx in last 5 min" -echo "── 5/6 /api/devices round-trip (testbot) ──────────────" +echo "── 4/5 /api/devices round-trip (testbot) ─────────────" JAR=$(mktemp) trap 'rm -f "$JAR"' EXIT csrf=$(curl -s -c "$JAR" "https://$HOST/login" | grep -oP 'name="_csrf_token"[^>]*value="\K[^"]+' | head -1) @@ -72,7 +71,7 @@ devices_json=$(curl -s -b "$JAR" "https://$HOST/api/devices") echo "$devices_json" | grep -q '"id":' || fail "/api/devices did not return a JSON array with id fields: $devices_json" ok "testbot login + /api/devices → device list with ids" -echo "── 6/6 Mercure publish→subscribe round-trip ───────────" +echo "── 5/5 Mercure publish→subscribe round-trip ──────────" device_id=$(echo "$devices_json" | grep -oE '"id":[0-9]+' | head -1 | grep -oE '[0-9]+') [ -n "$device_id" ] || fail "no device id available for round-trip test" out=$(mktemp)