#!/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"