#!/bin/sh # run-debug.sh — launch xenomorphic in an isolated debug sandbox. # # The app, its daemon, and all persisted state resolve one directory through # xeno::paths::configDir(): ${XDG_CONFIG_HOME:-$HOME/.config}/xenomorphic. The # app passes its environment to the daemon it spawns. Pointing XDG_CONFIG_HOME # at a throwaway dir therefore gives this instance its own daemon, daemon.json, # session.json, settings.json, and workspaces/. HOME stays untouched, so the # shells this instance spawns keep the real dotfiles and git/jj identity. # # Usage: run-debug.sh [args...] # # The sandbox is wiped before and after every run. The daemon outlives the app # by design (that is how panes survive a restart), so both ends send SIGTERM to # the sandbox daemon recorded in daemon.json, then remove the sandbox. Without # that, each run would leak a detached daemon and its shells. set -eu binary=$1 shift sandbox=$1 shift if [ ! -x "$binary" ]; then echo "run-debug: not executable: $binary" >&2 exit 1 fi # Stop the sandbox daemon if a previous run left one behind. Match the pid in # daemon.json against a live xenomorphicd so a recycled pid is never killed. stop_sandbox_daemon() { disc="$sandbox/xenomorphic/daemon.json" [ -f "$disc" ] || return 0 pid=$(sed -n 's/.*"pid"[[:space:]]*:[[:space:]]*\([0-9][0-9]*\).*/\1/p' \ "$disc" | head -n 1) [ -n "$pid" ] || return 0 if ps -p "$pid" -o comm= 2>/dev/null | grep -q 'xenomorphicd'; then kill "$pid" 2>/dev/null || true # SIGTERM runs the daemon's graceful daemonDown path; give it a moment # before the sandbox disappears under it. i=0 while [ "$i" -lt 50 ] && kill -0 "$pid" 2>/dev/null; do sleep 0.1 i=$((i + 1)) done fi } cleanup() { stop_sandbox_daemon rm -rf "$sandbox" } # Ctrl-C reaches the whole foreground process group, so reap the daemon even if # the app is killed before it returns. trap 'cleanup; exit 130' INT TERM cleanup mkdir -p "$sandbox" status=0 XDG_CONFIG_HOME="$sandbox" "$binary" "$@" || status=$? cleanup exit "$status"