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 stdio|udp|default] [--session NAME] [--duration 8s] [--vev-bin PATH] [--udp-health] 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 brice@arch
13 scripts/debug-remote-attach.sh --session work brice@arch
14 scripts/debug-remote-attach.sh --mode udp brice@arch
15 go build -o /tmp/vev-debug . && scripts/debug-remote-attach.sh --vev-bin /tmp/vev-debug brice@arch
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 - --udp-health collects redacted UDP transport-health log lines and Linux
23 ss -u -m socket-memory counters when available.
24 - The attach attempt unsets VEV so the smoke test can run from inside vev.
25USAGE
26}
27
28mode="default"
29session=""
30duration="8s"
31vev_bin="${VEV_BIN:-vev}"
32collect_udp_health=false
33while [[ $# -gt 0 ]]; do
34 case "$1" in
35 --mode)
36 mode="${2:-}"; shift 2 ;;
37 --session)
38 session="${2:-}"; shift 2 ;;
39 --duration)
40 duration="${2:-}"; shift 2 ;;
41 --vev-bin)
42 vev_bin="${2:-}"; shift 2 ;;
43 --udp-health)
44 collect_udp_health=true; shift ;;
45 -h|--help)
46 usage; exit 0 ;;
47 --)
48 shift; break ;;
49 -*)
50 echo "unknown option: $1" >&2; usage; exit 2 ;;
51 *)
52 break ;;
53 esac
54done
55
56if [[ $# -ne 1 ]]; then
57 usage
58 exit 2
59fi
60case "$mode" in
61 default|stdio|udp) ;;
62 *) echo "invalid --mode $mode (want default, stdio, or udp)" >&2; exit 2 ;;
63esac
64
65target="$1"
66attach_target="$target"
67if [[ -n "$session" ]]; then
68 attach_target="$target:$session"
69fi
70
71repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
72out_dir="${TMPDIR:-/tmp}/vev-remote-attach-$(date +%Y%m%d-%H%M%S)"
73mkdir -p "$out_dir"
74
75local_state="${XDG_STATE_HOME:-$HOME/.local/state}/vev"
76diagnostic_files=()
77
78# All persisted and displayed command output passes through this filter. Keep
79# diagnostic counters while removing the target, local home, network addresses,
80# and common key material labels from bundles that may be shared.
81redact() {
82 sed -E \
83 -e "s|${target//\/\\/\\}|[target]|g" \
84 -e "s|${HOME//\/\\/\\}|[home]|g" \
85 -e 's/([[:alnum:]_.-]+@)?([[:alnum:]_.-]+\.)+[[:alpha:]]{2,}/[host]/g' \
86 -e 's/([0-9]{1,3}\.){3}[0-9]{1,3}/[address]/g' \
87 -e 's/([[:xdigit:]]{1,4}:){2,}[[:xdigit:]:]*/[address]/g' \
88 -e 's/(private[ _-]?key|ssh-rsa|ssh-ed25519|ecdsa-sha2-[^[:space:]]*)/[redacted-key]/Ig'
89}
90
91redact_file() {
92 local raw="$1" dest="$2"
93 redact <"$raw" >"$dest" || true
94 rm -f "$raw"
95}
96
97run_capture() {
98 local name="$1" raw_out raw_err status
99 shift
100 raw_out="$out_dir/.$name.out.raw"
101 raw_err="$out_dir/.$name.err.raw"
102 status=0
103 {
104 printf '$'
105 printf ' %q' "$@"
106 printf '\n'
107 "$@"
108 } >"$raw_out" 2>"$raw_err" || status=$?
109 redact_file "$raw_out" "$out_dir/$name.out"
110 redact_file "$raw_err" "$out_dir/$name.err"
111 if [[ "$status" -ne 0 ]]; then printf '%s\n' "$status" >"$out_dir/$name.exit"; fi
112}
113
114copy_local_logs() {
115 local label="$1" raw
116 mkdir -p "$out_dir/local-$label"
117 if [[ -d "$local_state" ]]; then
118 find "$local_state" -maxdepth 1 -type f -name 'vev*.log' -print0 2>/dev/null | while IFS= read -r -d '' f; do
119 raw="$out_dir/local-$label/.$(basename "$f").raw"
120 tail -n 200 "$f" >"$raw" || true
121 redact_file "$raw" "$out_dir/local-$label/$(basename "$f")"
122 done
123 fi
124}
125
126copy_remote_logs() {
127 local label="$1" raw_out="$out_dir/remote-$1/.logs.out.raw" raw_err="$out_dir/remote-$1/.logs.err.raw"
128 mkdir -p "$out_dir/remote-$label"
129 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' \
130 >"$raw_out" 2>"$raw_err" || true
131 redact_file "$raw_out" "$out_dir/remote-$label/logs.out"
132 redact_file "$raw_err" "$out_dir/remote-$label/logs.err"
133}
134
135collect_udp_diagnostics() {
136 local label="$1" local_dir="$out_dir/local-$1" remote_dir="$out_dir/remote-$1"
137 if ! "$collect_udp_health"; then return; fi
138
139 grep -hE 'udp( transport)? health' "$local_dir"/*.log 2>/dev/null | redact >"$local_dir/udp-transport-health.out" || true
140 (ss -u -m 2>/dev/null || true) | awk '/skmem:/' | redact >"$local_dir/udp-socket-memory.out"
141 diagnostic_files+=("$local_dir/udp-transport-health.out" "$local_dir/udp-socket-memory.out")
142
143 ssh -o BatchMode=yes -o ConnectTimeout=8 "$target" 'state="${XDG_STATE_HOME:-$HOME/.local/state}/vev"; [ -d "$state" ] && grep -hE "udp( transport)? health" "$state"/vev*.log 2>/dev/null || true' \
144 2>&1 | redact >"$remote_dir/udp-transport-health.out" || true
145 ssh -o BatchMode=yes -o ConnectTimeout=8 "$target" 'ss -u -m 2>/dev/null | awk "/skmem:/" || true' \
146 2>&1 | redact >"$remote_dir/udp-socket-memory.out" || true
147 diagnostic_files+=("$remote_dir/udp-transport-health.out" "$remote_dir/udp-socket-memory.out")
148}
149
150printf 'debug bundle: %s\n' "$out_dir"
151printf 'mode: %s\nduration: %s\n' "$mode" "$duration" >"$out_dir/summary.txt"
152
153raw_git="$out_dir/.git.raw"
154(
155 cd "$repo_root"
156 git rev-parse --show-toplevel
157 git rev-parse --short HEAD
158 git status --short
159) >"$raw_git" 2>&1 || true
160redact_file "$raw_git" "$out_dir/git.txt"
161
162run_capture local-version "$vev_bin" --version
163raw_remote_version_out="$out_dir/.remote-version.out.raw"
164raw_remote_version_err="$out_dir/.remote-version.err.raw"
165ssh -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"' \
166 >"$raw_remote_version_out" 2>"$raw_remote_version_err" || echo "$?" >"$out_dir/remote-version.exit"
167redact_file "$raw_remote_version_out" "$out_dir/remote-version.out"
168redact_file "$raw_remote_version_err" "$out_dir/remote-version.err"
169
170copy_local_logs before
171copy_remote_logs before
172
173attach_log_raw="$out_dir/.attach.typescript.raw"
174attach_err_raw="$out_dir/.attach.err.raw"
175attach_stdout_raw="$out_dir/.attach.stdout.raw"
176cmd=("$vev_bin" attach "$attach_target")
177if [[ "$mode" == "default" ]]; then
178 env_cmd=(env -u VEV -u VEV_REMOTE_TRANSPORT)
179else
180 env_cmd=(env -u VEV "VEV_REMOTE_TRANSPORT=$mode")
181fi
182
183set +e
184if command -v script >/dev/null 2>&1; then
185 printf -v attach_cmd '%q ' "${cmd[@]}"
186 "${env_cmd[@]}" timeout "$duration" script -q -e -c "$attach_cmd" "$attach_log_raw" >"$attach_stdout_raw" 2>"$attach_err_raw"
187else
188 "${env_cmd[@]}" timeout "$duration" "${cmd[@]}" >"$attach_stdout_raw" 2>"$attach_err_raw"
189fi
190attach_status=$?
191set -e
192redact_file "$attach_stdout_raw" "$out_dir/attach.stdout"
193redact_file "$attach_err_raw" "$out_dir/attach.err"
194if [[ -e "$attach_log_raw" ]]; then redact_file "$attach_log_raw" "$out_dir/attach.typescript"; fi
195printf '%s\n' "$attach_status" >"$out_dir/attach.exit"
196
197copy_local_logs after
198copy_remote_logs after
199collect_udp_diagnostics after
200
201{
202 echo "debug bundle: $out_dir"
203 echo "attach exit: $attach_status"
204 echo "local version:"
205 sed 's/^/ /' "$out_dir/local-version.out" 2>/dev/null || true
206 echo "remote version:"
207 sed 's/^/ /' "$out_dir/remote-version.out" 2>/dev/null || true
208 if [[ -s "$out_dir/attach.err" ]]; then
209 echo "attach stderr:"
210 sed 's/^/ /' "$out_dir/attach.err" | tail -40
211 fi
212 if ((${#diagnostic_files[@]})); then
213 echo "collected diagnostic files:"
214 printf ' %s\n' "${diagnostic_files[@]}"
215 fi
216 echo "recent local after logs:"
217 find "$out_dir/local-after" -maxdepth 1 -type f -print0 2>/dev/null | while IFS= read -r -d '' f; do
218 echo " --- $(basename "$f") ---"
219 tail -20 "$f" | sed 's/^/ /'
220 done
221 echo "recent remote after logs:"
222 tail -80 "$out_dir/remote-after/logs.out" 2>/dev/null | sed 's/^/ /' || true
223} | tee -a "$out_dir/summary.txt"
224
225if [[ "$attach_status" -eq 124 ]]; then
226 echo "attach timed out after $duration; this often means the interactive attach stayed alive. Inspect $out_dir for protocol/log errors."
227 exit 0
228fi
229exit "$attach_status"