session-tools/bin/remote-run
2026-04-25 22:13:25 -07:00

57 lines
1.6 KiB
Bash
Executable file

#!/bin/sh
# remote-run <host> <cmd...>
#
# Run a command on <host> inside a detached tmux session, stream output back,
# propagate exit code. If the local ssh dies mid-run, the tmux session keeps
# going on the remote — recover with:
# ssh <host> tmux ls
# ssh <host> tmux attach -t <session>
#
# <host> is whatever ssh accepts: a Host alias from ~/.ssh/config, a
# user@hostname, an IP, etc.
set -eu
if [ $# -lt 2 ]; then
echo "usage: $0 <host> <cmd...>" >&2
exit 2
fi
host=$1
shift
session="claude-$(whoami)-$$-$(date +%s)"
# Single-quote-escape the user command for safe embedding in remote bootstrap.
user_cmd=$*
quoted_cmd=$(printf %s "$user_cmd" | sed "s/'/'\\\\''/g")
# Remote bootstrap — runs the user command in its own bash subshell so that
# any `exit` or `set -e` inside it does NOT short-circuit our exit-capture.
remote_cmd=$(cat <<REMOTE
session='${session}'
log="/tmp/\${session}.log"
exitf="/tmp/\${session}.exit"
: > "\$log"
tmux new-session -d -s "\$session" "bash -c '${quoted_cmd}' > \$log 2>&1; echo \\\$? > \$exitf; tmux wait-for -S done-\$session" 2>/tmp/\${session}.tmuxerr
if [ \$? -ne 0 ]; then
echo "tmux failed to start session:" >&2
cat /tmp/\${session}.tmuxerr >&2
rm -f /tmp/\${session}.tmuxerr
exit 127
fi
rm -f /tmp/\${session}.tmuxerr
( tail -F "\$log" 2>/dev/null ) &
tail_pid=\$!
tmux wait-for done-\$session 2>/dev/null
sleep 0.2
kill \$tail_pid 2>/dev/null || true
wait \$tail_pid 2>/dev/null || true
code=\$(cat "\$exitf" 2>/dev/null || echo 1)
tmux kill-session -t "\$session" 2>/dev/null || true
rm -f "\$log" "\$exitf"
exit \$code
REMOTE
)
ssh "$host" "$remote_cmd"