#!/usr/bin/env bash # # End-to-end re-render of the dashboard demo video. # # Defaults assume you run from inside this repo's vscode-server container # (where host.docker.internal reaches the docker-compose stack). Override # any URL/credential via env vars at the top. # # Usage: # ./render.sh # full pipeline (uses cached auth.json if fresh) # ./render.sh --fresh-auth # force re-auth even if auth.json exists # ./render.sh --no-encode # stop at WebM, skip MP4 encode # FORCE_AUTH=1 ./render.sh # same as --fresh-auth # APP_URL=http://localhost:3001 ./render.sh # override frontend URL set -euo pipefail # -- config (override via env) ------------------------------------------------- APP_URL="${APP_URL:-http://host.docker.internal:3001}" PB_URL="${PB_URL:-http://host.docker.internal:8090}" API_URL="${API_URL:-http://host.docker.internal:8001}" PB_EMAIL="${PB_EMAIL:-demo-video@local.test}" PB_PASSWORD="${PB_PASSWORD:-DemoVideoPass123!}" MAX_DURATION_S="${MAX_DURATION_S:-15}" AUTH_TTL_HOURS="${AUTH_TTL_HOURS:-24}" # re-auth if auth.json older than this FRESH_AUTH="${FORCE_AUTH:-0}" DO_ENCODE=1 for arg in "$@"; do case "$arg" in --fresh-auth) FRESH_AUTH=1 ;; --no-encode) DO_ENCODE=0 ;; -h|--help) sed -n '3,18p' "$0" exit 0 ;; *) echo "Unknown arg: $arg" >&2; exit 2 ;; esac done cd "$(dirname "$0")" # -- helpers ------------------------------------------------------------------ say() { printf '\n[render] %s\n' "$*"; } fail() { printf '\n[render] FAIL: %s\n' "$*" >&2; exit 1; } http_code() { curl -s -o /dev/null -w "%{http_code}" --connect-timeout 3 --max-time 5 "$1" || echo "000" } wait_for() { local url="$1" desc="$2" timeout="${3:-90}" say "Waiting for $desc ($url)" for i in $(seq 1 "$timeout"); do if [ "$(http_code "$url")" = "200" ]; then say " ready after ${i}s" return 0 fi sleep 1 done fail "$desc not reachable after ${timeout}s" } # -- stack health ------------------------------------------------------------- say "Checking stack health" fe_code="$(http_code "$APP_URL/")" api_code="$(http_code "$API_URL/api/features")" pb_code="$(http_code "$PB_URL/api/health")" echo " frontend=$fe_code api=$api_code pocketbase=$pb_code" if [ "$fe_code" != "200" ] || [ "$pb_code" != "200" ]; then fail "Stack down. From the repo root run: docker compose up -d" fi if [ "$api_code" != "200" ]; then wait_for "$API_URL/api/features" "Rust API" 120 fi # -- node deps ---------------------------------------------------------------- if [ ! -d node_modules ]; then say "Installing npm deps" npm install --no-audit --no-fund fi # Chromium binary lives in Playwright's cache; install if missing. if ! npx --no-install playwright --version >/dev/null 2>&1 \ || [ ! -d "$HOME/.cache/ms-playwright" ] \ || ! find "$HOME/.cache/ms-playwright" -maxdepth 1 -name "chromium-*" -print -quit | grep -q .; then say "Installing Playwright Chromium" npx playwright install chromium fi # -- build -------------------------------------------------------------------- say "Compiling TypeScript" ./node_modules/.bin/tsc # -- auth --------------------------------------------------------------------- need_auth=0 if [ "$FRESH_AUTH" = "1" ] || [ ! -f auth.json ]; then need_auth=1 else # File mtime check, portable: if older than TTL, refresh. if [ "$(find auth.json -mmin "+$((AUTH_TTL_HOURS * 60))" -print 2>/dev/null)" ]; then say "auth.json is older than ${AUTH_TTL_HOURS}h, will refresh" need_auth=1 fi fi if [ "$need_auth" = "1" ]; then say "Minting fresh auth.json (user: $PB_EMAIL)" PB_URL="$PB_URL" PB_EMAIL="$PB_EMAIL" PB_PASSWORD="$PB_PASSWORD" \ APP_URL="$APP_URL" \ node dist/auth.js else say "Reusing existing auth.json" fi # -- record ------------------------------------------------------------------- say "Recording" mkdir -p output # Wipe last run's leaking artifacts so the rename step picks up *this* run. rm -f output/recording.webm output/recording.mp4 output/page@*.webm output/page@*.webm.untrimmed APP_URL="$APP_URL" MAX_DURATION_S="$MAX_DURATION_S" node dist/record.js if [ ! -s output/recording.webm ]; then fail "recording.webm missing or empty" fi # -- encode ------------------------------------------------------------------- if [ "$DO_ENCODE" = "1" ]; then if ! command -v ffmpeg >/dev/null 2>&1; then fail "ffmpeg not on PATH; rerun with --no-encode if you only need the WebM" fi say "Encoding to MP4" ffmpeg -y -loglevel warning -i output/recording.webm \ -c:v libx264 -pix_fmt yuv420p -crf 18 -movflags +faststart \ output/recording.mp4 fi # -- report ------------------------------------------------------------------- say "Done" if command -v ffprobe >/dev/null 2>&1; then for f in output/recording.webm output/recording.mp4; do [ -f "$f" ] || continue dur=$(ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "$f") size=$(stat -c '%s' "$f" 2>/dev/null || stat -f '%z' "$f") printf ' %s %ss %s bytes\n' "$f" "$(printf '%.2f' "$dur")" "$size" done else ls -la output/recording.* 2>/dev/null || true fi