tools/collect.sh
Ref: Size: 4.4 KiB History
#!/bin/sh
# One-shot diagnostic bundle: everything mux knows how to say about
# itself, in one text stream, ordered for reading by a human or a model.
#
# tools/collect.sh [--sock PATH]... [--host SSHHOST]... [--hub PORT] [--log-lines N]
#
# Collects: the binary's version, the local daemon log, `mux d stats` per
# --sock, the hub's /tiles if --hub names a port, and per --host (over
# ssh): version, stats, log tail, key FINGERPRINT. Key material itself
# is never read — fingerprints are what a key-drift diagnosis needs,
# and were what the M17 field trial had to assemble by hand.
#
# Screen contents are NOT collected: `mux d dump` shows whatever the
# session shows, and a diagnostic bundle should be safe to paste.
set -u
LINES=100
SOCKS=""; HOSTS=""; HUB=""
while [ $# -gt 0 ]; do
case "$1" in
--sock) SOCKS="$SOCKS $2"; shift 2 ;;
--host) HOSTS="$HOSTS $2"; shift 2 ;;
--hub) HUB="$2"; shift 2 ;;
--log-lines) LINES="$2"; shift 2 ;;
*) echo "usage: collect.sh [--sock PATH]... [--host SSHHOST]... [--hub PORT] [--log-lines N]" >&2
exit 2 ;;
esac
done
section() { printf '\n===== %s =====\n' "$1"; }
# Every line of the bundle is stamped once, up top: the logs themselves
# carry no timestamps (a known gap, on the roadmap), so the collection
# instant is the only clock the bundle has.
printf 'mux diagnostic bundle: %s on %s\n' "$(date -u '+%Y-%m-%dT%H:%M:%SZ')" "$(uname -n)"
section "version (local)"
# One binary since the modes became words; muxd, muxa and muxweb are gone,
# and a box that still has them has an install this bundle cannot explain.
p=$(command -v mux 2>/dev/null) && printf 'mux: %s (%s)\n' "$("$p" --version 2>&1)" "$p" \
|| echo "mux: not on PATH"
for old in muxd muxa muxweb; do
o=$(command -v "$old" 2>/dev/null) && printf 'STALE %s: %s (%s)\n' "$old" "$("$o" --version 2>&1)" "$o"
done
section "local key fingerprint"
KEY="${MUX_KEY_FILE:-$HOME/.config/mux/key}"
if [ -f "$KEY" ]; then
printf '%s %s (%s bytes)\n' "$(sha256sum "$KEY" | cut -c1-16)" "$KEY" "$(wc -c < "$KEY")"
else
echo "no key at $KEY"
fi
section "local daemon log (last $LINES lines)"
LOG="${XDG_STATE_HOME:-$HOME/.local/state}/mux/muxd.log"
if [ -f "$LOG" ]; then
printf '%s (%s bytes)\n' "$LOG" "$(wc -c < "$LOG")"
tail -n "$LINES" "$LOG"
else
echo "no log at $LOG (a foreground daemon logs to its own stderr)"
fi
section "local daemons (observed, not claimed)"
# Matched on the ARGS, not the name: every mode is the same binary called
# `mux`, so only the mode word after it tells a daemon from a client.
pgrep -af 'mux d run' 2>/dev/null || echo "no mux daemons"
pgrep -af 'mux web' 2>/dev/null || echo "no mux hubs"
for s in $SOCKS; do
section "stats: $s"
mux d stats --sock "$s" 2>&1
done
if [ -n "$HUB" ]; then
section "hub on 127.0.0.1:$HUB"
if command -v curl >/dev/null 2>&1; then
printf 'tiles: '; curl -sf "http://127.0.0.1:$HUB/tiles" 2>&1 || echo "(no answer)"
printf '\n'
else
echo "curl not available; skipped"
fi
fi
for h in $HOSTS; do
section "box: $h"
# One ssh round trip per box, everything in one remote shell. BatchMode
# so an interactive prompt fails fast instead of hanging the bundle.
#
# The pattern is BRACKETED and double-quoted, both for the same reason:
# this whole script arrives on the remote as one `sh -c` argument, so the
# remote shell's own cmdline contains the pattern and an unbracketed
# `mux d run` lists that shell as a daemon. Single quotes would be worse
# than wrong — they close this argument's own quoting, and the remote
# would run `pgrep -af mux d run`, three operands pgrep refuses.
ssh -o BatchMode=yes -o ConnectTimeout=10 "$h" '
printf "version: %s\n" "$(mux --version 2>&1)"
k=~/.config/mux/key
if [ -f "$k" ]; then
printf "key: %s %s\n" "$(sha256sum "$k" | cut -c1-16)" "$k"
else
echo "key: none"
fi
pgrep -af "[m]ux d run" 2>/dev/null || echo "no mux daemons"
echo "stats: $(mux d stats 2>&1)"
l=${XDG_STATE_HOME:-$HOME/.local/state}/mux/muxd.log
if [ -f "$l" ]; then
printf "log %s (%s bytes), last '"$LINES"' lines:\n" "$l" "$(wc -c < "$l")"
tail -n '"$LINES"' "$l"
else
echo "no log at $l"
fi
' 2>&1 || echo "(ssh to $h failed)"
done
section "end of bundle"