This commit is contained in:
@@ -3,13 +3,15 @@
|
||||
# sync-zones.sh — rsync-based zone replication from primary to secondary
|
||||
#
|
||||
# Runs on the SECONDARY (netinfra-02). Syncs the zones/ directory from the
|
||||
# primary (netinfra-01) every 60 seconds. When a zone file changes, Technitium
|
||||
# detects the modification and reloads automatically.
|
||||
# primary (netinfra-01) every 60 seconds. When rsync changes any zone file,
|
||||
# Technitium is reloaded (container restart) so it serves the fresh zones —
|
||||
# Technitium does NOT reliably auto-detect externally modified zone files,
|
||||
# which left the secondary serving stale records (seen 2026-09-01 [#344]).
|
||||
#
|
||||
# This is used instead of AXFR-based zone transfer because Technitium's zone
|
||||
# transfer mechanism uses port 53 (standard DNS), but on the netinfra hosts
|
||||
# port 53 is Pi-hole and Technitium is on port 5300. rsync-based replication
|
||||
# avoids the port conflict entirely.
|
||||
# avoids the port conflict entirely. Native clustering tracked in [#469].
|
||||
#
|
||||
# Install as a systemd service/timer or run via cron:
|
||||
# * * * * * /home/localuser/services/technitium/sync-zones.sh
|
||||
@@ -21,23 +23,50 @@ CONFIG_DIR="${CONFIG_DIR:-/home/localuser/services/technitium/config}"
|
||||
ZONE_DIR="$CONFIG_DIR/zones"
|
||||
LOCK_FILE="/tmp/technitium-zone-sync.lock"
|
||||
LOG_FILE="${LOG_FILE:-/home/localuser/services/technitium/sync.log}"
|
||||
# Reload command; array form so tests can substitute a fake.
|
||||
read -r -a RELOAD_CMD <<< "${RELOAD_CMD:-docker restart tsys-dns}"
|
||||
export RELOAD_CMD
|
||||
|
||||
log() { printf '[%s] %s\n' "$(date +%H:%M:%S)" "$*" >> "$LOG_FILE"; }
|
||||
|
||||
# Prevent overlapping runs
|
||||
exec 9>"$LOCK_FILE" || exit 0
|
||||
flock -n 9 || { log "another sync is running; skipping"; exit 0; }
|
||||
|
||||
mkdir -p "$ZONE_DIR"
|
||||
|
||||
# rsync zones from primary. Use --temp-dir to avoid partial writes being
|
||||
# picked up by Technitium, and --delete to remove zones deleted on primary.
|
||||
log "Syncing zones from $PRIMARY_HOST..."
|
||||
if rsync -az --delete --temp-dir=/tmp \
|
||||
"${PRIMARY_HOST}:$ZONE_DIR/" "$ZONE_DIR/" >> "$LOG_FILE" 2>&1; then
|
||||
# Sync zones from the primary and reload Technitium when anything changed.
|
||||
# Returns non-zero when rsync fails (reload failures are logged, not fatal).
|
||||
sync_and_reload() {
|
||||
local changed rc zone_count changed_count
|
||||
# --temp-dir avoids partial writes; --delete removes zones deleted on
|
||||
# primary; --itemize-changes reports exactly what changed.
|
||||
changed=$(rsync -az --delete --itemize-changes --out-format='%n' --temp-dir=/tmp \
|
||||
"${PRIMARY_HOST}:$ZONE_DIR/" "$ZONE_DIR/" 2>> "$LOG_FILE")
|
||||
rc=$?
|
||||
if [ "$rc" -ne 0 ]; then
|
||||
return "$rc"
|
||||
fi
|
||||
zone_count=$(find "$ZONE_DIR" -maxdepth 1 -type f | wc -l)
|
||||
log "Sync complete: $zone_count zones"
|
||||
else
|
||||
log "ERROR: rsync failed (rc=$?)"
|
||||
exit 1
|
||||
if [ -n "$changed" ]; then
|
||||
changed_count=$(printf '%s\n' "$changed" | wc -l)
|
||||
log "Sync complete: $zone_count zones, $changed_count changed — reloading Technitium"
|
||||
if "${RELOAD_CMD[@]}" >> "$LOG_FILE" 2>&1; then
|
||||
log "Technitium reload triggered"
|
||||
else
|
||||
log "WARN: reload command failed (rc=$?) — records may be stale until next change"
|
||||
fi
|
||||
else
|
||||
log "Sync complete: $zone_count zones (no changes)"
|
||||
fi
|
||||
}
|
||||
|
||||
main() {
|
||||
# Prevent overlapping runs
|
||||
exec 9>"$LOCK_FILE" || exit 0
|
||||
flock -n 9 || { log "another sync is running; skipping"; exit 0; }
|
||||
|
||||
mkdir -p "$ZONE_DIR"
|
||||
|
||||
log "Syncing zones from $PRIMARY_HOST..."
|
||||
sync_and_reload || { log "ERROR: rsync failed (rc=$?)"; exit 1; }
|
||||
}
|
||||
|
||||
# Allow sourcing (unit tests) without executing the sync
|
||||
if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
|
||||
main
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user