a73x

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"