debug-remote-attach.sh RawHistoryFolder
1#!/usr/bin/env bash
2set -euo pipefail
3
4usage() {
5 cat <<'USAGE' >&2
6Usage: scripts/debug-remote-attach.sh [--mode ssh|quic|default] [--session NAME] [--duration 8s] [--vev-bin PATH] user@host
7
8Runs a real remote attach smoke/debug attempt under a PTY, captures local and
9remote vev versions, and stores local/remote log tails around the attempt.
10
11Examples:
12 scripts/debug-remote-attach.sh user@example-host
13 scripts/debug-remote-attach.sh --session work user@example-host
14 scripts/debug-remote-attach.sh --mode quic user@example-host
15 go build -o /tmp/vev-debug . && scripts/debug-remote-attach.sh --vev-bin /tmp/vev-debug user@example-host
16
17Notes:
18 - The command times out intentionally if attach succeeds and stays interactive.
19 - Exit 124 from timeout is treated as a likely successful attach if no early
20 vev/ssh error is captured; inspect the bundle for confirmation.
21 - The remote host must be reachable by SSH for log collection.
22 - The attach attempt unsets VEV so the smoke test can run from inside vev.
23 - --mode ssh|quic re-registers the host (vev host rm, then vev host add
24 --transport MODE) before attaching; default keeps the existing registration.
25USAGE
26}
27
28mode="default"
29session=""
30duration="8s"
31vev_bin="${VEV_BIN:-vev}"
32while [[ $# -gt 0 ]]; do
33 case "$1" in
34 --mode)
35 mode="${2:-}"; shift 2 ;;
36 --session)
37 session="${2:-}"; shift 2 ;;
38 --duration)
39 duration="${2:-}"; shift 2 ;;
40 --vev-bin)
41 vev_bin="${2:-}"; shift 2 ;;
42 -h|--help)
43 usage; exit 0 ;;
44 --)
45 shift; break ;;
46 -*)
47 echo "unknown option: $1" >&2; usage; exit 2 ;;
48 *)
49 break ;;
50 esac
51done
52
53if [[ $# -ne 1 ]]; then
54 usage
55 exit 2
56fi
57case "$mode" in
58 default|ssh|quic) ;;
59 *) echo "invalid --mode $mode (want default, ssh, or quic)" >&2; exit 2 ;;
60esac
61
62target="$1"
63attach_target="$target"
64if [[ -n "$session" ]]; then
65 attach_target="$target:$session"
66fi
67
68repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
69out_dir="${TMPDIR:-/tmp}/vev-remote-attach-$(date +%Y%m%d-%H%M%S)"
70mkdir -p "$out_dir"
71
72local_state="${XDG_STATE_HOME:-$HOME/.local/state}/vev"
73
74# All persisted and displayed command output passes through this filter. Keep
75# diagnostic counters while removing the target, local home, network addresses,
76# and common key material labels from bundles that may be shared.
77redact() {
78 sed -E \
79 -e "s|${target//\/\\/\\}|[target]|g" \
80 -e "s|${HOME//\/\\/\\}|[home]|g" \
81 -e 's/([[:alnum:]_.-]+@)?([[:alnum:]_.-]+\.)+[[:alpha:]]{2,}/[host]/g' \
82 -e 's/([0-9]{1,3}\.){3}[0-9]{1,3}/[address]/g' \
83 -e 's/([[:xdigit:]]{1,4}:){2,}[[:xdigit:]:]*/[address]/g' \
84 -e 's/(private[ _-]?key|ssh-rsa|ssh-ed25519|ecdsa-sha2-[^[:space:]]*)/[redacted-key]/Ig'
85}
86
87redact_file() {
88 local raw="$1" dest="$2"
89 redact <"$raw" >"$dest" || true
90 rm -f "$raw"
91}
92
93run_capture() {
94 local name="$1" raw_out raw_err status
95 shift
96 raw_out="$out_dir/.$name.out.raw"
97 raw_err="$out_dir/.$name.err.raw"
98 status=0
99 {
100 printf '$'
101 printf ' %q' "$@"
102 printf '\n'
103 "$@"
104 } >"$raw_out" 2>"$raw_err" || status=$?
105 redact_file "$raw_out" "$out_dir/$name.out"
106 redact_file "$raw_err" "$out_dir/$name.err"
107 if [[ "$status" -ne 0 ]]; then printf '%s\n' "$status" >"$out_dir/$name.exit"; fi
108}
109
110copy_local_logs() {
111 local label="$1" raw
112 mkdir -p "$out_dir/local-$label"
113 if [[ -d "$local_state" ]]; then
114 find "$local_state" -maxdepth 1 -type f -name 'vev*.log' -print0 2>/dev/null | while IFS= read -r -d '' f; do
115 raw="$out_dir/local-$label/.$(basename "$f").raw"
116 tail -n 200 "$f" >"$raw" || true
117 redact_file "$raw" "$out_dir/local-$label/$(basename "$f")"
118 done
119 fi
120}
121
122copy_remote_logs() {
123 local label="$1" raw_out="$out_dir/remote-$1/.logs.out.raw" raw_err="$out_dir/remote-$1/.logs.err.raw"
124 mkdir -p "$out_dir/remote-$label"
125 ssh -o BatchMode=yes -o ConnectTimeout=8 "$target" 'state="${XDG_STATE_HOME:-$HOME/.local/state}/vev"; if [ -d "$state" ]; then for f in "$state"/vev*.log; do [ -e "$f" ] || continue; echo "===== $f ====="; tail -n 200 "$f"; done; fi' \
126 >"$raw_out" 2>"$raw_err" || true
127 redact_file "$raw_out" "$out_dir/remote-$label/logs.out"
128 redact_file "$raw_err" "$out_dir/remote-$label/logs.err"
129}
130
131printf 'debug bundle: %s\n' "$out_dir"
132printf 'mode: %s\nduration: %s\n' "$mode" "$duration" >"$out_dir/summary.txt"
133
134raw_git="$out_dir/.git.raw"
135(
136 cd "$repo_root"
137 git rev-parse --show-toplevel
138 git rev-parse --short HEAD
139 git status --short
140) >"$raw_git" 2>&1 || true
141redact_file "$raw_git" "$out_dir/git.txt"
142
143run_capture local-version "$vev_bin" --version
144raw_remote_version_out="$out_dir/.remote-version.out.raw"
145raw_remote_version_err="$out_dir/.remote-version.err.raw"
146ssh -o BatchMode=yes -o ConnectTimeout=8 "$target" 'set -e; command -v vev; vev --version; pgrep -a vev || true; state="${XDG_STATE_HOME:-$HOME/.local/state}/vev"; echo "state=$state"' \
147 >"$raw_remote_version_out" 2>"$raw_remote_version_err" || echo "$?" >"$out_dir/remote-version.exit"
148redact_file "$raw_remote_version_out" "$out_dir/remote-version.out"
149redact_file "$raw_remote_version_err" "$out_dir/remote-version.err"
150
151copy_local_logs before
152copy_remote_logs before
153
154attach_log_raw="$out_dir/.attach.typescript.raw"
155attach_err_raw="$out_dir/.attach.err.raw"
156attach_stdout_raw="$out_dir/.attach.stdout.raw"
157cmd=("$vev_bin" attach "$attach_target")
158env_cmd=(env -u VEV)
159if [[ "$mode" != "default" ]]; then
160 # A host keeps the transport it was added with: switching is rm then add.
161 run_capture host-rm "${env_cmd[@]}" "$vev_bin" host rm "$target"
162 run_capture host-add "${env_cmd[@]}" "$vev_bin" host add --transport "$mode" "$target"
163fi
164
165set +e
166if command -v script >/dev/null 2>&1; then
167 printf -v attach_cmd '%q ' "${cmd[@]}"
168 "${env_cmd[@]}" timeout "$duration" script -q -e -c "$attach_cmd" "$attach_log_raw" >"$attach_stdout_raw" 2>"$attach_err_raw"
169else
170 "${env_cmd[@]}" timeout "$duration" "${cmd[@]}" >"$attach_stdout_raw" 2>"$attach_err_raw"
171fi
172attach_status=$?
173set -e
174redact_file "$attach_stdout_raw" "$out_dir/attach.stdout"
175redact_file "$attach_err_raw" "$out_dir/attach.err"
176if [[ -e "$attach_log_raw" ]]; then redact_file "$attach_log_raw" "$out_dir/attach.typescript"; fi
177printf '%s\n' "$attach_status" >"$out_dir/attach.exit"
178
179copy_local_logs after
180copy_remote_logs after
181
182{
183 echo "debug bundle: $out_dir"
184 echo "attach exit: $attach_status"
185 echo "local version:"
186 sed 's/^/ /' "$out_dir/local-version.out" 2>/dev/null || true
187 echo "remote version:"
188 sed 's/^/ /' "$out_dir/remote-version.out" 2>/dev/null || true
189 if [[ -s "$out_dir/attach.err" ]]; then
190 echo "attach stderr:"
191 sed 's/^/ /' "$out_dir/attach.err" | tail -40
192 fi
193 echo "recent local after logs:"
194 find "$out_dir/local-after" -maxdepth 1 -type f -print0 2>/dev/null | while IFS= read -r -d '' f; do
195 echo " --- $(basename "$f") ---"
196 tail -20 "$f" | sed 's/^/ /'
197 done
198 echo "recent remote after logs:"
199 tail -80 "$out_dir/remote-after/logs.out" 2>/dev/null | sed 's/^/ /' || true
200} | tee -a "$out_dir/summary.txt"
201
202if [[ "$attach_status" -eq 124 ]]; then
203 echo "attach timed out after $duration; this often means the interactive attach stayed alive. Inspect $out_dir for protocol/log errors."
204 exit 0
205fi
206exit "$attach_status"