#!/usr/bin/env bash # supervise.sh — one-shot test supervisor for the oko pipeline. # # Re-executes itself inside the oko-dev distrobox (binaries link container # OpenCV libs), builds all runtime services, generates/validates configs, # starts coordinator → terp → mofin → runCam in order, # and tears everything down on Ctrl-C. # # Usage: # ./supervise.sh [options] # run the full stack in foreground # ./supervise.sh stop # kill a stack left running elsewhere # # Options: # --no-camera skip runCam (pipeline-only test, no hardware needed) # --no-build reuse binaries from ./run/bin without rebuilding # --passphrase STRING enable end-to-end clip encryption (default: plaintext) # --auth-token STRING shared pipeline auth token (default: random per run) # --user NAME camera owner username (default: oko) # --camera-id ID camera identifier (default: front) # --threshold N mofin pixel-value sensitivity (default: 30) # # Matrix settings for coordinator come from OKO_MATRIX_HS, OKO_MATRIX_USER, # OKO_MATRIX_TOKEN_FILE (or OKO_MATRIX_TOKEN), OKO_CLIPS_ROOM, # OKO_DETECTIONS_ROOM, and OKO_VIEW_ROOM. set -Eeuo pipefail # ---------- re-enter inside the build container ---------- if [[ ! -f /run/.containerenv && ! -f /.dockerenv && -z "${container:-}" ]]; then DISTROBOX_BIN="$(command -v distrobox || echo /usr/bin/distrobox)" exec "$DISTROBOX_BIN" enter oko-dev -- "$(readlink -f "$0")" "$@" fi SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" cd "$SCRIPT_DIR" [[ "${SUPERVISE_DEBUG:-0}" == 1 ]] && set -x NO_CAMERA=0 NO_BUILD=0 PASSPHRASE="" AUTH_TOKEN="" USER_NAME="oko" CAMERA_ID="front" MOFIN_THRESHOLD=30 PBKDF2_ITERS=600000 VIDEO_WIDTH=0 VIDEO_HEIGHT=0 while [[ $# -gt 0 ]]; do case "$1" in --no-camera) NO_CAMERA=1 ;; --no-build) NO_BUILD=1 ;; --passphrase) PASSPHRASE="$2"; shift ;; --auth-token) AUTH_TOKEN="$2"; shift ;; --user) USER_NAME="$2"; shift ;; --camera-id) CAMERA_ID="$2"; shift ;; --threshold) MOFIN_THRESHOLD="$2"; shift ;; --pbkdf2-iters) PBKDF2_ITERS="$2"; shift ;; --video-width) VIDEO_WIDTH="$2"; shift ;; --video-height) VIDEO_HEIGHT="$2"; shift ;; stop) STOP=1 ;; *) echo "unknown option: $1" >&2; exit 2 ;; esac shift done : "${AUTH_TOKEN:=$(openssl rand -hex 16)}" : "${OKO_MATRIX_HS:=https://ayrc.online}" : "${OKO_MATRIX_USER:=@okobot:ayrc.online}" : "${OKO_MATRIX_TOKEN_FILE:=$SCRIPT_DIR/matrix.token}" : "${OKO_CLIPS_ROOM:=!0hCGPJph-odzFEOScsnAOBlPHSolX8rwbh3mxQNNUrg}" : "${OKO_DETECTIONS_ROOM:=!XqatfFvJJT6xq8B0p7b5NL2RYRCBS2Fa3ItFu8xV2gQ}" : "${OKO_VIEW_ROOM:=!wyLfOhunJTOzeXHFfAc6CRCCII7KlA1Pm2MkkFf40tE}" RUN_DIR="$SCRIPT_DIR/run" LOG_DIR="$RUN_DIR/logs" BIN_DIR="$RUN_DIR/bin" PID_FILE="$RUN_DIR/pids" if [[ "${STOP:-0}" == 1 ]]; then if [[ -f "$PID_FILE" ]]; then while read -r pid name; do [[ -z "$pid" ]] && continue if [[ "$name" == "supervise" ]]; then # killing the supervisor triggers its EXIT trap, which cascade- # kills every service group it manages. kill "$pid" 2>/dev/null && echo "stopped supervisor ($pid)" else kill -- -"$pid" 2>/dev/null && echo "stopped $name ($pid)" || echo "already dead: $name ($pid)" fi done < "$PID_FILE" sleep 1 while read -r pid name; do [[ -z "$pid" || "$name" == "supervise" ]] && continue if kill -0 "$pid" 2>/dev/null; then kill -9 -- -"$pid" 2>/dev/null echo "forced: $name ($pid)" fi done < "$PID_FILE" rm -f "$PID_FILE" else echo "no pidfile at $PID_FILE" >&2; exit 1 fi exit 0 fi # ---------- helpers ---------- log() { printf '\033[1;34m[supervise]\033[0m %s\n' "$*"; } die() { printf '\033[1;31m[supervise] ERROR:\033[0m %s\n' "$*" >&2; exit 1; } port_open() { (exec 3<>"/dev/tcp/127.0.0.1/$1") 2>/dev/null || return 1; exec 3>&- 3<&-; return 0; } wait_port() { local port="$1" name="$2" i for i in $(seq 1 50); do port_open "$port" && { log "$name is listening on :$port"; return 0; } sleep 0.2 done die "$name did not open port $port — check $LOG_DIR/$name.log" } declare -a CHILD_PIDS=() cleanup() { log "shutting down..." local pid for pid in "${CHILD_PIDS[@]:-}"; do [[ -n "$pid" ]] && kill -- -"$pid" 2>/dev/null done wait 2>/dev/null rm -f "$PID_FILE" log "all services stopped." } trap cleanup EXIT trap 'exit 130' INT trap 'exit 143' TERM start_service() { local name="$1" port="$2"; shift 2 log "starting $name ..." # setsid puts the service (and any bash -c wrapper) in its own process # group so stop/cleanup can kill the whole tree atomically with # `kill -- -PID`; a bare SIGTERM to a wrapper subshell would orphan it. setsid "$@" >>"$LOG_DIR/$name.log" 2>&1 & local pid=$! CHILD_PIDS+=("$pid") echo "$pid $name" >>"$PID_FILE" sleep 0.5 if ! kill -0 "$pid" 2>/dev/null; then tail -n 20 "$LOG_DIR/$name.log" >&2 die "$name exited immediately — see $LOG_DIR/$name.log" fi if [[ -n "$port" ]]; then wait_port "$port" "$name" fi } # ---------- preflight ---------- command -v go >/dev/null || die "required tool 'go' not found in container" mkdir -p "$BIN_DIR" "$LOG_DIR" if [[ -f "$PID_FILE" ]]; then while read -r pid _; do kill -0 "$pid" 2>/dev/null && die "stack already running (pid $pid). Use './supervise.sh stop' first." done < "$PID_FILE" rm -f "$PID_FILE" fi : > "$PID_FILE" for p in 8081 8082 8083; do if port_open "$p"; then die "port $p already in use by something outside this supervisor." fi done SERVICES=(coordinator terp mofin runCam) # ---------- build ---------- if [[ "$NO_BUILD" == 0 ]]; then log "building ${SERVICES[*]} ..." for svc in "${SERVICES[@]}"; do ( cd "$svc" && go build -o "$BIN_DIR/$svc" . ) || die "build failed: $svc" done log "build complete." else for svc in "${SERVICES[@]}"; do [[ -x "$BIN_DIR/$svc" ]] || die "--no-build set but $BIN_DIR/$svc missing; run a build first." done fi # ---------- ensure configurations ---------- log "ensuring configurations ..." TERP_DIR="$RUN_DIR/terp" mkdir -p "$TERP_DIR/classifiers" for cascade_src in /usr/share/opencv4/haarcascades /usr/share/OpenCV/haarcascades; do if [[ -d "$cascade_src" ]]; then cp -u "$cascade_src"/haarcascade_fullbody.xml "$TERP_DIR/classifiers/" 2>/dev/null cp -u "$cascade_src"/haarcascade_frontalface_default.xml "$TERP_DIR/classifiers/" 2>/dev/null break fi done ls "$TERP_DIR/classifiers/"*.xml >/dev/null 2>&1 || die "no Haar cascades found for terp (looked in /usr/share/opencv4/haarcascades)" SOURCE_DATA="${USER_NAME}--${CAMERA_ID}" # ---------- launch (dependency order) ---------- echo "$$ supervise" >>"$PID_FILE" start_service coordinator 8082 "$BIN_DIR/coordinator" \ -listen ":8082" \ -auth-token "$AUTH_TOKEN" \ -passphrase "$PASSPHRASE" \ -pbkdf2-iters "$PBKDF2_ITERS" \ -matrix-homeserver "${OKO_MATRIX_HS:-}" \ -matrix-user "${OKO_MATRIX_USER:-}" \ -matrix-token-file "${OKO_MATRIX_TOKEN_FILE:-}" \ -clips-room "${OKO_CLIPS_ROOM:-}" \ -detections-room "${OKO_DETECTIONS_ROOM:-}" \ -view-room "${OKO_VIEW_ROOM:-}" start_service terp 8081 bash -c "cd '$TERP_DIR' && exec '$BIN_DIR/terp' \ -listen ':8081' \ -sendoff 'localhost:8082' \ -auth-token '$AUTH_TOKEN' \ -classifiers 'classifiers/*.xml' \ -motion-percent 0 \ -passphrase '$PASSPHRASE' \ -pbkdf2-iters '$PBKDF2_ITERS'" start_service mofin 8083 "$BIN_DIR/mofin" \ -listen ":8083" \ -terp "localhost:8081" \ -auth-token "$AUTH_TOKEN" \ -threshold "$MOFIN_THRESHOLD" \ -passphrase "$PASSPHRASE" \ -pbkdf2-iters "$PBKDF2_ITERS" RUN_CAM=1 if [[ "$NO_CAMERA" == 0 ]]; then ls /dev/video* >/dev/null 2>&1 || { log "WARNING: no /dev/video* devices visible; skipping runCam (pipeline-only mode)"; RUN_CAM=0; } else RUN_CAM=0 fi if [[ "$RUN_CAM" == 1 ]]; then start_service runCam "" "$BIN_DIR/runCam" \ -mofin "localhost:8083" \ -auth-token "$AUTH_TOKEN" \ -min-cam 0 -max-cam 10 \ -video-width "$VIDEO_WIDTH" -video-height "$VIDEO_HEIGHT" \ -user "$USER_NAME" \ -camera-id "$CAMERA_ID" \ -passphrase "$PASSPHRASE" \ -pbkdf2-iters "$PBKDF2_ITERS" fi # ---------- status ---------- echo log "=== oko test stack is up ===" printf ' %-18s %-12s %s\n' SERVICE PORT LOG printf ' %-18s %-12s %s\n' coordinator 8082 "$LOG_DIR/coordinator.log" printf ' %-18s %-12s %s\n' terp 8081 "$LOG_DIR/terp.log" printf ' %-18s %-12s %s\n' mofin 8083 "$LOG_DIR/mofin.log" if [[ "$RUN_CAM" == 1 ]]; then printf ' %-18s %-12s %s\n' runCam — "$LOG_DIR/runCam.log" fi echo log "motion clips land in the configured Matrix rooms (OKO_CLIPS_ROOM / OKO_DETECTIONS_ROOM)" log "Ctrl-C to stop everything." wait