From 3f6ede49bcd0e1c1d0ec6d9955d266d8831578e0 Mon Sep 17 00:00:00 2001 From: Ihor Rusyn <0kaba0@gmail.com> Date: Sun, 6 Sep 2026 11:23:12 +0200 Subject: [PATCH] Restart PowerDNS instead of reloading it, and notice when it stays down MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On the live cPanel box the zone reached SecondDNS, the worker created it on ns3, and then sat at pending forever: 08:57:12 done queue=pdns.zone-add zone=my123domain.com 08:57:12 notify empty, staying pending AXFR was refused. The installer had written allow-axfr-ips into pdns.conf and run `systemctl reload pdns` — and a reload rereads the zones, not the server settings. The setting sat in the file with no effect until something restarted the service. A customer would see zones registered in SecondDNS and empty on the secondary, with nothing to explain it. Proven in place: `dig AXFR` refused after the reload, the same query answered after `systemctl restart pdns`, and my123domain.com moved pending -> synced within seconds. cPanel was the only installer using reload; CyberPanel and DirectAdmin already restart. Writing the guard turned up a second one: DirectAdmin restarted PowerDNS and reported success without checking it came back, so a config it could not parse would leave DNS down under a "[+] configured and restarted". It now verifies and restores the backup, as CyberPanel does. tests/test_pdns_restart.sh asserts, for every installer that touches pdns.conf: no reload, a restart, a message when it stays down, and a rollback to the backup. Lower bound of 3 installers so a rename fails instead of matching nothing. Mutations: restoring the reload fails 1, dropping the down-message fails 2, dropping the rollback fails 1. --- .github/workflows/tests.yml | 3 ++ .../common/tests/test_pdns_restart.sh | 44 +++++++++++++++++++ hosting-panels/cpanel/install.sh | 21 ++++++--- hosting-panels/directadmin/install.sh | 15 ++++++- 4 files changed, 74 insertions(+), 9 deletions(-) create mode 100755 hosting-panels/common/tests/test_pdns_restart.sh diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 64c8eed..732e041 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -56,6 +56,9 @@ jobs: - name: test_cli.sh run: bash hosting-panels/common/tests/test_cli.sh + - name: test_pdns_restart.sh + run: bash hosting-panels/common/tests/test_pdns_restart.sh + - name: test_cyberpanel_db.sh run: bash hosting-panels/common/tests/test_cyberpanel_db.sh diff --git a/hosting-panels/common/tests/test_pdns_restart.sh b/hosting-panels/common/tests/test_pdns_restart.sh new file mode 100755 index 0000000..b353481 --- /dev/null +++ b/hosting-panels/common/tests/test_pdns_restart.sh @@ -0,0 +1,44 @@ +#!/bin/bash +# PowerDNS rereads zones on a reload but not allow-axfr-ips, so a reload after +# editing pdns.conf leaves AXFR refused and every zone stuck pending. +# Run: bash hosting-panels/common/tests/test_pdns_restart.sh +set -u +HERE="$(cd "$(dirname "$0")" && pwd)" +PANELS="cpanel cyberpanel directadmin plesk" +PASS=0; FAIL=0 +ok() { PASS=$((PASS+1)); echo " ok: $1"; } +fail() { FAIL=$((FAIL+1)); echo " FAIL: $1"; } + +checked=0 +for panel in $PANELS; do + src="$HERE/../../$panel/install.sh" + [ -f "$src" ] || { echo "missing $src"; exit 1; } + grep -q "pdns" "$src" || continue + checked=$((checked+1)) + echo "== $panel" + + if grep -qE "systemctl reload pdns|pdns_control reload|service pdns reload" "$src"; then + fail "reloads PowerDNS after editing pdns.conf" + else + ok "no PowerDNS reload" + fi + + # editing the config at all obliges a restart + if grep -q 'allow-axfr-ips' "$src"; then + grep -qE "(systemctl|service) .*pdns.* restart|systemctl restart pdns" "$src" \ + && ok "restarts PowerDNS" || fail "edits allow-axfr-ips without restarting" + # a restart that does not come back must not be reported as success: + # the config is restored and the operator told, not a cheerful "[+]" + grep -q 'did not come back' "$src" \ + && ok "reports a PowerDNS that stayed down" || fail "a dead PowerDNS would be reported as success" + grep -qE 'cp "[^"]*(BAK|bak)[^"]*" "\$PDNS_CONF"' "$src" \ + && ok "restores the config on a failed restart" || fail "no rollback on a failed restart" + fi +done + +# a lower bound: a rename that stops matching the installers fails here +[ "$checked" -ge 3 ] && ok "checked $checked installers" || fail "only $checked installers matched" + +echo +echo "passed=$PASS failed=$FAIL" +[ "$FAIL" -eq 0 ] diff --git a/hosting-panels/cpanel/install.sh b/hosting-panels/cpanel/install.sh index a01c8ac..14b3fe9 100755 --- a/hosting-panels/cpanel/install.sh +++ b/hosting-panels/cpanel/install.sh @@ -351,7 +351,8 @@ else if [ ! -f "$PDNS_CONF" ]; then echo "[!] pdns.conf not found at $PDNS_CONF — configure AXFR manually" else - cp "$PDNS_CONF" "${PDNS_CONF}.bak.$(date +%s)" + PDNS_BAK="${PDNS_CONF}.bak.$(date +%s)" + cp "$PDNS_CONF" "$PDNS_BAK" # primary / master mode (name differs by pdns version) if grep -q "^master=" "$PDNS_CONF" 2>/dev/null; then @@ -401,13 +402,19 @@ else echo "[+] PowerDNS: also-notify=$SECONDARY_IP" fi - # Reload - if systemctl reload pdns &>/dev/null 2>&1; then - echo "[+] PowerDNS reloaded" - elif pdns_control reload &>/dev/null 2>&1; then - echo "[+] PowerDNS reloaded via pdns_control" + # A reload rereads the zones, not the settings above: allow-axfr-ips + # only takes effect on a restart, and until then AXFR is refused. + systemctl restart pdns 2>/dev/null || service pdns restart 2>/dev/null + sleep 2 + if systemctl is-active --quiet pdns 2>/dev/null || pgrep -x pdns_server >/dev/null; then + echo "[+] PowerDNS configured and restarted" else - echo "[!] Restart PowerDNS manually: systemctl restart pdns" + # Saying "OK" while the DNS server is down is worse than + # failing: restore what was there and let a human look. + [ -n "${PDNS_BAK:-}" ] && cp "$PDNS_BAK" "$PDNS_CONF" + systemctl restart pdns 2>/dev/null || service pdns restart 2>/dev/null + echo "[!] PowerDNS did not come back — configuration restored from the backup" + echo "[!] Check: journalctl -u pdns -n 20" fi fi diff --git a/hosting-panels/directadmin/install.sh b/hosting-panels/directadmin/install.sh index eb3669e..94c3d27 100644 --- a/hosting-panels/directadmin/install.sh +++ b/hosting-panels/directadmin/install.sh @@ -344,7 +344,8 @@ if [ -n "$DNS_IPS" ]; then if [ "$ISSUES" -gt 0 ]; then if confirm "Apply PowerDNS fixes automatically? (backup will be created)"; then - cp "$PDNS_CONF" "${PDNS_CONF}.bak.$(date +%s)" + PDNS_BAK="${PDNS_CONF}.bak.$(date +%s)" + cp "$PDNS_CONF" "$PDNS_BAK" grep -qE "^master=yes" "$PDNS_CONF" || echo "master=yes" >> "$PDNS_CONF" grep -qE "^default-soa-edit=" "$PDNS_CONF" || echo "default-soa-edit=INCEPTION-INCREMENT" >> "$PDNS_CONF" @@ -364,7 +365,17 @@ if [ -n "$DNS_IPS" ]; then fi systemctl restart pdns 2>/dev/null || service pdns restart 2>/dev/null - echo "[+] PowerDNS configured and restarted" + sleep 2 + if systemctl is-active --quiet pdns 2>/dev/null || pgrep -x pdns_server >/dev/null; then + echo "[+] PowerDNS configured and restarted" + else + # Saying "OK" while the DNS server is down is worse than + # failing: restore what was there and let a human look. + cp "$PDNS_BAK" "$PDNS_CONF" + systemctl restart pdns 2>/dev/null || service pdns restart 2>/dev/null + echo "[!] PowerDNS did not come back — configuration restored from the backup" + echo "[!] Check: journalctl -u pdns -n 20" + fi fi else echo "[+] PowerDNS AXFR config OK"