File: //proc/thread-self/root/proc/self/root/proc/thread-self/root/opt/sp_scripts/audit.sh
#!/bin/bash
set -euo pipefail
# Pre-migration audit for a cPanel/WHM server: account totals, nameserver
# locality, custom reseller nameservers, and accounts on dedicated IPs.
for cmd in whmapi1 jq dig; do
if ! command -v "$cmd" >/dev/null 2>&1; then
echo "Error: required command '$cmd' not found." >&2
exit 1
fi
done
if [ "$(id -u)" -ne 0 ]; then
echo "Error: this script must be run as root." >&2
exit 1
fi
whm_call() {
local json
json=$(whmapi1 --output=json "$@")
if [ "$(jq -r '.metadata.result' <<<"$json")" != "1" ]; then
echo "Error: whmapi1 $1 failed: $(jq -r '.metadata.reason' <<<"$json")" >&2
exit 1
fi
printf '%s' "$json"
}
acct_json=$(whm_call listaccts want=user,domain,owner,ip,diskused)
ns_json=$(whm_call get_nameserver_config)
ip_json=$(whm_call listips)
printf 'Accounts: %s\nCustomer data: %.2f GiB\nServer nameservers:\n' \
"$(jq '.data.acct | length' <<<"$acct_json")" \
"$(jq '[.data.acct[].diskused | sub("M$";"") | (tonumber? // 0)] | (add // 0) / 1024' <<<"$acct_json")"
resolve_host() {
{
dig +short A "$1" || true
dig +short AAAA "$1" || true
} |
sed 's/\.$//' |
{ grep -E '^[0-9A-Fa-f.:]+$' || true; } |
sort -u
}
# IPv4 comes from the WHM IP inventory (includes public IPs on NAT'd
# servers); listips is IPv4-only, so IPv6 still comes from the kernel.
server_ips=$(
{
jq -r '.data.ip[]?.ip' <<<"$ip_json"
ip -o -6 addr show scope global 2>/dev/null |
awk '{print $4}' |
cut -d/ -f1 ||
true
} |
sed '/^$/d' |
sort -u
)
# Our global DNS cluster (StableDNS) is reachable under two brand
# names; both resolve to the same cluster IPs.
stabledns_ips=$(
for i in 1 2 3 4; do
resolve_host "ns${i}.stableserver.net"
resolve_host "ns${i}.mysecurecloudhost.com"
done |
sort -u
)
jq -r '.data.nameservers[]?' <<<"$ns_json" |
while read -r ns; do
resolved=$(resolve_host "$ns")
if [ -z "$resolved" ]; then
status="UNRESOLVED"
elif grep -Fqx -f \
<(printf '%s\n' "$server_ips") \
<(printf '%s\n' "$resolved"); then
status="LOCAL"
elif [ -n "$stabledns_ips" ] &&
grep -Fqx -f \
<(printf '%s\n' "$stabledns_ips") \
<(printf '%s\n' "$resolved"); then
status="STABLEDNS"
else
status="REMOTE/CLUSTER"
fi
printf ' %-35s %-15s %s\n' \
"$ns" \
"$status" \
"$(paste -sd, <<<"$resolved")"
done
# No WHM API getter exists for reseller nameservers (only
# setresellernameservers), so read cPanel's backing file directly.
custom_reseller_ns=$(
awk -F: '{
ns=$2
gsub(/[[:space:]]/, "", ns)
# The file has four fixed slots, so collapse the empty entries
# left behind by unused slots (e.g. "ns1,ns2,,").
gsub(/,+/, ",", ns)
sub(/^,/, "", ns)
sub(/,$/, "", ns)
if (ns != "")
printf " %s: %s\n", $1, ns
}' /var/cpanel/resellers-nameservers 2>/dev/null ||
true
)
# One line per reseller, so the line count is the reseller count.
if [ -n "$custom_reseller_ns" ]; then
custom_reseller_ns_count=$(wc -l <<<"$custom_reseller_ns" | tr -d ' ')
else
custom_reseller_ns_count=0
fi
printf '\nCustom reseller nameservers (%s reseller(s)):\n' \
"$custom_reseller_ns_count"
if [ -n "$custom_reseller_ns" ]; then
printf '%s\n' "$custom_reseller_ns"
else
printf ' None\n'
fi
# listips only flags an IP as "dedicated" when a single account uses it,
# which misses IPs delegated to resellers as their shared IP. Instead,
# report every account that is not on the server's main shared IP and
# classify how its IP is used.
server_shared_ip=$(whm_call get_shared_ip | jq -r '.data.ip // empty')
if [ -z "$server_shared_ip" ] || [ "$server_shared_ip" = "~" ]; then
# listips can flag several addresses as main on multi-interface
# servers; only ever use one so later exact-match comparisons work.
server_shared_ip=$(
jq -r 'first(.data.ip[]? | select((.mainaddr | tonumber) == 1) | .ip) // empty' <<<"$ip_json"
)
fi
# Map of IP -> list of resellers using it as their shared IP. An IP
# with multiple resellers is effectively a shared pool IP.
reseller_shared_map=$(
whm_call listresellers |
jq -r '.data.reseller[]?' |
while read -r reseller; do
reseller_ip=$(whm_call get_shared_ip user="$reseller" | jq -r '.data.ip // empty')
if [ -n "$reseller_ip" ] &&
[ "$reseller_ip" != "~" ] &&
[ "$reseller_ip" != "$server_shared_ip" ]; then
printf '%s\t%s\n' "$reseller_ip" "$reseller"
fi
done |
jq -Rn '
[inputs | split("\t") | {ip: .[0], reseller: .[1]}]
| group_by(.ip)
| map({key: .[0].ip, value: map(.reseller)})
| from_entries
'
)
# Grouped by IP: an "H" line per qualifying IP followed by an "A" line
# per account on it.
dedicated_ip_groups=$(
jq -rn \
--argjson acct "$acct_json" \
--argjson reseller_shared "$reseller_shared_map" \
--arg shared_ip "$server_shared_ip" '
[
$acct.data.acct[]
| select(.ip != $shared_ip)
| ($reseller_shared[.ip] // []) as $resellers
# IPs shared between multiple resellers are treated as shared IPs
# and skipped, like the main shared IP.
| select(($resellers | length) <= 1)
]
| group_by(.ip)[]
| ($reseller_shared[.[0].ip] // []) as $resellers
# With no reseller attached, the IP only counts as dedicated when a
# single account uses it.
| select(($resellers | length) == 1 or length == 1)
| (
[
"H",
.[0].ip,
(if ($resellers | length) == 1 then "reseller-shared (" + $resellers[0] + ")"
else "dedicated (owner: " + .[0].owner + ")"
end),
(length | tostring)
],
(.[] | ["A", .user, .domain])
)
| @tsv
'
)
if [ -n "$dedicated_ip_groups" ]; then
dedicated_ip_count=$(grep -c '^H' <<<"$dedicated_ip_groups")
else
dedicated_ip_count=0
fi
printf '\nDedicated IPs (single account or single reseller): %s\n' \
"$dedicated_ip_count"
if [ "$dedicated_ip_count" -gt 0 ]; then
while IFS=$'\t' read -r tag field1 field2 field3; do
case "$tag" in
H)
printf '\n %s %s (%s account(s))\n' "$field1" "$field2" "$field3"
printf ' %-16s %s\n' 'USER' 'DOMAIN'
;;
A)
printf ' %-16s %s\n' "$field1" "$field2"
;;
esac
done <<<"$dedicated_ip_groups"
else
printf ' None\n'
fi