chore: enforce shellcheck across the repo

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
This commit is contained in:
2026-07-30 08:56:31 -05:00
parent 54e9927167
commit 0fa0692c37
37 changed files with 226 additions and 89 deletions
+2 -1
View File
@@ -52,7 +52,8 @@ function run_test_suite() {
function run_single_test() {
local test_file="$1"
local test_name="$(basename "$test_file" .sh)"
local test_name
test_name="$(basename "$test_file" .sh)"
print_info "Running test: $test_name"
+2 -3
View File
@@ -5,8 +5,6 @@
set -euo pipefail
PROJECT_ROOT="$(dirname "$(realpath "${BASH_SOURCE[0]}")")/../.."
function test_2fa_packages() {
echo "🔍 Testing 2FA package installation..."
@@ -235,7 +233,8 @@ function test_backup_existence() {
if [[ -d "$backup_dir" ]]; then
# Look for recent 2FA backups
local recent_backups=$(find "$backup_dir" -name "2fa-*" -type d -newer /etc/ssh/sshd_config 2>/dev/null | wc -l)
local recent_backups
recent_backups=$(find "$backup_dir" -name "2fa-*" -type d -newer /etc/ssh/sshd_config 2>/dev/null | wc -l)
if [[ $recent_backups -gt 0 ]]; then
echo "✅ Recent 2FA backup found in $backup_dir"
+92
View File
@@ -0,0 +1,92 @@
#!/usr/bin/bash
#
# tests/shellcheck.sh — enforce shellcheck (via Docker) on all repo shell scripts
#
# Usage:
# bash tests/shellcheck.sh # lint every .sh in the repo
# bash tests/shellcheck.sh path/a.sh path/b.sh # lint specific files
#
# Uses the koalaman/shellcheck:stable image (no native binary required).
# Exits non-zero if ANY script emits a diagnostic. Skips non-bash scripts
# (e.g. PHP files with a .sh extension) and the vendored/ trees.
#
set -uo pipefail
IMAGE="koalaman/shellcheck:stable"
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
# --- Gather target files -----------------------------------------------------
mapfile -t FILES < <(
if [ "$#" -gt 0 ]; then
# Explicit args: resolve to repo-root-relative paths, keep only .sh
for f in "$@"; do
f_abs="$(cd "$(dirname "$f")" && pwd)/$(basename "$f")"
case "$f_abs" in
"$ROOT"/vendor/*) ;; # skip vendored
"$ROOT"/provisioning/Agents/librenms/*) ;; # upstream librenms-agent scripts
*) case "$f_abs" in *.sh) echo "$f_abs";; esac ;;
esac
done
else
# Default: every .sh under the repo, minus vendor/
while IFS= read -r f_abs; do
case "$f_abs" in
"$ROOT"/vendor/*) ;; # skip vendored
"$ROOT"/provisioning/Agents/librenms/*) ;; # upstream librenms-agent scripts
*) echo "$f_abs" ;;
esac
done < <(find "$ROOT" -type f -name '*.sh' \
-not -path '*/.git/*' -not -path "$ROOT/vendor/*")
fi
)
if [ "${#FILES[@]}" -eq 0 ]; then
echo "shellcheck.sh: no .sh files to check." >&2
exit 0
fi
# --- Filter out non-bash scripts (e.g. PHP with a .sh extension) --------------
BASH_FILES=()
for f in "${FILES[@]}"; do
shebang=$(head -c 64 "$f" 2>/dev/null | head -n1)
case "$shebang" in
\#!/usr/bin/php*|\#!/usr/bin/env\ php*) ;; # PHP, skip
\#!*) BASH_FILES+=("$f") ;; # any other shebang → check
*) BASH_FILES+=("$f") ;; # no shebang → check anyway
esac
done
if [ "${#BASH_FILES[@]}" -eq 0 ]; then
echo "shellcheck.sh: no bash scripts among targets." >&2
exit 0
fi
# --- Run shellcheck in Docker (mount repo root, pass root-relative paths) -----
# shellcheck disable=SC2012 # basename loop is intentional
REL=()
for f in "${BASH_FILES[@]}"; do REL+=("${f#"$ROOT"/}"); done
# Disable checks that are intentional conventions of this codebase (not bugs):
# SC1090/SC1091 — cannot follow dynamically-computed `source` paths (KNEL framework)
# SC2029 — ssh orchestration deliberately expands the command client-side
DISABLES=(
-e SC1090
-e SC1091
-e SC2029
)
echo "Checking ${#BASH_FILES[@]} script(s) with $IMAGE:"
printf ' %s\n' "${REL[@]}"
docker run --rm \
-v "$ROOT:/mnt:ro" \
-w /mnt \
"$IMAGE" -x "${DISABLES[@]}" "${REL[@]}"
rc=$?
if [ "$rc" -eq 0 ]; then
echo "shellcheck: PASS (${#BASH_FILES[@]} scripts clean)"
else
echo "shellcheck: FAIL (fix the findings above or add targeted disable directives)" >&2
fi
exit "$rc"
-3
View File
@@ -6,8 +6,6 @@
set -euo pipefail
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
# The authoritative pair (pfv-netinfra-01 / pfv-netinfra-02).
DNS_PRIMARY="192.168.3.252"
DNS_SECONDARY="192.168.3.253"
@@ -20,7 +18,6 @@ NTP_CONF="/etc/ntpsec/ntp.conf"
# A name every recursive resolver must be able to resolve.
DNS_PROBE_NAME="github.com"
failed=0
have() { command -v "$1" >/dev/null 2>&1; }
# --- Configuration assertions -------------------------------------------------
+8 -4
View File
@@ -12,7 +12,8 @@ REQUIRED_COMMANDS=("curl" "wget" "git" "systemctl" "apt-get")
# Test functions
function test_memory_requirements() {
local total_mem_kb=$(grep MemTotal /proc/meminfo | awk '{print $2}')
local total_mem_kb
total_mem_kb=$(grep MemTotal /proc/meminfo | awk '{print $2}')
local total_mem_gb=$((total_mem_kb / 1024 / 1024))
if [[ $total_mem_gb -ge $MIN_RAM_GB ]]; then
@@ -25,7 +26,8 @@ function test_memory_requirements() {
}
function test_disk_space() {
local available_gb=$(df / | tail -1 | awk '{print int($4/1024/1024)}')
local available_gb
available_gb=$(df / | tail -1 | awk '{print int($4/1024/1024)}')
if [[ $available_gb -ge $MIN_DISK_GB ]]; then
echo "✅ Disk space requirement met: ${available_gb}GB >= ${MIN_DISK_GB}GB"
@@ -53,8 +55,10 @@ function test_required_commands() {
function test_os_compatibility() {
if [[ -f /etc/os-release ]]; then
local os_id=$(grep "^ID=" /etc/os-release | cut -d'=' -f2 | tr -d '"')
local os_version=$(grep "^VERSION_ID=" /etc/os-release | cut -d'=' -f2 | tr -d '"')
local os_id
local os_version
os_id=$(grep "^ID=" /etc/os-release | cut -d'=' -f2 | tr -d '"')
os_version=$(grep "^VERSION_ID=" /etc/os-release | cut -d'=' -f2 | tr -d '"')
case "$os_id" in
ubuntu|debian)