#!/usr/bin/env bash # scripts/demo.sh — bring the whole stack up: certs → build → VM+Grafana → # substrate → simulator. Tails simulator progress in the foreground. Ctrl-C # cleans everything up. # # Overridable via env vars: # PROFILE single | industrial (default: industrial) # RATE_HZ T1 datagram rate (default: 500) # T2_RATE_HZ T2 uni stream rate (default: 5) # T3_RATE_HZ T3 bi stream rate (default: 2) # DEVICES number of devices (default: 5) # BUILD release | debug (default: release) # KEEP_MONITORING if 1, don't `docker compose down` on exit (default: 0) # # Example: # ./scripts/demo.sh # PROFILE=single RATE_HZ=100 DEVICES=20 ./scripts/demo.sh # KEEP_MONITORING=1 ./scripts/demo.sh set -euo pipefail # --- locate repo root --- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" cd "$ROOT" # --- defaults --- PROFILE="${PROFILE:-industrial}" RATE_HZ="${RATE_HZ:-500}" T2_RATE_HZ="${T2_RATE_HZ:-5}" T3_RATE_HZ="${T3_RATE_HZ:-2}" DEVICES="${DEVICES:-5}" BUILD="${BUILD:-release}" KEEP_MONITORING="${KEEP_MONITORING:-0}" LOG_DIR="${LOG_DIR:-/tmp/quic_ecs_dt}" # --- pretty logging --- if [[ -t 1 ]]; then BOLD=$'\033[1m'; DIM=$'\033[2m'; GREEN=$'\033[32m' YELLOW=$'\033[33m'; RED=$'\033[31m'; CYAN=$'\033[36m'; RESET=$'\033[0m' else BOLD=; DIM=; GREEN=; YELLOW=; RED=; CYAN=; RESET= fi step() { printf '%s» %s%s\n' "$BOLD" "$1" "$RESET"; } ok() { printf '%s ✓ %s%s\n' "$GREEN" "$1" "$RESET"; } warn() { printf '%s ! %s%s\n' "$YELLOW" "$1" "$RESET"; } fail() { printf '%s ✗ %s%s\n' "$RED" "$1" "$RESET"; } # --- prereq check --- step "Checking prerequisites" for cmd in cargo docker openssl curl lsof; do if ! command -v "$cmd" >/dev/null 2>&1; then fail "missing required command: $cmd" exit 1 fi done if ! docker compose version >/dev/null 2>&1; then fail "docker compose plugin not available (try 'docker compose version')" exit 1 fi ok "cargo, docker, openssl, curl, lsof present" # --- port collision check (substrate runs on 9000 udp + 9100 tcp) --- for port in 9000 9100; do if lsof -nP -iUDP:$port -iTCP:$port -sTCP:LISTEN 2>/dev/null | grep -q LISTEN; then fail "port $port appears to be in use — another substrate or process is running" lsof -nP -iUDP:$port -iTCP:$port -sTCP:LISTEN 2>/dev/null | head -5 exit 1 fi done ok "ports 9000 (QUIC) and 9100 (/metrics) are free" # --- certs --- step "Ensuring dev TLS cert exists" if [[ ! -f certs/server.crt || ! -f certs/server.key ]]; then make certs >/dev/null ok "generated certs/server.{crt,key}" else ok "certs/server.{crt,key} already present" fi # --- build --- step "Building substrate + simulator ($BUILD profile)" if [[ "$BUILD" == "release" ]]; then cargo build --release -p substrate -p simulator SUBSTRATE_BIN="$ROOT/target/release/substrate" SIMULATOR_BIN="$ROOT/target/release/simulator" else cargo build -p substrate -p simulator SUBSTRATE_BIN="$ROOT/target/debug/substrate" SIMULATOR_BIN="$ROOT/target/debug/simulator" fi ok "binaries: $SUBSTRATE_BIN, $SIMULATOR_BIN" # --- monitoring --- step "Bringing up VictoriaMetrics + Grafana (docker compose)" docker compose -f monitoring/docker-compose.yml up -d >/dev/null ok "containers started" printf '%s ⏳ waiting for VictoriaMetrics on :8428' "$DIM" for i in $(seq 1 40); do if curl -sf http://localhost:8428/health >/dev/null 2>&1; then printf ' ready%s\n' "$RESET"; break fi printf '.'; sleep 0.5 if [[ $i -eq 40 ]]; then printf ' TIMEOUT%s\n' "$RESET"; exit 1; fi done printf '%s ⏳ waiting for Grafana on :3000' "$DIM" for i in $(seq 1 40); do if curl -sf http://localhost:3000/api/health >/dev/null 2>&1; then printf ' ready%s\n' "$RESET"; break fi printf '.'; sleep 0.5 if [[ $i -eq 40 ]]; then printf ' TIMEOUT%s\n' "$RESET"; exit 1; fi done # --- substrate --- mkdir -p "$LOG_DIR" SUB_LOG="$LOG_DIR/substrate.log" SIM_LOG="$LOG_DIR/simulator.log" : >"$SUB_LOG" : >"$SIM_LOG" step "Starting substrate (log: $SUB_LOG)" RUST_LOG=info "$SUBSTRATE_BIN" >"$SUB_LOG" 2>&1 & SUBSTRATE_PID=$! printf '%s ⏳ waiting for /metrics on :9100' "$DIM" for i in $(seq 1 40); do if curl -sf http://localhost:9100/metrics >/dev/null 2>&1; then printf ' ready%s\n' "$RESET"; break fi printf '.'; sleep 0.25 if [[ $i -eq 40 ]]; then printf ' TIMEOUT%s\n' "$RESET" warn "substrate failed to start; tail of $SUB_LOG:" tail -30 "$SUB_LOG" kill "$SUBSTRATE_PID" 2>/dev/null || true exit 1 fi done # --- simulator --- TOTAL_SLOTS=$DEVICES if [[ "$PROFILE" == "industrial" ]]; then TOTAL_SLOTS=$((DEVICES * 5)) fi step "Starting simulator (log: $SIM_LOG)" RUST_LOG=info "$SIMULATOR_BIN" \ --profile "$PROFILE" \ --rate-hz "$RATE_HZ" \ --t2-rate-hz "$T2_RATE_HZ" \ --t3-rate-hz "$T3_RATE_HZ" \ --count 0 \ --devices "$DEVICES" \ >"$SIM_LOG" 2>&1 & SIMULATOR_PID=$! sleep 0.5 if ! kill -0 "$SIMULATOR_PID" 2>/dev/null; then fail "simulator exited immediately; tail of $SIM_LOG:" tail -20 "$SIM_LOG" kill "$SUBSTRATE_PID" 2>/dev/null || true exit 1 fi ok "simulator PID $SIMULATOR_PID" # --- cleanup trap --- cleanup() { printf '\n%s» Cleaning up%s\n' "$BOLD" "$RESET" if [[ -n "${SIMULATOR_PID:-}" ]]; then kill -TERM "$SIMULATOR_PID" 2>/dev/null || true wait "$SIMULATOR_PID" 2>/dev/null || true ok "simulator stopped" fi if [[ -n "${SUBSTRATE_PID:-}" ]]; then kill -TERM "$SUBSTRATE_PID" 2>/dev/null || true wait "$SUBSTRATE_PID" 2>/dev/null || true ok "substrate stopped" fi if [[ "$KEEP_MONITORING" == "1" ]]; then warn "leaving monitoring stack up (KEEP_MONITORING=1) — 'make monitoring-down' to stop" else docker compose -f monitoring/docker-compose.yml down >/dev/null 2>&1 || true ok "monitoring stack stopped" fi printf '%sLogs preserved at:%s %s\n' "$DIM" "$RESET" "$LOG_DIR" } trap cleanup EXIT INT TERM # --- summary --- cat <