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
200 lines
6.1 KiB
Bash
200 lines
6.1 KiB
Bash
#!/bin/bash
|
|
|
|
# Redundant DNS/NTP Validation Test
|
|
# Validates that the host is configured to use the redundant pfv-netinfra-01/02
|
|
# pair for name resolution and time, and that both servers actually answer.
|
|
|
|
set -euo pipefail
|
|
|
|
# The authoritative pair (pfv-netinfra-01 / pfv-netinfra-02).
|
|
DNS_PRIMARY="192.168.3.252"
|
|
DNS_SECONDARY="192.168.3.253"
|
|
NTP_PRIMARY="192.168.3.252"
|
|
NTP_SECONDARY="192.168.3.253"
|
|
|
|
RESOLV_CONF="/etc/resolv.conf"
|
|
NTP_CONF="/etc/ntpsec/ntp.conf"
|
|
|
|
# A name every recursive resolver must be able to resolve.
|
|
DNS_PROBE_NAME="github.com"
|
|
|
|
have() { command -v "$1" >/dev/null 2>&1; }
|
|
|
|
# --- Configuration assertions -------------------------------------------------
|
|
|
|
function test_dns_config_present() {
|
|
echo "🔍 Checking $RESOLV_CONF ..."
|
|
local problems=0
|
|
|
|
if [[ -L "$RESOLV_CONF" ]]; then
|
|
echo "❌ $RESOLV_CONF is a symlink (would be overwritten by a resolver manager)"
|
|
((++problems))
|
|
elif [[ ! -f "$RESOLV_CONF" ]]; then
|
|
echo "❌ $RESOLV_CONF missing"
|
|
((++problems))
|
|
fi
|
|
|
|
for ns in "$DNS_PRIMARY" "$DNS_SECONDARY"; do
|
|
if grep -Eq "^[[:space:]]*nameserver[[:space:]]+$ns" "$RESOLV_CONF" 2>/dev/null; then
|
|
echo "✅ nameserver $ns configured"
|
|
else
|
|
echo "❌ nameserver $ns NOT in $RESOLV_CONF"
|
|
((++problems))
|
|
fi
|
|
done
|
|
|
|
return $problems
|
|
}
|
|
|
|
function test_ntp_config_present() {
|
|
echo "🔍 Checking $NTP_CONF ..."
|
|
if [[ ! -f "$NTP_CONF" ]]; then
|
|
echo "❌ $NTP_CONF missing (is ntpsec installed?)"
|
|
return 1
|
|
fi
|
|
local problems=0
|
|
for s in "$NTP_PRIMARY" "$NTP_SECONDARY"; do
|
|
if grep -Eq "^[[:space:]]*(server|pool)[[:space:]]+$s" "$NTP_CONF"; then
|
|
echo "✅ NTP server $s configured"
|
|
else
|
|
echo "❌ NTP server $s NOT in $NTP_CONF"
|
|
((++problems))
|
|
fi
|
|
done
|
|
return $problems
|
|
}
|
|
|
|
# --- Functional assertions: each server actually answers ----------------------
|
|
|
|
function _dns_resolves() {
|
|
# $1 = server ip. Returns 0 if it resolves DNS_PROBE_NAME.
|
|
local server="$1"
|
|
if have dig; then
|
|
dig @"$server" +short +time=4 +tries=1 "$DNS_PROBE_NAME" A 2>/dev/null | grep -Eq '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+'
|
|
elif have nslookup; then
|
|
nslookup "$DNS_PROBE_NAME" "$server" 2>/dev/null | grep -Eq 'Address:[[:space:]]*[0-9]'
|
|
elif have host; then
|
|
host "$DNS_PROBE_NAME" "$server" 2>/dev/null | grep -Eq 'has address'
|
|
else
|
|
# Last resort: the resolver itself.
|
|
getent ahostsv4 "$DNS_PROBE_NAME" >/dev/null 2>&1
|
|
fi
|
|
}
|
|
|
|
function test_dns_servers_answer() {
|
|
echo "🔍 Probing DNS servers ..."
|
|
local problems=0
|
|
for ns in "$DNS_PRIMARY" "$DNS_SECONDARY"; do
|
|
if _dns_resolves "$ns"; then
|
|
echo "✅ $ns resolves $DNS_PROBE_NAME"
|
|
else
|
|
echo "❌ $ns did not resolve $DNS_PROBE_NAME"
|
|
((++problems))
|
|
fi
|
|
done
|
|
return $problems
|
|
}
|
|
|
|
function _ntp_answers() {
|
|
# $1 = server ip. Returns 0 if it responds to a time query.
|
|
local server="$1"
|
|
if have ntpdate; then
|
|
timeout 8 ntpdate -q "$server" 2>/dev/null | grep -Eq 'no-leap|leap'
|
|
elif have sntp; then
|
|
timeout 8 sntp -t 4 "$server" >/dev/null 2>&1
|
|
elif have chronyc; then
|
|
# NTS/chrony not expected here, but be tolerant.
|
|
chronyc -n -h "$server" tracking >/dev/null 2>&1
|
|
else
|
|
return 2 # cannot test
|
|
fi
|
|
}
|
|
|
|
function test_ntp_servers_answer() {
|
|
echo "🔍 Probing NTP servers ..."
|
|
local problems=0
|
|
for s in "$NTP_PRIMARY" "$NTP_SECONDARY"; do
|
|
if _ntp_answers "$s"; then
|
|
echo "✅ NTP $s responds to time query"
|
|
else
|
|
echo "❌ $s did not respond to NTP query"
|
|
((++problems))
|
|
fi
|
|
done
|
|
return $problems
|
|
}
|
|
|
|
# --- End-to-end: the host is actually USING the pair --------------------------
|
|
|
|
function test_resolver_endtoend() {
|
|
echo "🔍 End-to-end resolution via $RESOLV_CONF ..."
|
|
if getent ahostsv4 "$DNS_PROBE_NAME" >/dev/null 2>&1; then
|
|
echo "✅ Host resolves $DNS_PROBE_NAME via configured resolver"
|
|
return 0
|
|
else
|
|
echo "❌ Host cannot resolve $DNS_PROBE_NAME via configured resolver"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
function test_ntp_daemon_peers() {
|
|
echo "🔍 NTP daemon peer list ..."
|
|
local peers
|
|
if have ntpq; then
|
|
peers="$(ntpq -pn 2>/dev/null || true)"
|
|
elif have chronyc; then
|
|
peers="$(chronyc -n sources 2>/dev/null || true)"
|
|
else
|
|
echo "⚠️ No ntpq/chronyc available; skipping daemon peer check"
|
|
return 0
|
|
fi
|
|
|
|
local problems=0
|
|
for s in "$NTP_PRIMARY" "$NTP_SECONDARY"; do
|
|
if echo "$peers" | grep -Eq "^\\s*${s//./\\.}"; then
|
|
echo "✅ NTP daemon has peer $s"
|
|
else
|
|
echo "❌ NTP daemon is NOT tracking $s"
|
|
((++problems))
|
|
fi
|
|
done
|
|
|
|
# Sync status is informational only: a freshly started daemon needs several
|
|
# polls before the reach counter stabilises, so we warn rather than fail.
|
|
if echo "$peers" | grep -Eq '\*'; then
|
|
echo "✅ NTP daemon reports a synced peer"
|
|
else
|
|
echo "⚠️ NTP daemon not yet synced (normal for a few minutes after restart)"
|
|
fi
|
|
return $problems
|
|
}
|
|
|
|
# --- Main ---------------------------------------------------------------------
|
|
|
|
function main() {
|
|
echo "🛰️ Running Redundant DNS/NTP Validation Tests"
|
|
echo "================================================"
|
|
|
|
local total_failures=0
|
|
|
|
test_dns_config_present || ((++total_failures))
|
|
test_ntp_config_present || ((++total_failures))
|
|
test_dns_servers_answer || ((++total_failures))
|
|
test_ntp_servers_answer || ((++total_failures))
|
|
test_resolver_endtoend || ((++total_failures))
|
|
test_ntp_daemon_peers || ((++total_failures))
|
|
|
|
echo "================================================"
|
|
if [[ $total_failures -eq 0 ]]; then
|
|
echo "✅ All redundant DNS/NTP validation tests passed"
|
|
exit 0
|
|
else
|
|
echo "❌ $total_failures redundant DNS/NTP tests failed"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
|
main "$@"
|
|
fi
|