Add SERVE=1: hold the whole stack up for working on the dashboard

Building a dashboard needs the backend, the proxy with its go2rtc, the
web console with hot reload and a seeded farm to stay up while it is
being edited, and e2e-camera.sh already boots exactly that. SERVE=1 skips
every check, seeds the user as owner (the dashboard has to command and
manage the org) with every printer in printers.toml, named as that file
names them, and holds until SIGTERM or Ctrl-C, when the usual cleanup
removes the farm, the processes and the sign-in file.

The password is written to a 0600 file under umask 077 and only the path
is printed. The web console binds loopback unless APP_HOST says
otherwise, because it serves a password sign-in.

Verified: signing in through the real form against the live stack shows
Live and both printers (the H2C live, the P1P honestly "offline, Never
reported" because it was switched off); SIGTERM leaves no process, port,
login file or seeded farm behind.
This commit is contained in:
2026-10-29 10:17:54 +00:00
parent 26d382b82d
commit 705336b422
+57 -4
View File
@@ -54,6 +54,18 @@
# password each run. Port 3042 must be free. # password each run. Port 3042 must be free.
# AUTH=supabase use that real user's token for the API phases too # AUTH=supabase use that real user's token for the API phases too
# (default is the local dev-auth issuer) # (default is the local dev-auth issuer)
#
# SERVE=1 not a test: bring the whole stack up for working on
# the dashboard and hold it until stopped (SIGTERM or
# Ctrl-C tears it down and deletes the seeded farm).
# Seeds the user as `owner` with every printer in
# printers.toml, runs the web console with hot
# reload, and writes the sign-in to a 0600 file whose
# path it prints (the password is never printed).
# Implies BROWSER=1. Skips every check.
# APP_HOST=127.0.0.1 the interface the web console binds. Loopback by
# default because it serves a password sign-in; set
# 0.0.0.0 only on a network you trust.
set -euo pipefail set -euo pipefail
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
@@ -76,9 +88,12 @@ R2_ENDPOINT="${R2_ENDPOINT:-http://localhost:9000}"
export DATABASE_URL VALKEY_URL EDGE_JWT_SECRET PORT export DATABASE_URL VALKEY_URL EDGE_JWT_SECRET PORT
export R2_ACCESS_KEY_ID R2_SECRET_ACCESS_KEY R2_ENDPOINT export R2_ACCESS_KEY_ID R2_SECRET_ACCESS_KEY R2_ENDPOINT
SERVE="${SERVE:-0}"
BROWSER="${BROWSER:-0}" BROWSER="${BROWSER:-0}"
[ "$SERVE" = "1" ] && BROWSER=1
AUTH="${AUTH:-dev}" AUTH="${AUTH:-dev}"
[ "$BROWSER" = "1" ] && AUTH=supabase [ "$BROWSER" = "1" ] && AUTH=supabase
APP_HOST="${APP_HOST:-127.0.0.1}"
APP_DIR="${APP_DIR:-$REPO_ROOT/../continuum-app}" APP_DIR="${APP_DIR:-$REPO_ROOT/../continuum-app}"
APP_PORT="${APP_PORT:-3042}" APP_PORT="${APP_PORT:-3042}"
CHROME_BIN="${CHROME_BIN:-/usr/bin/google-chrome}" CHROME_BIN="${CHROME_BIN:-/usr/bin/google-chrome}"
@@ -123,6 +138,7 @@ cleanup() {
echo "--- proxy log (tail) ---"; tail -30 "$WORK/proxy.log" 2>/dev/null || true echo "--- proxy log (tail) ---"; tail -30 "$WORK/proxy.log" 2>/dev/null || true
fi fi
rm -rf "$WORK" rm -rf "$WORK"
[ -n "${LOGIN_FILE:-}" ] && rm -f "$LOGIN_FILE"
# A go2rtc that outlived us is a leak, not a detail. # A go2rtc that outlived us is a leak, not a detail.
sleep 0.5 sleep 0.5
if ps -eo comm | grep -qx go2rtc; then if ps -eo comm | grep -qx go2rtc; then
@@ -192,13 +208,28 @@ EDGE_PROXY_TOKEN="e2e-$(date +%s)"
ORG_ID="$(psql_q "INSERT INTO organisations (name) VALUES ('e2e camera org') RETURNING id")" ORG_ID="$(psql_q "INSERT INTO organisations (name) VALUES ('e2e camera org') RETURNING id")"
# `viewer`: the relay route asks for viewer, so seeding the weakest role that # `viewer`: the relay route asks for viewer, so seeding the weakest role that
# should work shows the camera is watchable without any authority to command. # should work shows the camera is watchable without any authority to command.
psql_q "INSERT INTO org_members (org_id, user_id, role) VALUES ('$ORG_ID', '$USER_ID', 'viewer')" >/dev/null # Serving is for working on the dashboard, which needs to command and to manage
# the org, so it seeds the strongest role instead.
ROLE=viewer; [ "$SERVE" = "1" ] && ROLE=owner
psql_q "INSERT INTO org_members (org_id, user_id, role) VALUES ('$ORG_ID', '$USER_ID', '$ROLE')" >/dev/null
FARM_ID="$(psql_q "INSERT INTO farms (name, org_id, edge_proxy_token) FARM_ID="$(psql_q "INSERT INTO farms (name, org_id, edge_proxy_token)
VALUES ('e2e camera farm', '$ORG_ID', '$EDGE_PROXY_TOKEN') RETURNING id")" VALUES ('e2e camera farm', '$ORG_ID', '$EDGE_PROXY_TOKEN') RETURNING id")"
# Named as printers.toml names it (only the name line is read), so a dashboard
# shows "H2C" and not a test placeholder.
CAM_NAME="$(awk -F'"' -v id="$CAMERA_ID" '/^id *=/ {cur=$2} cur==id && /^name *=/ {print $2; exit}' "$PROXY_DIR/printers.toml")"
psql_q "INSERT INTO printers (farm_id, name, vendor, gateway_printer_id) psql_q "INSERT INTO printers (farm_id, name, vendor, gateway_printer_id)
VALUES ('$FARM_ID', 'camera under test', 'bambu', '$CAMERA_ID')" >/dev/null VALUES ('$FARM_ID', '${CAM_NAME:-camera under test}', 'bambu', '$CAMERA_ID')" >/dev/null
CAM_UUID="$(psql_q "SELECT id FROM printers WHERE farm_id='$FARM_ID' AND gateway_printer_id='$CAMERA_ID'")" CAM_UUID="$(psql_q "SELECT id FROM printers WHERE farm_id='$FARM_ID' AND gateway_printer_id='$CAMERA_ID'")"
ok "org $ORG_ID, farm $FARM_ID, viewer $USER_ID" if [ "$SERVE" = "1" ]; then
# Every other printer the gateway will report, or its telemetry is skipped as
# unknown. Only ids and names are read from printers.toml, never credentials.
while read -r pid pname; do
[ -z "$pid" ] || [ "$pid" = "$CAMERA_ID" ] && continue
psql_q "INSERT INTO printers (farm_id, name, vendor, gateway_printer_id)
VALUES ('$FARM_ID', '$pname', 'bambu', '$pid')" >/dev/null
done < <(awk -F'"' '/^id *=/ {id=$2} /^name *=/ {print id, $2}' "$PROXY_DIR/printers.toml")
fi
ok "org $ORG_ID, farm $FARM_ID, $ROLE $USER_ID"
# --- 2. backend ------------------------------------------------------------ # --- 2. backend ------------------------------------------------------------
@@ -289,6 +320,7 @@ run_viewer() {
fi fi
} }
if [ "$SERVE" != "1" ]; then
echo "==> a real WebRTC peer plays the camera through the relay (direct candidates)" echo "==> a real WebRTC peer plays the camera through the relay (direct candidates)"
run_viewer "direct" 5 run_viewer "direct" 5
@@ -302,6 +334,7 @@ else
echo " SKIPPED: relay-only phase - CLOUDFLARE_TURN_KEY_ID / CLOUDFLARE_TURN_API_TOKEN not set." echo " SKIPPED: relay-only phase - CLOUDFLARE_TURN_KEY_ID / CLOUDFLARE_TURN_API_TOKEN not set."
echo " The TURN-relayed path, which the browser is pinned to, is NOT verified by this run." echo " The TURN-relayed path, which the browser is pinned to, is NOT verified by this run."
fi fi
fi
# --- 5. the real web console in a real browser ------------------------------- # --- 5. the real web console in a real browser -------------------------------
@@ -315,7 +348,8 @@ if [ "$BROWSER" = "1" ]; then
NUXT_PUBLIC_WS_BASE="ws://localhost:$PORT" \ NUXT_PUBLIC_WS_BASE="ws://localhost:$PORT" \
SUPABASE_URL="$SUPABASE_URL" \ SUPABASE_URL="$SUPABASE_URL" \
SUPABASE_KEY="$SUPABASE_PUBLISHABLE_KEY" \ SUPABASE_KEY="$SUPABASE_PUBLISHABLE_KEY" \
pnpm exec nuxt dev --host 127.0.0.1 --port "$APP_PORT" > "$WORK/app.log" 2>&1 & NUXT_PUBLIC_PASSWORD_LOGIN=true \
pnpm exec nuxt dev --host "$APP_HOST" --port "$APP_PORT" > "$WORK/app.log" 2>&1 &
APP_PID=$! APP_PID=$!
for _ in $(seq 1 120); do for _ in $(seq 1 120); do
curl -sf -o /dev/null "http://127.0.0.1:$APP_PORT/login" 2>/dev/null && break curl -sf -o /dev/null "http://127.0.0.1:$APP_PORT/login" 2>/dev/null && break
@@ -325,6 +359,25 @@ if [ "$BROWSER" = "1" ]; then
|| { tail -20 "$WORK/app.log" >&2; fail "the web console did not start on :$APP_PORT"; } || { tail -20 "$WORK/app.log" >&2; fail "the web console did not start on :$APP_PORT"; }
ok "the web console is up" ok "the web console is up"
if [ "$SERVE" = "1" ]; then
# Written to a file rather than printed: the password is real, if
# throwaway. umask first so it is never briefly readable by anyone else.
LOGIN_FILE="${SERVE_LOGIN_FILE:-/tmp/continuum-dev-login}"
( umask 077; printf 'email: %s\npassword: %s\n' "$SB_EMAIL" "$SB_PASSWORD" > "$LOGIN_FILE" )
echo
echo "READY - the dashboard stack is running and will hold until stopped."
echo " web console http://$APP_HOST:$APP_PORT (hot reload on edits in $APP_DIR)"
echo " backend API $BASE"
echo " sign in $LOGIN_FILE (mode 600)"
echo " gateway e2e-cam-gw, farm $FARM_ID, printers: $(psql_q "SELECT string_agg(gateway_printer_id, ', ') FROM printers WHERE farm_id='$FARM_ID'")"
# `sleep & wait` rather than a foreground sleep: bash defers a trapped signal
# until a foreground child exits, but `wait` is interrupted at once. The
# trap turns TERM/INT into a normal exit so the EXIT trap tears it all down.
trap 'exit 143' TERM INT
sleep infinity &
wait $!
fi
echo "==> the real console in real Chrome: sign in, open the fleet, watch the camera" echo "==> the real console in real Chrome: sign in, open the fleet, watch the camera"
BROWSER_OUT="$(env APP_URL="http://127.0.0.1:$APP_PORT" SUPABASE_E2E_EMAIL="$SB_EMAIL" \ BROWSER_OUT="$(env APP_URL="http://127.0.0.1:$APP_PORT" SUPABASE_E2E_EMAIL="$SB_EMAIL" \
SUPABASE_E2E_PASSWORD="$SB_PASSWORD" CHROME_BIN="$CHROME_BIN" \ SUPABASE_E2E_PASSWORD="$SB_PASSWORD" CHROME_BIN="$CHROME_BIN" \