348 lines
8.4 KiB
Bash
Executable file
348 lines
8.4 KiB
Bash
Executable file
#!/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
|