chobit/run

349 lines
8.4 KiB
Text
Raw Normal View History

#!/usr/bin/env bash
set -euo pipefail
ROOT="$(cd "$(dirname "$0")" && pwd)"
GODOT_DIR="$ROOT/godot-desktop"
TRAY_DIR="$ROOT/services/tray"
BRIDGE_DIR="$ROOT/services/bridge"
GODOT="flatpak run --user org.godotengine.Godot"
PIDFILE="$ROOT/.godot.pid"
TRAY_PIDFILE="$ROOT/.tray.pid"
BRIDGE_PIDFILE="$ROOT/.bridge.pid"
# Load .env if present
if [ -f "$ROOT/.env" ]; then
set -o allexport
# shellcheck disable=SC1091
source "$ROOT/.env"
set +o allexport
fi
REQUIRED_SERVICES=(
"model-boss-coordinator:model-boss"
"chatterbox-tts:speech-synthesis"
)
check_services() {
local all_ok=0
for entry in "${REQUIRED_SERVICES[@]}"; do
local unit="${entry%%:*}"
local label="${entry##*:}"
if systemctl --user is-active --quiet "$unit" 2>/dev/null; then
echo " ✓ $label ($unit)"
else
echo " ✗ $label ($unit) — not running"
all_ok=1
fi
done
return $all_ok
}
ensure_services() {
echo "Checking required services..."
if check_services; then
return 0
fi
echo ""
echo "Starting missing services..."
for entry in "${REQUIRED_SERVICES[@]}"; do
local unit="${entry%%:*}"
local label="${entry##*:}"
if ! systemctl --user is-active --quiet "$unit" 2>/dev/null; then
if systemctl --user start "$unit" 2>/dev/null; then
echo " ✓ Started $label ($unit)"
else
echo " ⚠ Failed to start $label ($unit) — continuing anyway"
fi
fi
done
# Brief settle time for services to initialize
sleep 2
echo ""
echo "Service status after startup:"
check_services || true
}
cmd_start() {
if [ -f "$PIDFILE" ] && kill -0 "$(cat "$PIDFILE")" 2>/dev/null; then
echo "Already running (pid $(cat "$PIDFILE"))"
return 1
fi
ensure_services
echo ""
# 1. Bridge (pub/sub relay for vision events)
if [ -f "$BRIDGE_DIR/chobit_bridge.py" ]; then
python3 "$BRIDGE_DIR/chobit_bridge.py" &
echo $! > "$BRIDGE_PIDFILE"
echo "Started bridge (pid $!)"
fi
# 2. Godot + tray
setsid $GODOT --path "$GODOT_DIR" &
echo $! > "$PIDFILE"
echo "Started Godot (pid $!)"
if [ -f "$TRAY_DIR/chobit_tray.py" ]; then
python3 "$TRAY_DIR/chobit_tray.py" &
echo $! > "$TRAY_PIDFILE"
echo "Started tray (pid $!)"
fi
}
cmd_dev() {
if [ -f "$PIDFILE" ] && kill -0 "$(cat "$PIDFILE")" 2>/dev/null; then
echo "Already running (pid $(cat "$PIDFILE"))"
return 1
fi
ensure_services
echo ""
# 1. Bridge
if [ -f "$BRIDGE_DIR/chobit_bridge.py" ]; then
python3 "$BRIDGE_DIR/chobit_bridge.py" &
echo $! > "$BRIDGE_PIDFILE"
echo "Started bridge (pid $!)"
fi
# 2. Godot + multi-tray (dev mode)
setsid $GODOT --path "$GODOT_DIR" &
echo $! > "$PIDFILE"
echo "Started Godot (pid $!)"
if [ -f "$TRAY_DIR/dev_trays.py" ]; then
python3 "$TRAY_DIR/dev_trays.py" &
echo $! > "$TRAY_PIDFILE"
echo "Started dev trays (pid $!)"
fi
}
cmd_stop() {
# Stop tray
if [ -f "$TRAY_PIDFILE" ]; then
local tray_pid
tray_pid=$(cat "$TRAY_PIDFILE" 2>/dev/null)
if [ -n "$tray_pid" ] && kill -0 "$tray_pid" 2>/dev/null; then
kill "$tray_pid" 2>/dev/null && echo "Stopped tray (pid $tray_pid)" || true
fi
rm -f "$TRAY_PIDFILE"
fi
pgrep -f "chobit_tray\\.py" | while read -r cpid; do
kill "$cpid" 2>/dev/null
done || true
# Stop Godot
local stopped=0
if [ -f "$PIDFILE" ]; then
local pid
pid=$(cat "$PIDFILE" 2>/dev/null)
if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
kill -- -"$pid" 2>/dev/null || kill "$pid" 2>/dev/null || true
pkill -P "$pid" 2>/dev/null || true
wait "$pid" 2>/dev/null || true
echo "Stopped Godot (pid $pid)"
stopped=1
fi
rm -f "$PIDFILE"
fi
# Sweep stale Godot processes
local sweep_count=0
for cpid in $(pgrep -f "godot-bin.*(--path godot|@chobit/godot)" 2>/dev/null); do
kill "$cpid" 2>/dev/null && sweep_count=$((sweep_count + 1))
done
for cpid in $(pgrep -f "bwrap.*-- godot --path godot" 2>/dev/null); do
kill "$cpid" 2>/dev/null
done
if [ "$stopped" -eq 0 ] && [ "$sweep_count" -eq 0 ]; then
echo "Godot not running"
elif [ "$sweep_count" -gt 0 ]; then
echo "Swept $sweep_count stale Godot process(es)"
fi
# Stop bridge last — Godot may flush state on exit
if [ -f "$BRIDGE_PIDFILE" ]; then
local bridge_pid
bridge_pid=$(cat "$BRIDGE_PIDFILE" 2>/dev/null)
if [ -n "$bridge_pid" ] && kill -0 "$bridge_pid" 2>/dev/null; then
kill "$bridge_pid" 2>/dev/null && echo "Stopped bridge (pid $bridge_pid)" || true
fi
rm -f "$BRIDGE_PIDFILE"
fi
pgrep -f "chobit_bridge\\.py" | while read -r cpid; do
kill "$cpid" 2>/dev/null
done || true
}
cmd_launch() {
if [ "${ENVIRONMENT:-}" = "development" ]; then
cmd_dev
else
cmd_start
fi
}
cmd_restart() {
cmd_stop
sleep 2
cmd_launch
}
cmd_verify() {
local failed=0
echo "=== Shared Source: Lint ==="
if (cd "$GODOT_DIR" && gdlint src/); then
echo "PASS"
else
echo "FAIL"
failed=1
fi
echo ""
echo "=== Desktop Platform: Lint ==="
if (cd "$GODOT_DIR" && gdlint platform/); then
echo "PASS"
else
echo "FAIL"
failed=1
fi
echo ""
echo "=== Shared Source: Format Check ==="
if (cd "$GODOT_DIR" && gdformat --check src/ 2>&1); then
echo "PASS"
else
echo "FAIL (run: cd godot-desktop && gdformat src/)"
failed=1
fi
echo ""
echo "=== Desktop Platform: Format Check ==="
if (cd "$GODOT_DIR" && gdformat --check platform/ 2>&1); then
echo "PASS"
else
echo "FAIL (run: cd godot-desktop && gdformat platform/)"
failed=1
fi
echo ""
echo "=== Godot Import ==="
local import_errors
import_errors=$($GODOT --headless --path "$GODOT_DIR" --import 2>&1 | grep -iE "error|fail" || true)
if [ -z "$import_errors" ]; then
echo "PASS"
else
echo "$import_errors"
echo "FAIL"
failed=1
fi
echo ""
if [ "$failed" -eq 0 ]; then
echo "All checks passed."
else
echo "Verification failed."
return 1
fi
}
cmd_editor() {
$GODOT --editor --path "$GODOT_DIR"
}
cmd_mobile_editor() {
$GODOT --editor --path "$ROOT/godot-mobile"
}
cmd_screenshot() {
$GODOT --path "$GODOT_DIR" --script tools/screenshot.gd 2>&1 | tail -1
}
cmd_test() {
echo "Running unit tests..."
$GODOT --headless --path "$GODOT_DIR" --script tests/test_runner.gd 2>&1
}
SYSTEMD_UNIT="$ROOT/infrastructure/services/chobit.service"
SYSTEMD_DEST="$HOME/.config/systemd/user/chobit.service"
cmd_install_service() {
if [ ! -f "$SYSTEMD_UNIT" ]; then
echo "Error: $SYSTEMD_UNIT not found"
return 1
fi
mkdir -p "$(dirname "$SYSTEMD_DEST")"
cp "$SYSTEMD_UNIT" "$SYSTEMD_DEST"
systemctl --user daemon-reload
echo "Installed chobit.service → $SYSTEMD_DEST"
echo ""
echo "Enable with: systemctl --user enable chobit"
echo "Start with: systemctl --user start chobit"
echo ""
echo "This will auto-start model-boss-coordinator and chatterbox-tts via Wants= dependency."
}
cmd_status() {
echo "=== Chobit Services ==="
echo ""
echo "External dependencies:"
check_services || true
echo ""
echo "Local processes:"
if [ -f "$PIDFILE" ] && kill -0 "$(cat "$PIDFILE")" 2>/dev/null; then
echo " ✓ Godot (pid $(cat "$PIDFILE"))"
else
echo " ✗ Godot — not running"
fi
if [ -f "$TRAY_PIDFILE" ] && kill -0 "$(cat "$TRAY_PIDFILE")" 2>/dev/null; then
echo " ✓ Tray (pid $(cat "$TRAY_PIDFILE"))"
else
echo " ✗ Tray — not running"
fi
if [ -f "$BRIDGE_PIDFILE" ] && kill -0 "$(cat "$BRIDGE_PIDFILE")" 2>/dev/null; then
echo " ✓ Bridge (pid $(cat "$BRIDGE_PIDFILE"))"
else
echo " ✗ Bridge — not running"
fi
}
case "${1:-}" in
""|start) cmd_launch ;;
dev) cmd_dev ;;
stop) cmd_stop ;;
restart) cmd_restart ;;
status) cmd_status ;;
install-service) cmd_install_service ;;
verify) cmd_verify ;;
test) cmd_test ;;
editor) cmd_editor ;;
mobile-editor) cmd_mobile_editor ;;
screenshot) cmd_screenshot ;;
*)
echo "Usage: ./run [command]"
echo ""
echo "Commands:"
echo " (none), start Launch bridge + companion + tray (desktop)"
echo " dev Launch with dev multi-tray [Bridge:dev][Chat:dev][Char:dev][Speech:dev][Face:dev][Main]"
echo " stop Stop everything"
echo " restart Stop then start"
echo " status Check service status (local + dependencies)"
echo " install-service Install systemd user unit (auto-starts dependencies)"
echo " verify Run lint, format check, and Godot import"
echo " test Run unit tests (headless)"
echo " editor Open Godot desktop editor"
echo " mobile-editor Open Godot mobile editor"
echo " screenshot Capture a screenshot"
exit 1
;;
esac