Skip to content

Instantly share code, notes, and snippets.

@BluSyn
Last active November 5, 2025 01:52
Show Gist options
  • Select an option

  • Save BluSyn/37670b7b9778489fb8394cb90de57294 to your computer and use it in GitHub Desktop.

Select an option

Save BluSyn/37670b7b9778489fb8394cb90de57294 to your computer and use it in GitHub Desktop.
updates unifi internal DNS with IPv6 addresses (needed for dual WAN failover scenario, where local IPv4 is same, but global IPv6 changes)
#!/bin/bash
# Script to update UniFi DNS AAAA record with current IPv6
# --- Config ---
CONTROLLER="https://192.168.1.1" # Your UniFi controller URL (include port if not 443)
SITE="default" # Site name, usually "default"
API_KEY="<KEY>" # UniFi api key (Settings > Control Plane > Integrations)
HOSTNAMES="example.org *.example.org" # The DNS hostname to update (e.g., nas.mylan)
INTERFACE="eth0" # Network interface with global IPv6 (check with ip -6 addr)
# --- Get current global IPv6 ---
IPV6=$(ip -6 addr show dev "$INTERFACE" scope global | awk '/inet6/ {print $2}' | cut -d/ -f1 | head -n1)
if [ -z "$IPV6" ]; then
echo "Error: No global IPv6 found on $INTERFACE" >&2
exit 1
fi
ENDPOINT="$CONTROLLER/proxy/network/v2/api/site/$SITE/static-dns"
CURRENT="/tmp/unifi_dns.json"
# --- Get current DNS settings ---
curl --silent -k -H "X-API-KEY: $API_KEY" "$ENDPOINT" > "$CURRENT"
if [ ! -s "$CURRENT" ]; then
echo "Error: Failed to get DNS settings" >&2
exit 1
fi
# --- Loop over each hostname ---
for HOSTNAME in $HOSTNAMES; do
# Find existing record
EXISTING=$(jq -r --arg h "$HOSTNAME" 'map(select(.key == $h and .record_type == "AAAA")) | .[0]' "$CURRENT")
EXISTING_ID=$(echo "$EXISTING" | jq -r '._id // ""')
EXISTING_VALUE=$(echo "$EXISTING" | jq -r '.value // ""')
# Skip if exists and value matches
if [ -n "$EXISTING_ID" ] && [ "$EXISTING_VALUE" = "$IPV6" ]; then
echo "No change needed for $HOSTNAME (already $IPV6)"
continue
fi
# Build payload
PAYLOAD=$(jq -n --arg h "$HOSTNAME" --arg v "$IPV6" '{
enabled: true,
key: $h,
port: 0,
priority: 0,
record_type: "AAAA",
ttl: 0,
value: $v,
weight: 0
}')
# Update or add
if [ -n "$EXISTING_ID" ]; then
# Update existing
curl -k -H "X-API-KEY: $API_KEY" -H "Content-Type: application/json" -X PUT "$ENDPOINT/$EXISTING_ID" -d "$PAYLOAD" >/dev/null 2>&1
echo "Updated $HOSTNAME to $IPV6"
else
# Add new
curl -k -H "X-API-KEY: $API_KEY" -H "Content-Type: application/json" -X POST "$ENDPOINT" -d "$PAYLOAD" >/dev/null 2>&1
echo "Added $HOSTNAME as $IPV6"
fi
done
# --- Cleanup ---
rm -f "$CURRENT"
#!/bin/bash
# Script to monitor IPv6 changes and update UniFi DNS only on actual IP change
# Run as systemd service
INTERFACE="eth0" # Your interface with global IPv6
UPDATE_SCRIPT="/usr/local/bin/unifi-dns-update" # Path to the script from previous messages
# Function to get current primary global IPv6 (non-temporary)
get_ipv6() {
ip -6 addr show dev "$INTERFACE" scope global | grep -v "temporary" | awk '/inet6/ {print $2}' | cut -d/ -f1 | head -n1
}
# Get initial IPv6
LAST_IPV6=$(get_ipv6)
if [ -z "$LAST_IPV6" ]; then
echo "Error: No initial global IPv6 found on $INTERFACE" >&2
exit 1
fi
echo "Starting monitor on $INTERFACE, initial IPv6: $LAST_IPV6"
# call update in init to keep in sync
"$UPDATE_SCRIPT"
# Monitor address events
ip -o -6 monitor address dev "$INTERFACE" | while read line; do
# Parse event
if [[ "$line" == "Deleted "* ]]; then
action="del"
rest="${line#Deleted }"
else
action="add"
rest="$line"
fi
# Extract addr and scope
addr=$(echo "$rest" | awk '{print $4}' | cut -d/ -f1)
scope=$(echo "$rest" | awk '{for(i=1;i<=NF;i++) if ($i=="scope") print $(i+1)}')
# Filter for global non-temporary
if [[ "$scope" == "global" && "$rest" != *"temporary"* && -n "$addr" ]]; then
echo "IPv6 $action event: $addr"
# Re-fetch current IPv6 after event
NEW_IPV6=$(get_ipv6)
# Only trigger if actual change and new IP is present
if [ -n "$NEW_IPV6" ] && [ "$NEW_IPV6" != "$LAST_IPV6" ]; then
echo "Actual IPv6 change detected: $LAST_IPV6 -> $NEW_IPV6 - Running update"
"$UPDATE_SCRIPT"
LAST_IPV6="$NEW_IPV6"
fi
fi
done
[Unit]
Description=Monitor IPv6 changes and update UniFi DNS
After=network.target
[Service]
Type=simple
ExecStart=/usr/local/bin/unifi-ipv6-monitor
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment