Add dual Pi-hole high availability setup guide, deployment notes, and disk optimization docs. Update NPM + Pi-hole sync script and docs. Add UniFi DNS firewall troubleshooting and networking scripts CONTEXT. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
164 lines
5.2 KiB
Bash
Executable File
164 lines
5.2 KiB
Bash
Executable File
#!/bin/bash
|
|
# NPM to Pi-hole DNS Sync (Pi-hole v6 compatible)
|
|
# Syncs Nginx Proxy Manager proxy hosts to Pi-hole local DNS
|
|
# All domains point to NPM's IP, not the forward destination
|
|
# Supports dual Pi-hole deployment for high availability
|
|
#
|
|
# Pi-hole v6 changes:
|
|
# - Updates /etc/pihole/pihole.toml (dns.hosts array)
|
|
# - Updates /etc/pihole/hosts/custom.list (traditional format)
|
|
|
|
set -e
|
|
|
|
DRY_RUN=false
|
|
if [[ "$1" == "--dry-run" ]]; then
|
|
DRY_RUN=true
|
|
fi
|
|
|
|
# NPM's IP address (where all domains should point)
|
|
NPM_IP="10.10.0.16"
|
|
|
|
# Pi-hole instances
|
|
PRIMARY_PIHOLE="10.10.0.16"
|
|
SECONDARY_PIHOLE="10.10.0.226"
|
|
|
|
echo "NPM → Pi-hole DNS Sync (v6 compatible)"
|
|
echo "============================================================"
|
|
|
|
# Query NPM database for all enabled proxy hosts
|
|
DOMAINS=$(docker exec nginx-proxy-manager_app_1 python3 -c '
|
|
import sqlite3
|
|
import json
|
|
|
|
conn = sqlite3.connect("/data/database.sqlite")
|
|
cursor = conn.cursor()
|
|
cursor.execute("SELECT domain_names FROM proxy_host WHERE enabled = 1")
|
|
|
|
domains = []
|
|
for (domain_names,) in cursor.fetchall():
|
|
for domain in json.loads(domain_names or "[]"):
|
|
domains.append(domain)
|
|
|
|
for domain in sorted(domains):
|
|
print(domain)
|
|
|
|
conn.close()
|
|
')
|
|
|
|
# Count records
|
|
RECORD_COUNT=$(echo "$DOMAINS" | wc -l)
|
|
echo "Found $RECORD_COUNT enabled proxy hosts"
|
|
echo ""
|
|
echo "All domains will point to NPM at: $NPM_IP"
|
|
echo ""
|
|
echo "Domains to sync:"
|
|
echo "$DOMAINS" | awk -v ip="$NPM_IP" '{printf " %-15s %s\n", ip, $0}'
|
|
|
|
if [ "$DRY_RUN" = true ]; then
|
|
echo ""
|
|
echo "[DRY RUN] Not applying changes"
|
|
exit 0
|
|
fi
|
|
|
|
# Build new custom.list (traditional format)
|
|
NEW_DNS="# Pi-hole Local DNS Records
|
|
# Auto-synced from Nginx Proxy Manager
|
|
# All domains point to NPM at $NPM_IP
|
|
|
|
"
|
|
|
|
while IFS= read -r domain; do
|
|
NEW_DNS+="$NPM_IP $domain"$'\n'
|
|
done <<< "$DOMAINS"
|
|
|
|
# Build TOML hosts array
|
|
TOML_HOSTS=""
|
|
while IFS= read -r domain; do
|
|
TOML_HOSTS+=" \"$NPM_IP $domain\","$'\n'
|
|
done <<< "$DOMAINS"
|
|
# Remove trailing comma from last entry
|
|
TOML_HOSTS=$(echo "$TOML_HOSTS" | sed '$ s/,$//')
|
|
|
|
echo ""
|
|
echo "Syncing to Pi-hole instances..."
|
|
echo "============================================================"
|
|
|
|
# Function to update Pi-hole v6 configuration
|
|
update_pihole_v6() {
|
|
local pihole_container="$1"
|
|
local ssh_prefix="$2" # Empty for local, "ssh user@host" for remote
|
|
|
|
# Write to /etc/pihole/hosts/custom.list
|
|
if [ -z "$ssh_prefix" ]; then
|
|
echo "$NEW_DNS" | docker exec -i "$pihole_container" tee /etc/pihole/hosts/custom.list > /dev/null 2>&1
|
|
else
|
|
$ssh_prefix "echo '${NEW_DNS}' | docker exec -i $pihole_container tee /etc/pihole/hosts/custom.list > /dev/null" 2>&1
|
|
fi
|
|
|
|
# Update pihole.toml using perl for reliable multi-line regex replacement
|
|
# Escape special characters for perl
|
|
local toml_hosts_perl=$(echo "$TOML_HOSTS" | sed 's/\\/\\\\/g; s/"/\\"/g')
|
|
|
|
local perl_cmd="perl -i.bak -0pe 's/hosts\\s*=\\s*\\[[^\\]]*\\]/hosts = [\\n${toml_hosts_perl}\\n ]/s' /etc/pihole/pihole.toml"
|
|
|
|
if [ -z "$ssh_prefix" ]; then
|
|
docker exec "$pihole_container" sh -c "$perl_cmd"
|
|
else
|
|
$ssh_prefix "docker exec $pihole_container sh -c \"$perl_cmd\""
|
|
fi
|
|
}
|
|
|
|
# Sync to primary Pi-hole (local)
|
|
echo "Primary Pi-hole ($PRIMARY_PIHOLE):"
|
|
if update_pihole_v6 "pihole" ""; then
|
|
if docker exec pihole pihole reloaddns > /dev/null 2>&1; then
|
|
echo " ✓ Updated $RECORD_COUNT DNS records"
|
|
echo " ✓ Updated /etc/pihole/hosts/custom.list"
|
|
echo " ✓ Updated /etc/pihole/pihole.toml"
|
|
echo " ✓ Reloaded DNS"
|
|
else
|
|
echo " ✗ Failed to reload DNS"
|
|
PRIMARY_SYNC_FAILED=1
|
|
fi
|
|
else
|
|
echo " ✗ Failed to update Pi-hole configuration"
|
|
PRIMARY_SYNC_FAILED=1
|
|
fi
|
|
|
|
# Sync to secondary Pi-hole (remote via SSH using IP)
|
|
echo "Secondary Pi-hole ($SECONDARY_PIHOLE):"
|
|
if update_pihole_v6 "pihole" "ssh -o StrictHostKeyChecking=no cal@$SECONDARY_PIHOLE"; then
|
|
if ssh -o StrictHostKeyChecking=no "cal@$SECONDARY_PIHOLE" "docker exec pihole pihole reloaddns > /dev/null" 2>&1; then
|
|
echo " ✓ Updated $RECORD_COUNT DNS records"
|
|
echo " ✓ Updated /etc/pihole/hosts/custom.list"
|
|
echo " ✓ Updated /etc/pihole/pihole.toml"
|
|
echo " ✓ Reloaded DNS"
|
|
else
|
|
echo " ✗ Failed to reload DNS"
|
|
SECONDARY_SYNC_FAILED=1
|
|
fi
|
|
else
|
|
echo " ✗ Failed to update Pi-hole configuration or SSH connection issue"
|
|
SECONDARY_SYNC_FAILED=1
|
|
fi
|
|
|
|
echo ""
|
|
if [ -z "$PRIMARY_SYNC_FAILED" ] && [ -z "$SECONDARY_SYNC_FAILED" ]; then
|
|
echo "✓ Successfully synced to both Pi-hole instances"
|
|
echo "✓ All $RECORD_COUNT domains now point to NPM at $NPM_IP"
|
|
echo "✓ Updated both pihole.toml and custom.list files"
|
|
exit 0
|
|
elif [ -z "$PRIMARY_SYNC_FAILED" ] && [ -n "$SECONDARY_SYNC_FAILED" ]; then
|
|
echo "⚠ Primary sync successful, but secondary sync failed"
|
|
echo " Check SSH connectivity to $SECONDARY_PIHOLE and secondary Pi-hole health"
|
|
exit 1
|
|
elif [ -n "$PRIMARY_SYNC_FAILED" ] && [ -z "$SECONDARY_SYNC_FAILED" ]; then
|
|
echo "⚠ Secondary sync successful, but primary sync failed"
|
|
echo " Check primary Pi-hole health"
|
|
exit 1
|
|
else
|
|
echo "✗ Both Pi-hole syncs failed"
|
|
echo " Check Pi-hole containers and SSH connectivity"
|
|
exit 2
|
|
fi
|