Files
PFVCluster/netinfra/audit-netboot.sh
T
mrcharles 48cb6842c6 docs: lock in storage philosophy and PDM migration capability
Storage philosophy (user directive):
- NVMe/SSD: k8s worker scratch + ultix-streaming (dev workstation
  running "cluster of 1" pre-prod jobs before full k8s deployment)
- Spinning rust: all infrastructure VMs (UCS, netinfra, LibreNMS, SIEM)

Clarified that hosts are standalone but managed via Proxmox Datacenter
Manager (PDM), which supports VM migration between nodes through the
UI -- eliminating the need for manual disk copies in the migration plan.

Updated all migration steps to reference PDM storage migrate instead
of manual cp commands.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-07-27 21:46:48 -05:00

298 lines
14 KiB
Bash
Executable File

#!/usr/bin/env bash
# =============================================================================
# audit-netboot.sh
# -----------------------------------------------------------------------------
# READ-ONLY audit of the reference node "pfv-netboot".
#
# Purpose: gather enough information to replicate its Pi-hole, Technitium DNS,
# and NTP services onto pfv-netinfra-01 and pfv-netinfra-02.
#
# Guarantees:
# * NO writes, NO installs, NO service restarts, NO network changes.
# * Every command below is read-only (status, cat, ls, ss, ps, sqlite3 SELECT).
#
# Intended to be run as the "localuser" account on pfv-netboot:
#
# ssh localuser@pfv-netboot 'bash -s' < audit-netboot.sh > netboot-audit.txt
#
# or, if sudo is needed for a few reads, the script will try `sudo -n` for
# specific files that are normally root-readable only. It will NEVER use sudo
# to write or modify anything.
# =============================================================================
set -u
AUDIT_DATE="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
# Helper: print a clearly delimited section header.
section() {
printf '\n========================================================================\n'
printf 'SECTION: %s\n' "$1"
printf '========================================================================\n'
}
# Helper: read a file with sudo if needed, silently skip if missing.
read_file() {
local path="$1"
if [ -r "$path" ]; then
printf '--- %s (uid-readable) ---\n' "$path"
cat "$path" 2>/dev/null
elif sudo -n true 2>/dev/null; then
if sudo -n test -e "$path"; then
printf '--- %s (via sudo -n) ---\n' "$path"
sudo -n cat "$path" 2>/dev/null
else
printf '--- %s : NOT FOUND ---\n' "$path"
fi
else
printf '--- %s : NOT READABLE (no passwordless sudo) ---\n' "$path"
fi
printf '\n'
}
list_dir() {
local path="$1"
printf '--- ls -la %s ---\n' "$path"
ls -la "$path" 2>/dev/null || sudo -n ls -la "$path" 2>/dev/null || printf '(cannot list %s)\n' "$path"
printf '\n'
}
printf '##### AUDIT START %s #####\n' "$AUDIT_DATE"
printf 'Audit host: %s\n' "$(hostname -f 2>/dev/null || hostname)"
printf 'Audit user: %s\n' "$(id -un 2>/dev/null)"
printf 'Script: audit-netboot.sh (READ-ONLY)\n'
# -----------------------------------------------------------------------------
# 1. System basics
# -----------------------------------------------------------------------------
section "1. SYSTEM BASICS"
echo "-- uname --"; uname -a
echo "-- /etc/os-release --"; cat /etc/os-release 2>/dev/null
echo "-- uptime --"; uptime
echo "-- timezone --"; timedatectl 2>/dev/null || cat /etc/timezone 2>/dev/null || date
echo "-- arch --"; dpkg --print-architecture 2>/dev/null || uname -m
echo "-- memory --"; free -h 2>/dev/null
echo "-- disk --"; df -h / 2>/dev/null
echo "-- cpu count --"; nproc 2>/dev/null
# -----------------------------------------------------------------------------
# 2. Network configuration
# -----------------------------------------------------------------------------
section "2. NETWORK"
echo "-- hostname --"; hostname; hostname -f 2>/dev/null; hostname -I 2>/dev/null
echo "-- ip addr --"; ip -br addr 2>/dev/null; echo; ip addr 2>/dev/null
echo "-- default route --"; ip route 2>/dev/null
echo "-- /etc/resolv.conf --"; cat /etc/resolv.conf 2>/dev/null
echo "-- /etc/hosts --"; cat /etc/hosts 2>/dev/null
echo "-- listening TCP/UDP sockets --"
ss -tlnup 2>/dev/null || sudo -n ss -tlnup 2>/dev/null
echo "-- /etc/network/interfaces --"; cat /etc/network/interfaces 2>/dev/null
echo "-- netplan --"; ls -la /etc/netplan/ 2>/dev/null; for f in /etc/netplan/*.yaml; do [ -e "$f" ] && { echo "--- $f ---"; cat "$f"; }; done 2>/dev/null
echo "-- systemd-networkd --"; ls -la /etc/systemd/network/ 2>/dev/null; networkctl status 2>/dev/null | head -40
# -----------------------------------------------------------------------------
# 3. DNS / DHCP / NTP related systemd units
# -----------------------------------------------------------------------------
section "3. RELEVANT SYSTEMD UNITS"
echo "-- all units matching dns|pihole|dnsmasq|technitium|ftl|ntp|chrony|timesync --"
systemctl list-units --type=service --all --no-pager 2>/dev/null \
| grep -Ei 'pihole|dnsmasq|ftl|technitium|dns|ntp|chrony|timesync|resolv|resolved' || true
echo "-- unit files (enabled state) --"
systemctl list-unit-files --no-pager 2>/dev/null \
| grep -Ei 'pihole|dnsmasq|ftl|technitium|dns|ntp|chrony|timesync|resolv|resolved' || true
echo "-- installed packages of interest --"
dpkg -l 2>/dev/null | grep -Ei 'pihole|dnsmasq|ftl|technitium|ntp|chrony|timesync|unbound|resolved|resolvconf' || true
# -----------------------------------------------------------------------------
# 4. NTP service details
# -----------------------------------------------------------------------------
section "4. NTP"
echo "-- chrony --"
systemctl status chrony --no-pager 2>/dev/null | head -15 || echo "(no chrony unit)"
read_file /etc/chrony/chrony.conf
echo "-- chronyc sources/stats (if available) --"
chronyc -n sources 2>/dev/null || sudo -n chronyc -n sources 2>/dev/null || true
chronyc -n tracking 2>/dev/null || sudo -n chronyc -n tracking 2>/dev/null || true
echo "-- ntpsec / ntp classic --"
systemctl status ntp --no-pager 2>/dev/null | head -15 || echo "(no ntp unit)"
systemctl status ntpsec --no-pager 2>/dev/null | head -15 || true
read_file /etc/ntp.conf
read_file /etc/ntpsec/ntp.conf
echo "-- systemd-timesyncd --"
systemctl status systemd-timesyncd --no-pager 2>/dev/null | head -15 || echo "(no timesyncd)"
read_file /etc/systemd/timesyncd.conf
echo "-- openntpd --"
systemctl status openntpd --no-pager 2>/dev/null | head -15 || true
read_file /etc/openntpd/ntpd.conf
# -----------------------------------------------------------------------------
# 5. Docker (Pi-hole and/or Technitium may be containerized)
# -----------------------------------------------------------------------------
section "5. DOCKER"
if command -v docker >/dev/null 2>&1; then
echo "-- docker version --"; docker version 2>/dev/null || sudo -n docker version 2>/dev/null || true
echo "-- containers (running) --"; docker ps 2>/dev/null || sudo -n docker ps 2>/dev/null || true
echo "-- containers (all) --"; docker ps -a 2>/dev/null || sudo -n docker ps -a 2>/dev/null || true
echo "-- images --"; docker images 2>/dev/null || sudo -n docker images 2>/dev/null || true
echo "-- volumes --"; docker volume ls 2>/dev/null || sudo -n docker volume ls 2>/dev/null || true
echo "-- networks --"; docker network ls 2>/dev/null || sudo -n docker network ls 2>/dev/null || true
else
echo "(docker not installed / not on PATH)"
fi
echo "-- compose files in common locations --"
for d in /etc/docker-compose /opt/docker-compose /root/docker-compose /home/localuser/docker-compose /srv/docker-compose /opt/pihole /opt/technitium /opt; do
if sudo -n test -d "$d" 2>/dev/null || [ -d "$d" ]; then
echo "### $d ###"
sudo -n ls -la "$d" 2>/dev/null || ls -la "$d" 2>/dev/null || true
for f in docker-compose.yml docker-compose.yaml compose.yml compose.yaml; do
if sudo -n test -f "$d/$f" 2>/dev/null; then
read_file "$d/$f"
elif [ -f "$d/$f" ]; then
read_file "$d/$f"
fi
done
fi
done
# -----------------------------------------------------------------------------
# 6. Pi-hole
# -----------------------------------------------------------------------------
section "6. PI-HOLE"
if command -v pihole >/dev/null 2>&1; then
echo "-- pihole version --"; pihole -v 2>/dev/null || sudo -n pihole -v 2>/dev/null || true
echo "-- pihole status --"; pihole status 2>/dev/null || sudo -n pihole status 2>/dev/null || true
else
echo "(pihole command not on PATH)"
fi
echo "-- /etc/pihole listing --"
list_dir /etc/pihole
# Key Pi-hole config files
for f in \
/etc/pihole/setupVars.conf \
/etc/pihole/pihole-FTL.conf \
/etc/pihole/adlists.list \
/etc/pihole/whitelist.txt \
/etc/pihole/blacklist.txt \
/etc/pihole/regex.list \
/etc/pihole/custom.list \
/etc/pihole/local.list \
/etc/pihole/hostnameMappings.txt \
/etc/pihole/dhcp.leases \
/etc/pihole/static_ip.conf \
/etc/pihole/GitHubVersions \
/etc/pihole/macvendor.db ; do
read_file "$f"
done
echo "-- /etc/pihole/*.conf (all) --"
for f in /etc/pihole/*.conf; do [ -e "$f" ] && read_file "$f"; done 2>/dev/null
echo "-- /etc/dnsmasq.d listing --"
list_dir /etc/dnsmasq.d
for f in /etc/dnsmasq.d/*; do [ -e "$f" ] && read_file "$f"; done 2>/dev/null
echo "-- /etc/dnsmasq.conf (if present) --"
read_file /etc/dnsmasq.conf
echo "-- gravity.db schema + row counts (Pi-hole v5+) --"
GRAVITY=""
for g in /etc/pihole/gravity.db /etc/pihole/gravity.db.*; do
if [ -e "$g" ]; then GRAVITY="$g"; break; fi
done
if [ -n "${GRAVITY:-}" ]; then
echo "gravity.db = $GRAVITY"
if command -v sqlite3 >/dev/null 2>&1; then
sqlite3 -readonly "$GRAVITY" ".tables" 2>/dev/null || sudo -n sqlite3 -readonly "$GRAVITY" ".tables" 2>/dev/null || true
for tbl in adlist adlist_by_group domainlist domainlist_by_group client client_by_group group info; do
cnt=$(sqlite3 -readonly "$GRAVITY" "SELECT COUNT(*) FROM $tbl;" 2>/dev/null || sudo -n sqlite3 -readonly "$GRAVITY" "SELECT COUNT(*) FROM $tbl;" 2>/dev/null || echo "n/a")
printf 'gravity.%s count = %s\n' "$tbl" "$cnt"
done
echo "-- adlist entries (url, enabled, comment) --"
sqlite3 -readonly "$GRAVITY" "SELECT id,address,enabled,comment FROM adlist;" 2>/dev/null \
|| sudo -n sqlite3 -readonly "$GRAVITY" "SELECT id,address,enabled,comment FROM adlist;" 2>/dev/null || true
echo "-- domainlist sample (first 50) --"
sqlite3 -readonly "$GRAVITY" "SELECT id,type,domain,enabled,comment FROM domainlist LIMIT 50;" 2>/dev/null \
|| sudo -n sqlite3 -readonly "$GRAVITY" "SELECT id,type,domain,enabled,comment FROM domainlist LIMIT 50;" 2>/dev/null || true
echo "-- client list --"
sqlite3 -readonly "$GRAVITY" "SELECT id,ip,comment FROM client;" 2>/dev/null \
|| sudo -n sqlite3 -readonly "$GRAVITY" "SELECT id,ip,comment FROM client;" 2>/dev/null || true
echo "-- group list --"
sqlite3 -readonly "$GRAVITY" "SELECT id,name,enabled,comment FROM 'group';" 2>/dev/null \
|| sudo -n sqlite3 -readonly "$GRAVITY" "SELECT id,name,enabled,comment FROM 'group';" 2>/dev/null || true
echo "-- info table --"
sqlite3 -readonly "$GRAVITY" "SELECT * FROM info;" 2>/dev/null \
|| sudo -n sqlite3 -readonly "$GRAVITY" "SELECT * FROM info;" 2>/dev/null || true
else
echo "(sqlite3 not installed; gravity.db present at $GRAVITY)"
fi
else
echo "(no gravity.db found)"
fi
echo "-- lighttpd / pihole web admin --"
systemctl status lighttpd --no-pager 2>/dev/null | head -15 || true
read_file /etc/lighttpd/lighttpd.conf
for f in /etc/lighttpd/conf-enabled/*; do [ -e "$f" ] && read_file "$f"; done 2>/dev/null
echo "-- pihole-FTL service --"
systemctl status pihole-FTL --no-pager 2>/dev/null | head -20 || true
# -----------------------------------------------------------------------------
# 7. Technitium DNS
# -----------------------------------------------------------------------------
section "7. TECHNITIUM DNS"
echo "-- technitium systemd unit --"
systemctl status technitium-dns --no-pager 2>/dev/null | head -20 || echo "(no technitium-dns unit)"
echo "-- any unit containing technitium --"
systemctl list-units --all --no-pager 2>/dev/null | grep -i technitium || true
echo "-- unit file path --"
sudo -n systemctl cat technitium-dns 2>/dev/null || systemctl cat technitium-dns 2>/dev/null || true
# Technitium common install locations
for d in /etc/technitium /etc/technitium/dns /opt/technitium /opt/technitium/dns /var/lib/technitium /usr/local/technitium; do
echo "### checking $d ###"
if sudo -n test -d "$d" 2>/dev/null || [ -d "$d" ]; then
list_dir "$d"
# recurse one level for config files
for sub in "$d" "$d"/*; do
[ -e "$sub" ] || continue
if [ -f "$sub" ] && echo "$sub" | grep -Eq '\.(xml|json|conf|config|txt)$'; then
read_file "$sub"
fi
done
fi
done
echo "-- technitium config.xml / dnsServer.conf (search) --"
sudo -n find /etc/technitium /opt/technitium /var/lib/technitium -maxdepth 4 \
\( -name 'config.xml' -o -name '*.config' -o -name 'dnsServer.conf' -o -name 'blockList.txt' \) \
-print 2>/dev/null || true
# -----------------------------------------------------------------------------
# 8. Firewall / SELinux / misc
# -----------------------------------------------------------------------------
section "8. FIREWALL & MISC"
echo "-- nftables --"; sudo -n nft list ruleset 2>/dev/null | head -80 || true
echo "-- iptables --"; sudo -n iptables -S 2>/dev/null | head -40 || true
echo "-- ufw --"; sudo -n ufw status verbose 2>/dev/null || true
echo "-- firewalld --"; systemctl status firewalld --no-pager 2>/dev/null | head -8 || true
echo "-- selinux/apparmor --"; getenforce 2>/dev/null || echo "(SELinux not present)"; aa-status 2>/dev/null | head -5 || true
# -----------------------------------------------------------------------------
# 9. Cron / timers that maintain these services
# -----------------------------------------------------------------------------
section "9. CRON & TIMERS"
echo "-- root crontab --"; sudo -n crontab -l 2>/dev/null || echo "(n/a)"
echo "-- localuser crontab --"; crontab -l 2>/dev/null || echo "(none)"
echo "-- /etc/cron.d --"; ls -la /etc/cron.d 2>/dev/null
for f in /etc/cron.d/*pihole* /etc/cron.d/*technitium* /etc/cron.d/*gravity*; do
[ -e "$f" ] && read_file "$f"
done 2>/dev/null
echo "-- pihole timer --"; systemctl list-timers --all --no-pager 2>/dev/null | grep -Ei 'pihole|gravity|technitium' || true
printf '\n##### AUDIT END %s #####\n' "$AUDIT_DATE"