Sanad_Package_1/p1ctl.sh

70 lines
3.0 KiB
Bash
Executable File

#!/usr/bin/env bash
# p1ctl.sh — run / stop Sanad Package 1 (Basic Communication) on the robot
# in the existing gemini_sdk conda env (dev mode; no Docker required).
#
# ./p1ctl.sh start # launch P1 dashboard on :8011 (coexists with Sanad :8000)
# ./p1ctl.sh stop # stop P1
# ./p1ctl.sh restart
# ./p1ctl.sh status # process + /api/health
# ./p1ctl.sh logs [N] # tail N lines of the P1 log
#
# Self-contained: runs against the vendored engine in ./vendor — no sibling
# Sanad/ or Packages/ checkout needed. Overridable env: SANAD_P1_PY,
# SANAD_DASHBOARD_PORT (8011), SANAD_AUDIO_PROFILE (builtin), SANAD_DDS_INTERFACE (eth0),
# SANAD_LICENSE / SANAD_PUBKEY.
set -u
PKG_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PY="${SANAD_P1_PY:-$HOME/miniconda3/envs/gemini_sdk/bin/python}"
PORT="${SANAD_DASHBOARD_PORT:-8011}"
APP="$PKG_DIR/app_p1.py"
LOG="$PKG_DIR/p1.log"
LIC="${SANAD_LICENSE:-$PKG_DIR/license/sanad.lic}"; [ -f "$LIC" ] || LIC="$PKG_DIR/license/sanad.lic.example"
_start() {
if pgrep -f app_p1.py >/dev/null 2>&1; then
echo "P1 already running on :$PORT"; return 0
fi
[ -f "$APP" ] || { echo "ERROR: $APP not found (deploy first)"; return 1; }
cd "$PKG_DIR"
export SANAD_APP_DIR="$PKG_DIR/vendor" \
SANAD_LICENSE="$LIC" \
SANAD_PUBKEY="${SANAD_PUBKEY:-$PKG_DIR/license/pubkey.ed25519}" \
SANAD_P1_STATIC="$PKG_DIR/static" \
PYTHONPATH="$PKG_DIR/vendor" \
SANAD_DASHBOARD_PORT="$PORT" SANAD_DASHBOARD_HOST="0.0.0.0" \
SANAD_VOICE_BRAIN="gemini" \
SANAD_AUDIO_PROFILE="${SANAD_AUDIO_PROFILE:-builtin}" \
SANAD_DDS_INTERFACE="${SANAD_DDS_INTERFACE:-eth0}" \
PYTHONUNBUFFERED=1
[ -f /usr/lib/aarch64-linux-gnu/libgomp.so.1 ] && export LD_PRELOAD=/usr/lib/aarch64-linux-gnu/libgomp.so.1
nohup "$PY" "$APP" > "$LOG" 2>&1 &
sleep 3
if pgrep -f app_p1.py >/dev/null 2>&1; then
echo "P1 started -> http://$(hostname -I | awk '{print $1}'):$PORT (log: $LOG)"
else
echo "P1 failed to start. Last log lines:"; tail -20 "$LOG"
fi
}
_stop() {
pgrep -f app_p1.py >/dev/null 2>&1 || { echo "P1 was not running."; return 0; }
pkill -f app_p1.py 2>/dev/null # SIGTERM (graceful)
# uvicorn waits for open websockets (e.g. a browser on /ws/logs) — force after 8s
for _ in $(seq 1 8); do pgrep -f app_p1.py >/dev/null 2>&1 || break; sleep 1; done
pgrep -f app_p1.py >/dev/null 2>&1 && pkill -9 -f app_p1.py 2>/dev/null
sleep 1
pgrep -f app_p1.py >/dev/null 2>&1 && echo "P1 still running (could not kill)." || echo "P1 stopped."
}
_status() { if pgrep -af app_p1.py; then echo -n "health: "; curl -s --max-time 4 "http://127.0.0.1:$PORT/api/health"; echo; else echo "P1 not running."; fi; }
_logs() { tail -n "${1:-40}" "$LOG" 2>/dev/null || echo "no log at $LOG"; }
case "${1:-}" in
start) _start ;;
stop) _stop ;;
restart) _stop; sleep 2; _start ;;
status) _status ;;
logs) shift; _logs "${1:-40}" ;;
*) echo "usage: $0 {start|stop|restart|status|logs [N]}"; exit 2 ;;
esac