#!/usr/bin/env bash # deploy/smoke/compose_smoke.sh — T2 compose smoke (deploy-B §9). # # Brings up deploy/compose.yaml with `docker compose up -d --wait`, asserts # every service reports healthy via `docker compose ps`, then curls # /healthz through Caddy's TLS (using the internal CA — # RUTSTER_LOCAL_CERTS=true is set in the smoke's env). The compose smoke # is intentionally lighter than the all-in-one smoke (which already proves # the WS path) — its job is to assert the four-service orchestrator shape # boots + the network_mode: "service:engine" brain->engine tap posture. # # The ValkeyEventSink-lands-in-stream assertion is slice C's concern. # Named hook below; do NOT implement it in this slice. set -euo pipefail cd "$(dirname "$0")/.." # deploy/ IMAGES_TAG="${RUTSTER_IMAGES_TAG:-smoke}" export RUTSTER_DOMAIN=localhost export RUTSTER_ACME_EMAIL=smoke@example.com export RUTSTER_LOCAL_CERTS=true export RUTSTER_HTTP_BIND=0.0.0.0:8080 export RUTSTER_MEDIA_BIND_IP=127.0.0.1 export RUTSTER_MEDIA_PORT_RANGE=49160-49170 export RUTSTER_DRAIN_DEADLINE_SECS=10 export RUTSTER_TAP_URL=ws://127.0.0.1:8082 echo "[compose_smoke] overriding compose.yaml image tags -> :${IMAGES_TAG}" # Sed the image: tags from :latest to :smoke so CI's locally-built images # are picked up. In-place + restored on exit. cp compose.yaml compose.yaml.smoke-backup trap 'mv compose.yaml.smoke-backup compose.yaml' EXIT sed -i "s|image: rutster-edge:latest|image: rutster-edge:${IMAGES_TAG}|" compose.yaml sed -i "s|image: rutster-engine:latest|image: rutster-engine:${IMAGES_TAG}|" compose.yaml sed -i "s|image: rutster-brain:latest|image: rutster-brain:${IMAGES_TAG}|" compose.yaml echo "[compose_smoke] docker compose up -d --wait" docker compose up -d --wait --timeout 120 echo "[compose_smoke] docker compose ps" docker compose ps # Each service should report "running" state. (Healthy flag is set by the # engine's /readyz via the Docker healthcheck — none of the shipped images # define a HEALTHCHECK yet; slice-G or a future hardening slice can add one. # For now State="running" + /healthz 200 + /readyz 200 is the gate.) HEALTHY_COUNT=$(docker compose ps --format json | jq '[.[] | select(.State == "running")] | length') EXPECTED=4 if [ "$HEALTHY_COUNT" -ne "$EXPECTED" ]; then echo "[compose_smoke] FAIL: expected $EXPECTED running services, got $HEALTHY_COUNT" >&2 docker compose logs exit 1 fi echo "[compose_smoke] $EXPECTED services running" # Caddy /healthz via TLS (internal CA). Port 443 is published by the caddy service. ROOT_CERT=$(docker compose exec -T caddy cat /data/caddy/pki/authorities/local/root.crt) echo "$ROOT_CERT" > /tmp/rutster-smoke-root.pem # Wait for /healthz to return 200 via Caddy TLS. for _ in $(seq 1 60); do if curl --cacert /tmp/rutster-smoke-root.pem -fsS https://localhost/healthz \ | grep -q '^ok$'; then echo "[compose_smoke] /healthz ok" break fi sleep 0.5 done curl --cacert /tmp/rutster-smoke-root.pem -fsS https://localhost/healthz \ || { echo "[compose_smoke] FAIL: /healthz never returned ok"; docker compose logs; exit 1; } # Engine /readyz — fresh boot, MUST be 200 (drain happens on shutdown, not # boot; cap only after RUTSTER_MAX_SESSIONS sessions exist). curl --cacert /tmp/rutster-smoke-root.pem -fsS https://localhost/readyz \ || { echo "[compose_smoke] FAIL: /readyz never returned ok"; docker compose logs; exit 1; } echo "[compose_smoke] /readyz ok" # TODO slice-C: assert lifecycle event in Valkey stream. # Slice C lands ValkeyEventSink (spec §5.6) + the assert-event-lands-in- # Valkey-stream smoke addition. The hook here documents the contract: # Once slice C lands, this smoke runs: # docker compose exec valkey valkey-cli XLEN rutster:events # and asserts > 0 after a sim call completes (an EventSink::on_event # call writes via XADD MAXLEN ~). Until then, the valkey service is dark # (no consumers) — counting the stream length would always be 0. # Leaving the named hook so slice C's plan can grep for it. echo "[compose_smoke] PASS: T2 compose boots + /healthz + /readyz verified" # Cleanup (compose down; volumes left for warm cache on re-run). docker compose down --volumes --timeout 30 || true exit 0