Files

271 lines
9.1 KiB
Bash
Raw Permalink Normal View History

2026-09-09 21:44:05 -05:00
#!/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