worldbox-observer-mod/scripts/worldbox-ctl.sh

153 lines
4 KiB
Bash
Executable file

#!/usr/bin/env bash
# Canonical WorldBox process control for IdleSpectator agents.
# Always run this script (or harness-run.sh) with unrestricted / `all` permissions.
# Never invent ad-hoc pkill -f patterns that include the worldbox path in the
# agent shell command line - that can kill the agent itself.
#
# Usage:
# ./scripts/worldbox-ctl.sh status
# ./scripts/worldbox-ctl.sh start
# ./scripts/worldbox-ctl.sh stop
# ./scripts/worldbox-ctl.sh restart # stop + start + wait for IdleSpectator ready
set -euo pipefail
STEAM_APP_ID="${WORLDBOX_APP_ID:-1206560}"
LOG="${WORLDBOX_LOG:-$HOME/.config/unity3d/mkarpenko/WorldBox/Player.log}"
BOOT_TIMEOUT_SEC="${HARNESS_BOOT_TIMEOUT:-180}"
LAUNCH_LOG="${WORLDBOX_LAUNCH_LOG:-/tmp/idle-spectator-harness-launch.log}"
worldbox_pids() {
# Match the real game binary only (process name "worldbox").
# Do NOT use pkill -f with a path string that also appears in the calling shell.
ps -C worldbox -o pid= 2>/dev/null | tr -s ' ' | sed '/^$/d' || true
}
worldbox_running() {
local pids
pids="$(worldbox_pids)"
[[ -n "${pids// /}" ]]
}
mod_ready() {
[[ -f "$LOG" ]] || return 1
if rg -q "error CS" "$LOG" 2>/dev/null \
&& rg -q "Compile Mod IdleSpectator|IdleSpectator.*error CS|error CS.*IdleSpectator" "$LOG" 2>/dev/null; then
return 2
fi
rg -q "\[IdleSpectator\]: Harmony patches applied" "$LOG" 2>/dev/null
}
cmd_status() {
if worldbox_running; then
echo "WorldBox running pids=$(worldbox_pids | tr '\n' ' ')"
if mod_ready; then
echo "IdleSpectator: ready (Harmony patches applied)"
else
echo "IdleSpectator: not ready yet (check Player.log)"
fi
return 0
fi
echo "WorldBox not running"
return 1
}
cmd_stop() {
if ! worldbox_running; then
echo "WorldBox already stopped"
return 0
fi
echo "Stopping WorldBox ..."
# killall by process name - safe for agent shells (no -f path match).
killall -q worldbox 2>/dev/null || true
local i
for i in $(seq 1 30); do
if ! worldbox_running; then
echo "WorldBox stopped"
return 0
fi
sleep 0.5
done
echo "WorldBox still running after killall; sending SIGKILL ..." >&2
killall -q -9 worldbox 2>/dev/null || true
sleep 1
if worldbox_running; then
echo "FAILED to stop WorldBox pids=$(worldbox_pids | tr '\n' ' ')" >&2
return 1
fi
echo "WorldBox stopped"
}
cmd_start() {
if worldbox_running; then
echo "WorldBox already running"
return 0
fi
echo "Starting WorldBox (steam app $STEAM_APP_ID) ..."
echo "===== WORLDBOX-CTL START $(date -Iseconds) =====" >>"$LAUNCH_LOG" 2>/dev/null || true
nohup steam -applaunch "$STEAM_APP_ID" >>"$LAUNCH_LOG" 2>&1 &
disown || true
local deadline=$((SECONDS + BOOT_TIMEOUT_SEC))
local saw=0
while (( SECONDS < deadline )); do
if worldbox_running; then
saw=1
local ready_rc=0
mod_ready || ready_rc=$?
if (( ready_rc == 0 )); then
echo "WorldBox + IdleSpectator ready"
return 0
fi
if (( ready_rc == 2 )); then
echo "IdleSpectator compile failed - see $LOG" >&2
rg -n "error CS|Compile Mod IdleSpectator" "$LOG" 2>/dev/null | tail -n 40 >&2 || true
return 1
fi
printf '.'
else
printf 'o'
fi
sleep 2
done
echo >&2
if (( saw == 0 )); then
echo "Timed out waiting for WorldBox process (${BOOT_TIMEOUT_SEC}s)" >&2
else
echo "Timed out waiting for IdleSpectator ready (${BOOT_TIMEOUT_SEC}s)" >&2
fi
tail -n 40 "$LOG" 2>/dev/null >&2 || true
return 1
}
cmd_restart() {
cmd_stop
sleep 2
cmd_start
}
usage() {
cat <<EOF
Usage: $0 {status|start|stop|restart}
Canonical WorldBox control for IdleSpectator.
Always invoke with unrestricted / all permissions (never from the Cursor sandbox).
EOF
}
main() {
local op="${1:-}"
case "$op" in
status) cmd_status ;;
start) cmd_start ;;
stop) cmd_stop ;;
restart) cmd_restart ;;
-h|--help|help|"") usage; exit 2 ;;
*)
echo "Unknown op: $op" >&2
usage
exit 2
;;
esac
}
main "$@"