271 lines
9.1 KiB
Bash
Executable File
271 lines
9.1 KiB
Bash
Executable File
#!/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 |