#!/usr/bin/env bash # Builds and starts the production stack under a throwaway project name with # generated secrets, checks it, then removes it (containers and volumes). # deploy/check.sh from the repository root or deploy/ # Exits non-zero if any check fails. Needs Docker with Compose v2. set -euo pipefail # Git Bash on Windows would rewrite container paths like /scripts/… otherwise. export MSYS_NO_PATHCONV=1 cd "$(dirname "$0")" project="deadhead-check" env_file=".check.env" secret() { node -e 'console.log(require("crypto").randomBytes(+process.argv[1]).toString("base64url"))' "$1"; } port="${CHECK_PORT:-8088}" site="http://localhost:$port" cat > "$env_file" </dev/null 2>&1 || true compose down -v --remove-orphans >/dev/null 2>&1 || true rm -rf "$env_file" .check-backups } trap cleanup EXIT echo "Building and starting the stack…" compose up -d --build --wait --wait-timeout 180 fail=0 check() { local name="$1" url="$2" expect="$3" body body="$(compose exec -T server node -e "fetch('$url').then(r => r.text().then(t => console.log(r.status + ' ' + t)))")" if [[ "$body" == $expect ]]; then echo "ok $name: $body" else echo "FAIL $name: $body" fail=1 fi } # Step 13.1: the server, with migrations applied, answers health and time. check "health" "http://127.0.0.1:3000/api/health" '200 {"status":"ok"}' check "server time" "http://127.0.0.1:3000/api/time" '200 {"realTime":*' # Step 13.2: Caddy serves the app and proxies /api on one origin. expect() { local name="$1" got="$2" pattern="$3" if [[ "$got" == $pattern ]]; then echo "ok $name"; else echo "FAIL $name: $got"; fail=1; fi } expect "app shell" "$(curl -s "$site/")" '*Deadhead: Space Freight Tycoon*' expect "client route falls back to the app" "$(curl -s "$site/fleet")" '*
*' expect "API through the proxy" "$(curl -s "$site/api/health")" '{"status":"ok"}' expect "service worker is never cached" "$(curl -sI "$site/sw.js" | tr -d '\r' | grep -i '^cache-control')" '*no-cache*' bundle="$(curl -s "$site/" | grep -o '/assets/index-[A-Za-z0-9_-]*\.js' | head -1)" expect "hashed bundle is cached for a year" "$(curl -sI "$site$bundle" | tr -d '\r' | grep -i '^cache-control')" '*immutable*' # A guest can start a company and dispatch a tutorial run (Step 13.2's manual check, scripted). jar="$(mktemp)" trap 'cleanup; rm -f "$jar"' EXIT post() { curl -s -b "$jar" -c "$jar" -H "Origin: $site" -H 'content-type: application/json' -X POST "$site$1" -d "$2"; } expect "guest sign-up" "$(post /api/guest '{}')" '*"tutorial":{*' contract="$(curl -s -b "$jar" "$site/api/tutorial/board" | node -e 'let d="";process.stdin.on("data",c=>d+=c).on("end",()=>console.log(JSON.parse(d).contracts[0].id))')" expect "tutorial run dispatched" "$(post /api/dispatches "{\"kind\":\"tutorial\",\"contractId\":\"$contract\"}")" '*"dispatch":{*' # Step 13.3: back up, restore the latest dump into a fresh database, and find # that player's state exactly as it is in the live one. player="$(curl -s -b "$jar" "$site/api/state" | node -e 'let d="";process.stdin.on("data",c=>d+=c).on("end",()=>console.log(JSON.parse(d).playerId))')" expect "backup written" "$(compose exec -T backup /scripts/backup.sh)" 'backup: wrote /backups/deadhead-*.dump*' expect "latest dump restored" "$(compose exec -T backup /scripts/restore.sh)" 'restore: /backups/deadhead-*.dump -> deadhead_restore' player_state() { local rows="" table column for spec in players:id companies:player_id ships:player_id pilots:player_id bays:player_id \ discovered_locations:player_id contract_boards:player_id dispatches:player_id \ accepted_contracts:player_id; do table="${spec%%:*}" column="${spec##*:}" rows+="$table=$(compose exec -T backup psql -d "$1" -tAc \ "SELECT coalesce(json_agg(t ORDER BY t::text), '[]') FROM $table t WHERE $column = '$player'");" done echo "$rows" } live="$(player_state deadhead)" restored="$(player_state deadhead_restore)" expect "player found in the live database" "$live" '*"tutorial":true*' if [[ "$restored" == "$live" ]]; then echo "ok restored player state matches the live one" else echo "FAIL restored player state differs" fail=1 fi exit "$fail"