#!/bin/sh set -eu cd "$(dirname "$0")/.." marker="smoke-$(date +%s)-$$" headers="" smoke_project=${SMOKE_PROJECT:-"thothii-smoke-$(date +%s)-$$"} keep_resources=${KEEP_SMOKE_RESOURCES:-0} bundle=$(mktemp) printf '%s\n' '# disposable smoke bundle' 'THT_MODEL_API_KEY=smoke-model-key' >"$bundle" chmod 0600 "$bundle" export THT_SECRETS_FILE="$bundle" trap 'rm -f "$bundle"' EXIT HUP INT TERM case "$smoke_project" in thothii) echo "SMOKE_PROJECT=thothii is reserved for the operator stack" >&2 exit 2 ;; ""|*[!a-z0-9_-]*|[!a-z0-9]*) echo "invalid SMOKE_PROJECT: use lowercase letters, digits, hyphens, or underscores" >&2 exit 2 ;; esac compose() { docker compose --project-name "$smoke_project" "$@" } # Avoid colliding with a developer's existing service. Production/developer Compose still # defaults to 8080; a published port of 0 asks Docker for a free ephemeral smoke port. export THOTH_HTTP_PORT=${THOTH_HTTP_PORT:-0} cleanup() { if [ -n "$headers" ]; then rm -f "$headers"; fi rm -f "$bundle" if [ "$keep_resources" = "1" ]; then echo "Keeping smoke resources for project $smoke_project (KEEP_SMOKE_RESOURCES=1)." >&2 else # The unique project namespace makes this safe: remove the smoke volume while leaving # the fixed `thothii` operator project and every operator-owned volume untouched. compose down --volumes >/dev/null 2>&1 || true fi } trap cleanup EXIT HUP INT TERM compose config --quiet compose up --build --wait core frontend published=$(compose port frontend 8080) http_port=${published##*:} assert_health() { health_headers=$(mktemp) health_body=$(mktemp) if ! curl --fail --silent --show-error --dump-header "$health_headers" \ "http://127.0.0.1:$http_port/health" >"$health_body"; then rm -f "$health_headers" "$health_body" return 1 fi if ! grep -qi '^content-type: application/json' "$health_headers" || ! jq -e 'type == "object" and keys == ["status"] and .status == "ok"' \ "$health_body" >/dev/null; then rm -f "$health_headers" "$health_body" return 1 fi rm -f "$health_headers" "$health_body" } assert_health # The nginx proxy must preserve streaming semantics for the backend SSE endpoint. headers=$(mktemp) curl --fail --silent --show-error --max-time 2 --dump-header "$headers" \ "http://127.0.0.1:$http_port/api/sessions/docker-smoke/events" \ >/dev/null 2>&1 || status=$? status=${status:-0} if [ "$status" -ne 0 ] && [ "$status" -ne 28 ]; then echo "SSE probe failed with curl status $status" >&2 exit "$status" fi grep -qi '^content-type: text/event-stream' "$headers" grep -qi '^cache-control: no-cache' "$headers" rm -f "$headers" headers="" # Write through the named volume, restart the application, and prove persistence. compose exec -T core sh -c 'printf "%s\n" "$1" > /data/.compose-smoke-marker' sh "$marker" compose restart core compose up --wait core frontend persisted=$(compose exec -T core sh -c 'cat /data/.compose-smoke-marker') [ "$persisted" = "$marker" ] compose exec -T core rm -f /data/.compose-smoke-marker assert_health echo "Compose health, SSE proxy, and restart persistence checks passed."