anvilsign in

collin/anvil

1#!/bin/sh
2# PID 1 of an agent-session container.
3#
4# Starts the agent under a detached tmux session, tees a byte-exact transcript,
5# and then blocks until that session ends — so the container's lifetime is the
6# agent's lifetime, and `docker wait` is a valid completion signal.
7#
8# Every browser attach is a SEPARATE `docker exec … tmux attach`, so a dropped
9# websocket kills only that client. That is the reason tmux is here at all.
10#
11# Usage: anvil-session <command> [args...]
12set -eu
13
14SESSION="${ANVIL_TMUX_SESSION:-agent}"
15TRANSCRIPT="${ANVIL_TRANSCRIPT:-/tmp/anvil-transcript}"
16WORKDIR="${ANVIL_WORKDIR:-/home/ubuntu/workspace}"
17EXIT_FILE="${ANVIL_EXIT_FILE:-/tmp/anvil-exit}"
18CMD_FILE="${ANVIL_CMD_FILE:-/tmp/anvil-cmd.sh}"
19
20if [ "$#" -eq 0 ]; then
21 echo "anvil-session: no command given" >&2
22 exit 64
23fi
24
25# Single-quote one argument for safe re-parsing by /bin/sh.
26quote() {
27 printf "'%s'" "$(printf '%s' "$1" | sed "s/'/'\\\\''/g")"
28}
29
30: > "$TRANSCRIPT"
31rm -f "$EXIT_FILE"
32
33# Generate a script rather than nesting quotes inside tmux's command string:
34# tmux hands that string to `sh -c`, so an argument containing spaces or quotes
35# (an autonomous session's prompt, most obviously) would otherwise re-split.
36{
37 printf '#!/bin/sh\n'
38 printf 'cd %s || exit 1\n' "$(quote "$WORKDIR")"
39 for arg in "$@"; do
40 printf '%s ' "$(quote "$arg")"
41 done
42 printf '\n'
43 printf 'printf %%s $? > %s\n' "$(quote "$EXIT_FILE")"
44} > "$CMD_FILE"
45chmod 0755 "$CMD_FILE"
46
47tmux -f /etc/anvil/tmux.conf new-session -d -s "$SESSION" -c "$WORKDIR" \
48 "sh $CMD_FILE"
49
50# Tee everything the pane emits into the transcript. `-o` means "only if not
51# already piping", so a re-invocation is harmless.
52tmux pipe-pane -o -t "$SESSION" "cat >> '$TRANSCRIPT'"
53
54# Block until the session is gone. Polling rather than `tmux wait-for` because
55# the session can also be killed from outside (an operator stop, a sweep, an
56# attached user typing `exit`), and has-session covers every one of those
57# without needing a cooperating signaller.
58while tmux has-session -t "$SESSION" 2>/dev/null; do
59 if [ -f "$EXIT_FILE" ]; then
60 # The command finished; the pane lingers because of remain-on-exit.
61 # Leave the final screen readable for a moment, then wind up.
62 sleep "${ANVIL_LINGER_SECS:-5}"
63 tmux kill-session -t "$SESSION" 2>/dev/null || true
64 break
65 fi
66 sleep 1
67done
68
69exit "$(cat "$EXIT_FILE" 2>/dev/null || echo 0)"