Establish shellcheck as a mandatory pre-commit quality gate and bring all 93
shell scripts to a clean state.
- tests/shellcheck.sh: wrapper that runs koalaman/shellcheck:stable via Docker
(no native binary needed), skips vendored + upstream librenms-agent scripts.
- .shellcheckrc: documents intentional codebase-wide disables (dynamic source
paths SC1090/SC1091, client-side ssh expansion SC2029).
- AGENTS.md: new Git Policy rule mandating clean shellcheck for every shell
script before commit.
Fixes applied (real bugs + quality): missing quote in netinfra/gather-configs.sh
(caused cascading parse errors), unquoted expansions, declare-and-assign masking,
egrep -> grep -E, $FUNCNAME array indexing, unused variable removal, cd || exit.
Intentional patterns (sourced config, sysfs/ps diagnostics, ssh heredocs that
expand local config) get justified targeted disables.
💘 Generated with Crush
Assisted-by: Crush:glm-5.2
129 lines
4.4 KiB
Bash
Executable File
129 lines
4.4 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# sw-capture-remote.sh - orchestrate a serial capture from this workstation.
|
|
#
|
|
# Flow:
|
|
# 1. De-conflict: abort if any local ssh to pfv-tsys4 is in flight
|
|
# (other agent could be there).
|
|
# 2. Free the serial port: kill whatever holds /dev/ttyUSBx
|
|
# (typically a screen session). Targeted, not blanket.
|
|
# 3. scp driver + .cmds to pfv-tsys4.
|
|
# 4. Run driver over ssh, capture stderr to console.
|
|
# 5. scp the resulting log back to returned-logs/.
|
|
#
|
|
# Usage:
|
|
# sw-capture-remote.sh <switch-name> [device]
|
|
#
|
|
# <switch-name> e.g. pfv-core-sw01 (must have switches/<name>.cmds)
|
|
# [device] /dev/ttyUSBx on pfv-tsys4. Defaults per switch map below.
|
|
#
|
|
# Currently scoped to pfv-core-sw01 only (per user direction). The other
|
|
# two switches are deferred; their defaults are placeholders.
|
|
set -u
|
|
|
|
SWITCH=${1:-}
|
|
DEVICE=${2:-}
|
|
|
|
if [ -z "$SWITCH" ]; then
|
|
echo "Usage: $0 <switch-name> [device]" >&2
|
|
echo " e.g. $0 pfv-core-sw01 /dev/ttyUSB2" >&2
|
|
exit 2
|
|
fi
|
|
|
|
# Switch -> default device map (ttyUSB2 = core-sw01 confirmed by user).
|
|
case "$SWITCH" in
|
|
pfv-core-sw01)
|
|
[ -z "$DEVICE" ] && DEVICE=/dev/ttyUSB2 ;;
|
|
pfv-r3-tor-mgmt)
|
|
[ -z "$DEVICE" ] && DEVICE=/dev/ttyUSB0 # TENTATIVE - unconfirmed
|
|
if [ "${2:-}" = "" ]; then
|
|
echo "NOTE: pfv-r3-tor-mgmt device is tentative (/dev/ttyUSB0)." >&2
|
|
echo " Pass the device explicitly if different." >&2
|
|
fi ;;
|
|
pfv-r3-tor-stor)
|
|
[ -z "$DEVICE" ] && DEVICE=/dev/ttyUSB1 # TENTATIVE - unconfirmed
|
|
if [ "${2:-}" = "" ]; then
|
|
echo "NOTE: pfv-r3-tor-stor device is tentative (/dev/ttyUSB1)." >&2
|
|
echo " Pass the device explicitly if different." >&2
|
|
fi ;;
|
|
*)
|
|
echo "unknown switch: $SWITCH" >&2; exit 2 ;;
|
|
esac
|
|
|
|
BAUD=9600
|
|
HOST=root@pfv-tsys4
|
|
HERE=$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)
|
|
LOCAL_DRIVER=$HERE/scripts/sw-capture.py
|
|
LOCAL_CMDS=$HERE/switches/$SWITCH.cmds
|
|
LOCAL_LOG=$HERE/returned-logs/$SWITCH.log
|
|
REMOTE_DRIVER=/root/sw-capture.py
|
|
REMOTE_CMDS=/root/$SWITCH.cmds
|
|
REMOTE_LOG=/root/$SWITCH.log
|
|
|
|
[ -f "$LOCAL_DRIVER" ] || { echo "missing $LOCAL_DRIVER" >&2; exit 2; }
|
|
[ -f "$LOCAL_CMDS" ] || { echo "missing $LOCAL_CMDS" >&2; exit 2; }
|
|
|
|
ts() { date +%H:%M:%S; }
|
|
|
|
echo "[$(ts)] switch=$SWITCH device=$DEVICE baud=$BAUD host=$HOST"
|
|
|
|
# 1. De-conflict: any local ssh to pfv-tsys4 in flight?
|
|
echo "[$(ts)] checking for in-flight ssh to pfv-tsys4..."
|
|
# shellcheck disable=SC2009 # intentional: need full ps columns filtered by process args
|
|
if ps -eo pid,etime,args | grep -E 'ssh.*pfv-tsys4|scp.*pfv-tsys4' | grep -v grep >/tmp/.swcap.ps 2>&1; then
|
|
cat /tmp/.swcap.ps
|
|
echo "[$(ts)] ABORT: another ssh/scp to pfv-tsys4 is running (other agent?)." >&2
|
|
exit 1
|
|
fi
|
|
echo "[$(ts)] clear."
|
|
rm -f /tmp/.swcap.ps
|
|
|
|
# 2. Free the serial port: kill whatever holds $DEVICE.
|
|
echo "[$(ts)] freeing $DEVICE on $HOST (targeted; other screen sessions untouched)..."
|
|
ssh -o BatchMode=yes -o ConnectTimeout=8 "$HOST" \
|
|
"fuser -v $DEVICE 2>&1 | tee /dev/stderr; \
|
|
fuser -k -TERM $DEVICE 2>/dev/null; sleep 1; \
|
|
if fuser $DEVICE 2>/dev/null; then \
|
|
echo 'still held after SIGTERM, escalating to SIGKILL'; \
|
|
fuser -k -KILL $DEVICE 2>/dev/null; sleep 1; \
|
|
fi; \
|
|
fuser $DEVICE 2>/dev/null && echo 'STILL HELD' || echo 'FREE'"
|
|
|
|
# Re-check; abort if still held.
|
|
HELD=$(ssh -o BatchMode=yes "$HOST" "fuser $DEVICE 2>/dev/null && echo HELD || echo FREE")
|
|
if [ "$HELD" = "HELD" ]; then
|
|
echo "[$(ts)] ABORT: $DEVICE still held on $HOST." >&2
|
|
exit 1
|
|
fi
|
|
|
|
# 3. Copy driver + cmds.
|
|
echo "[$(ts)] copying driver + cmds to $HOST..."
|
|
scp -q "$LOCAL_DRIVER" "$HOST:$REMOTE_DRIVER"
|
|
scp -q "$LOCAL_CMDS" "$HOST:$REMOTE_CMDS"
|
|
|
|
# 4. Run the capture on pfv-tsys4. Stream stderr (progress) to console.
|
|
echo "[$(ts)] running capture..."
|
|
ssh -o BatchMode=yes -o ServerAliveInterval=10 "$HOST" \
|
|
"python3 $REMOTE_DRIVER \
|
|
--device $DEVICE --baud $BAUD \
|
|
--cmds $REMOTE_CMDS --log $REMOTE_LOG"
|
|
RC=$?
|
|
echo "[$(ts)] capture exit code: $RC"
|
|
|
|
# 5. Pull log back.
|
|
echo "[$(ts)] pulling log back to $LOCAL_LOG..."
|
|
mkdir -p "$(dirname "$LOCAL_LOG")"
|
|
scp -q "$HOST:$REMOTE_LOG" "$LOCAL_LOG"
|
|
if [ -f "$LOCAL_LOG" ]; then
|
|
SZ=$(wc -c < "$LOCAL_LOG")
|
|
echo "[$(ts)] OK: $LOCAL_LOG ($SZ bytes)"
|
|
echo "----- head -----"
|
|
head -30 "$LOCAL_LOG"
|
|
echo "----- tail -----"
|
|
tail -10 "$LOCAL_LOG"
|
|
else
|
|
echo "[$(ts)] ERROR: log not pulled back." >&2
|
|
exit 1
|
|
fi
|
|
|
|
exit $RC
|