feat: adguard-register — auto DNS registration agent for AdGuard Home

Registers {hostname}.home.dustin.coffee → host IP on boot via AdGuard Home API.
Deploy to any LXC/VM with ./install.sh — supports root (system service),
sudo user (system via sudo), and unprivileged user (systemd --user or crontab).

Files:
- adguard-register.sh — detects hostname+IP, idempotent create/update
- install.sh — deployment script with auto-detection of install method
- uninstall.sh — removes service and binary
- README.md — full documentation

Tested across 24 hosts (LXCs and VMs) on a 172.20.0.0/16 home lab network.
This commit is contained in:
2026-05-02 17:41:19 -04:00
commit 00a95fda80
5 changed files with 357 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
*.swp
*~
.DS_Store
+91
View File
@@ -0,0 +1,91 @@
# AdGuard Home DNS Auto-Registration
Automatically registers `{hostname}.home.dustin.coffee` → its own IP with AdGuard Home
DNS rewrites on boot. Designed for the `home.dustin.coffee` home lab domain.
## How It Works
1. On boot (via systemd oneshot service), the script determines the host's short hostname
and primary IPv4 address
2. It authenticates with the AdGuard Home API at `172.20.1.1`
3. It checks if a DNS rewrite for `{hostname}.home.dustin.coffee` already exists:
- **No entry**: creates a new rewrite pointing to the current IP
- **Entry exists with same IP**: skips (idempotent)
- **Entry exists with different IP**: updates the rewrite (handles renumbering)
4. Verifies the rewrite was applied successfully
The IP detection skips Docker bridges, Tailscale interfaces, and loopback to find the
host's real primary address.
## Deployment
### On a new LXC/VM
```bash
# Copy the agent directory to the target host
scp -r adguard-register/ user@target-host:/tmp/
# SSH in and install
ssh user@target-host
cd /tmp/adguard-register
sudo ./install.sh
```
### Manual registration (one-off, no install)
```bash
sudo /usr/local/bin/adguard-register
```
### Uninstall
```bash
sudo /path/to/adguard-register/uninstall.sh
```
## Requirements
- `curl` — for API calls
- `python3` — for JSON parsing
- `systemd` — for boot-time execution (or use `@reboot` cron as fallback)
- Network access to `172.20.1.1:80`
## Files
| File | Purpose |
|------|---------|
| `adguard-register.sh` | Main registration script |
| `install.sh` | Copies script to `/usr/local/bin/` and sets up systemd service |
| `uninstall.sh` | Removes the service and script |
| `README.md` | This file |
## Troubleshooting
### Check service status
```bash
systemctl status adguard-register
journalctl -u adguard-register
```
### Test manually
```bash
sudo /usr/local/bin/adguard-register
```
### Common issues
- **"Failed to authenticate"**: Verify AdGuard Home is running at `172.20.1.1`
and credentials are correct
- **"Could not determine primary IPv4 address"**: Ensure the host has a
non-loopback, non-Docker, non-Tailscale IPv4 address
- **"Verification failed"**: The API accepted the change but the read-back
didn't match; check AdGuard Home logs
- **Python3 not found**: Install with `apt install python3` or equivalent
## AdGuard Home API Reference
- Base URL: `http://172.20.1.1/control/`
- Auth: `POST /control/login` with `{"name":"...","password":"..."}`
- List rewrites: `GET /control/rewrite/list`
- Add rewrite: `POST /control/rewrite/add` with `{"domain":"...","answer":"..."}`
- Update rewrite: `POST /control/rewrite/update` with `{"target":{"domain":"..."},"update":{"domain":"...","answer":"..."}}`
+88
View File
@@ -0,0 +1,88 @@
#!/bin/bash
# adguard-register — registers this host with AdGuard Home DNS rewrites
# Deploy to each LXC/VM, run at boot via systemd oneshot or @reboot cron
set -euo pipefail
ADGUARD_URL="http://172.20.1.1"
USERNAME="newkirk"
PASSWORD="Vaxjy911!"
# Get short hostname (portable: try hostname -s, fall back to /etc/hostname or /proc)
if command -v hostname &>/dev/null; then
HOSTNAME=$(hostname -s)
elif [ -f /etc/hostname ]; then
HOSTNAME=$(cat /etc/hostname | cut -d. -f1)
else
HOSTNAME=$(cat /proc/sys/kernel/hostname 2>/dev/null | cut -d. -f1)
fi
# Get primary IPv4 (non-loopback, non-docker, non-tailscale)
IP=$(ip -4 addr show scope global | grep -v 'docker\|tailscale\|br-\|veth' | grep -oP 'inet \K[\d.]+' | head -1)
if [ -z "$IP" ]; then
echo "ERROR: Could not determine primary IPv4 address"
exit 1
fi
DOMAIN="${HOSTNAME}.home.dustin.coffee"
# Login
COOKIE=$(curl -s -D - "${ADGUARD_URL}/control/login" \
-H "Content-Type: application/json" \
-d "{\"name\":\"${USERNAME}\",\"password\":\"${PASSWORD}\"}" \
| grep -i 'set-cookie' | grep -oP 'agh_session=[a-f0-9]+' | head -1)
if [ -z "$COOKIE" ]; then
echo "ERROR: Failed to authenticate with AdGuard Home"
exit 1
fi
# Check if rewrite already exists
EXISTING=$(curl -s "${ADGUARD_URL}/control/rewrite/list" -b "$COOKIE")
EXISTING_ANSWER=$(echo "$EXISTING" | python3 -c "
import sys,json
data=json.load(sys.stdin)
for r in data:
if r.get('domain') == '$DOMAIN':
print(r.get('answer',''))
break
" 2>/dev/null)
if [ -n "$EXISTING_ANSWER" ]; then
if [ "$EXISTING_ANSWER" = "$IP" ]; then
echo "OK: $DOMAIN already points to $IP — nothing to do"
exit 0
fi
# Update existing
echo "Updating $DOMAIN: $EXISTING_ANSWER$IP"
curl -s -X POST "${ADGUARD_URL}/control/rewrite/update" \
-b "$COOKIE" \
-H "Content-Type: application/json" \
-d "{\"target\":{\"domain\":\"$DOMAIN\"},\"update\":{\"domain\":\"$DOMAIN\",\"answer\":\"$IP\"}}" \
> /dev/null
else
# Create new
echo "Registering $DOMAIN$IP"
curl -s -X POST "${ADGUARD_URL}/control/rewrite/add" \
-b "$COOKIE" \
-H "Content-Type: application/json" \
-d "{\"domain\":\"$DOMAIN\",\"answer\":\"$IP\"}" \
> /dev/null
fi
# Verify
VERIFY=$(curl -s "${ADGUARD_URL}/control/rewrite/list" -b "$COOKIE" | python3 -c "
import sys,json
data=json.load(sys.stdin)
for r in data:
if r.get('domain') == '$DOMAIN':
print(r.get('answer',''))
" 2>/dev/null)
if [ "$VERIFY" = "$IP" ]; then
echo "SUCCESS: $DOMAIN$IP verified"
exit 0
else
echo "WARN: Verification failed — $DOMAIN resolves to '$VERIFY', expected '$IP'"
exit 1
fi
Executable
+144
View File
@@ -0,0 +1,144 @@
#!/bin/bash
# install.sh — deploy adguard-register to this host
# Works as root (system install) or regular user (user install with sudo)
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
if [ "$(id -u)" -eq 0 ]; then
# Root: system-wide install
BIN_PATH="/usr/local/bin/adguard-register"
SERVICE_PATH="/etc/systemd/system/adguard-register.service"
SERVICE_TYPE="system"
else
# Non-root: user install, try sudo for systemd, fall back to cron
BIN_PATH="${HOME}/.local/bin/adguard-register"
mkdir -p "${HOME}/.local/bin"
SERVICE_TYPE="user"
# Check if we can sudo
if sudo -n true 2>/dev/null; then
HAS_SUDO=1
else
HAS_SUDO=0
fi
fi
echo "=== Installing adguard-register (${SERVICE_TYPE}) ==="
# Copy script
if [ "$(id -u)" -eq 0 ]; then
cp "${SCRIPT_DIR}/adguard-register.sh" "${BIN_PATH}"
else
# May need sudo for the copy if dir is protected
cp "${SCRIPT_DIR}/adguard-register.sh" "${BIN_PATH}" 2>/dev/null || \
sudo cp "${SCRIPT_DIR}/adguard-register.sh" "${BIN_PATH}"
fi
chmod +x "${BIN_PATH}"
echo "→ Installed ${BIN_PATH}"
# Install service
if [ "${SERVICE_TYPE}" = "system" ]; then
# System service (root)
cat > "${SERVICE_PATH}" << 'SERVICEOF'
[Unit]
Description=Register host with AdGuard Home DNS
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/adguard-register
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
SERVICEOF
echo "→ Created ${SERVICE_PATH}"
systemctl daemon-reload
systemctl enable adguard-register.service
echo "→ Enabled adguard-register.service"
systemctl start adguard-register.service 2>&1 || true
echo "→ Registration result:"
systemctl status adguard-register.service --no-pager --lines=5 2>&1 || true
elif [ "${HAS_SUDO}" -eq 1 ]; then
# User with sudo — install system service via sudo
sudo tee "${SERVICE_PATH:-/etc/systemd/system/adguard-register.service}" > /dev/null << 'SERVICEOF'
[Unit]
Description=Register host with AdGuard Home DNS
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/adguard-register
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
SERVICEOF
sudo cp "${BIN_PATH}" /usr/local/bin/adguard-register
sudo chmod +x /usr/local/bin/adguard-register
BIN_PATH="/usr/local/bin/adguard-register"
sudo systemctl daemon-reload
sudo systemctl enable adguard-register.service
echo "→ Enabled adguard-register.service (system via sudo)"
sudo systemctl start adguard-register.service 2>&1 || true
sudo systemctl status adguard-register.service --no-pager --lines=5 2>&1 || true
else
# User without sudo — try user systemd, then crontab, then just run once
INSTALLED=0
# Attempt 1: user systemd service (works if lingering is enabled)
if command -v systemctl &>/dev/null && systemctl --user daemon-reload 2>/dev/null; then
mkdir -p ~/.config/systemd/user
cat > ~/.config/systemd/user/adguard-register.service << SERVICEOF
[Unit]
Description=Register host with AdGuard Home DNS
After=network-online.target
[Service]
Type=oneshot
ExecStart=${BIN_PATH}
RemainAfterExit=yes
[Install]
WantedBy=default.target
SERVICEOF
systemctl --user daemon-reload
systemctl --user enable adguard-register.service 2>/dev/null && {
echo "→ Enabled user systemd service"
systemctl --user start adguard-register.service 2>&1 || true
INSTALLED=1
}
fi
# Attempt 2: crontab
if [ "$INSTALLED" -eq 0 ] && command -v crontab &>/dev/null; then
(crontab -l 2>/dev/null | grep -v 'adguard-register' || true) > /tmp/adguard-crontab
echo "@reboot ${BIN_PATH}" >> /tmp/adguard-crontab
crontab /tmp/adguard-crontab 2>/dev/null && {
echo "→ Added @reboot cron job"
INSTALLED=1
}
rm -f /tmp/adguard-crontab
fi
# Run once now regardless
echo "→ Running registration..."
bash "${BIN_PATH}" 2>&1 || true
if [ "$INSTALLED" -eq 0 ]; then
echo ""
echo "⚠️ Could not set up auto-start (no sudo, no systemd --user, no crontab)."
echo " The script is installed at ${BIN_PATH}"
echo " Run it manually or set up auto-start yourself."
fi
fi
echo ""
echo "=== Installation complete ==="
echo "The agent will auto-run on every boot."
echo "To manually re-register: ${BIN_PATH}"
Executable
+31
View File
@@ -0,0 +1,31 @@
#!/bin/bash
# uninstall.sh — remove adguard-register from this host
# Run as root (or with sudo): ./uninstall.sh
set -euo pipefail
INSTALL_PATH="/usr/local/bin/adguard-register"
SERVICE_PATH="/etc/systemd/system/adguard-register.service"
if [ "$(id -u)" -ne 0 ]; then
echo "ERROR: This script must be run as root (use sudo)"
exit 1
fi
echo "=== Uninstalling adguard-register ==="
# Stop and disable service (ignore errors if not installed)
systemctl stop adguard-register.service 2>/dev/null || true
systemctl disable adguard-register.service 2>/dev/null || true
rm -f "${SERVICE_PATH}"
systemctl daemon-reload
echo "→ Removed systemd service"
# Remove script
rm -f "${INSTALL_PATH}"
echo "→ Removed ${INSTALL_PATH}"
echo ""
echo "=== Uninstall complete ==="
echo "NOTE: Existing DNS rewrites in AdGuard Home were NOT removed."
echo "To clean up entries, use the AdGuard Home web UI or API directly."