mirror of
https://codeberg.org/shm0rt/ddns-pdns-updater.git
synced 2025-07-28 21:19:08 +02:00
populated repository
This commit is contained in:
parent
5a38cf342a
commit
def723b223
5 changed files with 420 additions and 2 deletions
261
README.md
261
README.md
|
@ -1,3 +1,260 @@
|
||||||
# ddns-powerdns-updater
|
# PowerDNS DDNS Updater
|
||||||
|
|
||||||
Hybrid DDNS service for PowerDNS setups. Synchronizes external Infomaniak updates with PowerDNS internal A record management via systemd.
|
Hybrid DDNS service for PowerDNS environments. Automatically synchronizes external Infomaniak DNS updates with PowerDNS internal A record management via systemd service and timer.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Dual DNS Management**: Updates both external (Infomaniak) and internal (PowerDNS) DNS records
|
||||||
|
- **Automatic IP Detection**: Fetches current external IP using `ifconfig.me`
|
||||||
|
- **Smart Updates**: Only updates records when IP address changes
|
||||||
|
- **Comprehensive Logging**: Tracks IP changes and DNS updates with timestamps
|
||||||
|
- **Systemd Integration**: Runs automatically as background service with configurable intervals
|
||||||
|
- **Multi-Domain Support**: Manages multiple internal PowerDNS domains from single configuration
|
||||||
|
- **Error Handling**: Validates IP addresses and handles API failures gracefully
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
The service consists of four main components:
|
||||||
|
|
||||||
|
1. **Main Script** (`pdns-ddns`) - Core logic for IP detection and DNS updates
|
||||||
|
2. **Configuration** (`ddns.conf`) - API credentials and domain settings
|
||||||
|
3. **Systemd Service** (`pdns-ddns.service`) - Service definition for one-shot execution
|
||||||
|
4. **Systemd Timer** (`pdns-ddns.timer`) - Scheduler running every minute
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
### 1. Clone and Setup Files
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create directories
|
||||||
|
sudo mkdir -p /etc/powerdns
|
||||||
|
|
||||||
|
# Copy files to system locations
|
||||||
|
sudo cp pdns-ddns /usr/local/bin/
|
||||||
|
sudo cp ddns.conf /etc/powerdns/
|
||||||
|
sudo cp pdns-ddns.service /etc/systemd/system/
|
||||||
|
sudo cp pdns-ddns.timer /etc/systemd/system/
|
||||||
|
|
||||||
|
# Make script executable
|
||||||
|
sudo chmod +x /usr/local/bin/pdns-ddns
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Configure Settings
|
||||||
|
|
||||||
|
Edit the configuration file:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo nano /etc/powerdns/ddns.conf
|
||||||
|
```
|
||||||
|
|
||||||
|
Update the following variables:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Infomaniak API credentials
|
||||||
|
INFOMANIAK_TOKEN="your-infomaniak-api-token"
|
||||||
|
INFOMANIAK_DOMAIN="your-external-domain.com"
|
||||||
|
|
||||||
|
# Internal PowerDNS domains (space-separated)
|
||||||
|
POWERDNS_DOMAINS="internal1.com internal2.xyz"
|
||||||
|
|
||||||
|
# DNS record TTL in seconds
|
||||||
|
TTL=300
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Enable and Start Service
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Reload systemd configuration
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
|
||||||
|
# Enable and start the timer
|
||||||
|
sudo systemctl enable pdns-ddns.timer
|
||||||
|
sudo systemctl start pdns-ddns.timer
|
||||||
|
|
||||||
|
# Check status
|
||||||
|
sudo systemctl status pdns-ddns.timer
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
### Infomaniak API Setup
|
||||||
|
|
||||||
|
1. Log into your Infomaniak account
|
||||||
|
2. Navigate to API settings
|
||||||
|
3. Generate an API token with DNS management permissions
|
||||||
|
4. Add the token to `INFOMANIAK_TOKEN` in the configuration
|
||||||
|
|
||||||
|
### PowerDNS Requirements
|
||||||
|
|
||||||
|
The script requires PowerDNS with the following tools installed:
|
||||||
|
- `pdnsutil` - PowerDNS utility for zone management
|
||||||
|
- Proper permissions for the script to modify DNS zones
|
||||||
|
|
||||||
|
### Supported Domains
|
||||||
|
|
||||||
|
- **External**: Single Infomaniak-managed domain
|
||||||
|
- **Internal**: Multiple PowerDNS zones (space-separated in config)
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Manual Execution
|
||||||
|
|
||||||
|
Run the script manually for testing:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo /usr/local/bin/pdns-ddns
|
||||||
|
```
|
||||||
|
|
||||||
|
### Service Management
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check timer status
|
||||||
|
sudo systemctl status pdns-ddns.timer
|
||||||
|
|
||||||
|
# View recent service runs
|
||||||
|
sudo systemctl status pdns-ddns.service
|
||||||
|
|
||||||
|
# Check logs
|
||||||
|
sudo journalctl -u pdns-ddns.service -f
|
||||||
|
|
||||||
|
# Stop/start the timer
|
||||||
|
sudo systemctl stop pdns-ddns.timer
|
||||||
|
sudo systemctl start pdns-ddns.timer
|
||||||
|
```
|
||||||
|
|
||||||
|
### Changing Update Interval
|
||||||
|
|
||||||
|
Edit the timer configuration:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo nano /etc/systemd/system/pdns-ddns.timer
|
||||||
|
```
|
||||||
|
|
||||||
|
Modify the `OnCalendar` setting:
|
||||||
|
- Every minute: `OnCalendar=*:*`
|
||||||
|
- Every 5 minutes: `OnCalendar=*:*/5`
|
||||||
|
- Every hour: `OnCalendar=hourly`
|
||||||
|
|
||||||
|
Then reload and restart:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
sudo systemctl restart pdns-ddns.timer
|
||||||
|
```
|
||||||
|
|
||||||
|
## Logging
|
||||||
|
|
||||||
|
### IP Change History
|
||||||
|
|
||||||
|
The service maintains an IP change log at `/var/log/ddns-ip-history.log`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# View recent IP changes
|
||||||
|
sudo tail -f /var/log/ddns-ip-history.log
|
||||||
|
|
||||||
|
# View all changes
|
||||||
|
sudo cat /var/log/ddns-ip-history.log
|
||||||
|
```
|
||||||
|
|
||||||
|
### System Logs
|
||||||
|
|
||||||
|
View detailed execution logs:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Follow live logs
|
||||||
|
sudo journalctl -u pdns-ddns.service -f
|
||||||
|
|
||||||
|
# View recent logs
|
||||||
|
sudo journalctl -u pdns-ddns.service --since "1 hour ago"
|
||||||
|
|
||||||
|
# View logs with timestamps
|
||||||
|
sudo journalctl -u pdns-ddns.service -o short-iso
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Common Issues
|
||||||
|
|
||||||
|
**Script fails with permission errors:**
|
||||||
|
```bash
|
||||||
|
# Ensure proper ownership and permissions
|
||||||
|
sudo chown root:root /usr/local/bin/pdns-ddns
|
||||||
|
sudo chmod 755 /usr/local/bin/pdns-ddns
|
||||||
|
```
|
||||||
|
|
||||||
|
**Infomaniak API errors:**
|
||||||
|
- Verify API token has DNS management permissions
|
||||||
|
- Check domain name spelling in configuration
|
||||||
|
- Ensure API token hasn't expired
|
||||||
|
|
||||||
|
**PowerDNS errors:**
|
||||||
|
- Verify `pdnsutil` is installed and accessible
|
||||||
|
- Check PowerDNS service is running: `sudo systemctl status pdns`
|
||||||
|
- Ensure script has permissions to modify DNS zones
|
||||||
|
|
||||||
|
**IP detection fails:**
|
||||||
|
- Check internet connectivity
|
||||||
|
- Verify `curl` is installed
|
||||||
|
- Test manually: `curl -s ifconfig.me`
|
||||||
|
|
||||||
|
### Debug Mode
|
||||||
|
|
||||||
|
Run the script manually to see detailed output:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo bash -x /usr/local/bin/pdns-ddns
|
||||||
|
```
|
||||||
|
|
||||||
|
### Logs Analysis
|
||||||
|
|
||||||
|
Check for specific error patterns:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# API errors
|
||||||
|
sudo journalctl -u pdns-ddns.service | grep -i error
|
||||||
|
|
||||||
|
# IP changes
|
||||||
|
sudo journalctl -u pdns-ddns.service | grep "IP changed"
|
||||||
|
|
||||||
|
# PowerDNS operations
|
||||||
|
sudo journalctl -u pdns-ddns.service | grep "PowerDNS"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Security Considerations
|
||||||
|
|
||||||
|
- Store API tokens securely in the configuration file
|
||||||
|
- Limit file permissions: `sudo chmod 600 /etc/powerdns/ddns.conf`
|
||||||
|
- Regularly rotate Infomaniak API tokens
|
||||||
|
- Monitor logs for unauthorized access attempts
|
||||||
|
- Consider firewall rules for PowerDNS management access
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
### Required Packages
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Ubuntu/Debian
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install curl jq
|
||||||
|
|
||||||
|
# CentOS/RHEL
|
||||||
|
sudo yum install curl jq
|
||||||
|
|
||||||
|
# PowerDNS (if not already installed)
|
||||||
|
sudo apt install pdns-server pdns-tools
|
||||||
|
```
|
||||||
|
|
||||||
|
### System Requirements
|
||||||
|
|
||||||
|
- Linux system with systemd
|
||||||
|
- PowerDNS server with `pdnsutil`
|
||||||
|
- Internet connectivity for IP detection and Infomaniak API
|
||||||
|
- Root or sudo access for DNS modifications
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This project is open source. Feel free to modify and distribute according to your needs.
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Contributions are welcome! Please ensure any changes maintain compatibility with existing PowerDNS and Infomaniak setups.
|
||||||
|
|
10
ddns.conf
Normal file
10
ddns.conf
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
|
||||||
|
# Infomaniak API
|
||||||
|
INFOMANIAK_TOKEN="YOUR-API-KEY"
|
||||||
|
INFOMANIAK_DOMAIN="YOUR-DOMAIN"
|
||||||
|
|
||||||
|
# PowerDNS Domains (space separated)
|
||||||
|
POWERDNS_DOMAINS="INTERNAL-DOMAIN1.com INTERNAL-DOMAIN2.com"
|
||||||
|
|
||||||
|
# TTL for DNS records
|
||||||
|
TTL=300
|
131
pdns-ddns
Executable file
131
pdns-ddns
Executable file
|
@ -0,0 +1,131 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# pdns-ddns - Updates both Infomaniak and PowerDNS with external IP
|
||||||
|
# Speichern als: /usr/local/bin/pdns-ddns
|
||||||
|
|
||||||
|
# Load config
|
||||||
|
source /etc/powerdns/ddns.conf
|
||||||
|
|
||||||
|
# IP History file
|
||||||
|
IP_HISTORY_FILE="/var/log/ddns-ip-history.log"
|
||||||
|
|
||||||
|
# Get external IP
|
||||||
|
EXTERNAL_IP=$(curl -s ifconfig.me)
|
||||||
|
|
||||||
|
if [[ ! $EXTERNAL_IP =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
|
||||||
|
echo "ERROR: Invalid IP address: $EXTERNAL_IP"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$(date): External IP: $EXTERNAL_IP"
|
||||||
|
|
||||||
|
# Read last known IP
|
||||||
|
LAST_IP=""
|
||||||
|
if [[ -f "$IP_HISTORY_FILE" ]]; then
|
||||||
|
LAST_IP=$(grep "IP changed from" "$IP_HISTORY_FILE" | tail -1 | sed 's/.*to \([0-9.]*\)$/\1/')
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Log IP change if different
|
||||||
|
if [[ -n "$LAST_IP" && "$LAST_IP" != "$EXTERNAL_IP" ]]; then
|
||||||
|
echo "$(date): IP changed from $LAST_IP to $EXTERNAL_IP" | tee -a "$IP_HISTORY_FILE"
|
||||||
|
echo "$(date): Previous IPs in last 10 changes:"
|
||||||
|
grep "IP changed from" "$IP_HISTORY_FILE" | tail -10 | sed 's/.*from \([0-9.]*\) to \([0-9.]*\)$/Old: \1 -> New: \2/' || echo "No previous changes found"
|
||||||
|
elif [[ -z "$LAST_IP" ]]; then
|
||||||
|
echo "$(date): IP unchanged: $EXTERNAL_IP (first run or no history)"
|
||||||
|
else
|
||||||
|
echo "$(date): IP unchanged: $EXTERNAL_IP"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check and update Infomaniak (external)
|
||||||
|
echo "$(date): Checking Infomaniak A record for $INFOMANIAK_DOMAIN..."
|
||||||
|
CURRENT_RECORD=$(curl -s -H "Authorization: Bearer $INFOMANIAK_TOKEN" \
|
||||||
|
"https://api.infomaniak.com/2/zones/$INFOMANIAK_DOMAIN/records" | \
|
||||||
|
jq -r '.data[] | select(.type=="A" and .source==".") | .target' | head -1)
|
||||||
|
|
||||||
|
if [[ "$CURRENT_RECORD" == "$EXTERNAL_IP" ]]; then
|
||||||
|
echo "$(date): Infomaniak A record already correct: $EXTERNAL_IP"
|
||||||
|
else
|
||||||
|
if [[ -n "$CURRENT_RECORD" ]]; then
|
||||||
|
echo "$(date): Infomaniak A record needs update: $CURRENT_RECORD -> $EXTERNAL_IP"
|
||||||
|
echo "$(date): Infomaniak change: $CURRENT_RECORD -> $EXTERNAL_IP" >> "$IP_HISTORY_FILE"
|
||||||
|
else
|
||||||
|
echo "$(date): Infomaniak A record does not exist, creating with IP: $EXTERNAL_IP"
|
||||||
|
echo "$(date): Infomaniak created: -> $EXTERNAL_IP" >> "$IP_HISTORY_FILE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
RECORD_ID=$(curl -s -H "Authorization: Bearer $INFOMANIAK_TOKEN" \
|
||||||
|
"https://api.infomaniak.com/2/zones/$INFOMANIAK_DOMAIN/records" | \
|
||||||
|
jq -r '.data[] | select(.type=="A" and .source==".") | .id' | head -1)
|
||||||
|
|
||||||
|
if [[ -z "$RECORD_ID" ]]; then
|
||||||
|
echo "$(date): Creating new Infomaniak A record..."
|
||||||
|
if curl -s -X POST "https://api.infomaniak.com/2/zones/$INFOMANIAK_DOMAIN/records" \
|
||||||
|
-H "Authorization: Bearer $INFOMANIAK_TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "{\"type\": \"A\", \"source\": \".\", \"target\": \"$EXTERNAL_IP\", \"ttl\": $TTL}" > /dev/null; then
|
||||||
|
echo "$(date): SUCCESS: Infomaniak A record created"
|
||||||
|
else
|
||||||
|
echo "$(date): ERROR: Failed to create Infomaniak A record"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "$(date): Updating Infomaniak A record..."
|
||||||
|
if curl -s -X PUT "https://api.infomaniak.com/2/zones/$INFOMANIAK_DOMAIN/records/$RECORD_ID" \
|
||||||
|
-H "Authorization: Bearer $INFOMANIAK_TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "{\"target\": \"$EXTERNAL_IP\", \"ttl\": $TTL}" > /dev/null; then
|
||||||
|
echo "$(date): SUCCESS: Infomaniak A record updated"
|
||||||
|
else
|
||||||
|
echo "$(date): ERROR: Failed to update Infomaniak A record"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check and update PowerDNS (internal)
|
||||||
|
echo "$(date): Checking PowerDNS A records..."
|
||||||
|
for domain in $POWERDNS_DOMAINS; do
|
||||||
|
echo "$(date): Checking zone $domain..."
|
||||||
|
|
||||||
|
# Check if zone exists
|
||||||
|
if ! pdnsutil list-zone "$domain" >/dev/null 2>&1; then
|
||||||
|
echo "$(date): Zone $domain does not exist, creating..."
|
||||||
|
echo "$(date): PowerDNS $domain created: -> $EXTERNAL_IP" >> "$IP_HISTORY_FILE"
|
||||||
|
pdnsutil create-zone "$domain"
|
||||||
|
NEEDS_UPDATE=true
|
||||||
|
else
|
||||||
|
# Check current A record - match actual PowerDNS output format
|
||||||
|
CURRENT_IP=$(pdnsutil list-zone "$domain" 2>/dev/null | grep -E "^${domain}\s+[0-9]+\s+IN\s+A\s+" | awk '{print $5}' | head -1)
|
||||||
|
|
||||||
|
if [[ "$CURRENT_IP" == "$EXTERNAL_IP" ]]; then
|
||||||
|
echo "$(date): PowerDNS A record for $domain already correct: $EXTERNAL_IP"
|
||||||
|
NEEDS_UPDATE=false
|
||||||
|
else
|
||||||
|
if [[ -n "$CURRENT_IP" ]]; then
|
||||||
|
echo "$(date): PowerDNS A record for $domain needs update: $CURRENT_IP -> $EXTERNAL_IP"
|
||||||
|
echo "$(date): PowerDNS $domain change: $CURRENT_IP -> $EXTERNAL_IP" >> "$IP_HISTORY_FILE"
|
||||||
|
else
|
||||||
|
echo "$(date): PowerDNS A record for $domain does not exist, creating with IP: $EXTERNAL_IP"
|
||||||
|
echo "$(date): PowerDNS $domain created: -> $EXTERNAL_IP" >> "$IP_HISTORY_FILE"
|
||||||
|
fi
|
||||||
|
NEEDS_UPDATE=true
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Update if needed
|
||||||
|
if [[ "$NEEDS_UPDATE" == "true" ]]; then
|
||||||
|
echo "$(date): Updating PowerDNS A record for $domain..."
|
||||||
|
if pdnsutil replace-rrset "$domain" @ A "$TTL" "$EXTERNAL_IP" 2>/dev/null; then
|
||||||
|
echo "$(date): SUCCESS: PowerDNS A record updated for $domain"
|
||||||
|
pdnsutil rectify-zone "$domain" 2>/dev/null
|
||||||
|
pdnsutil increase-serial "$domain" 2>/dev/null
|
||||||
|
else
|
||||||
|
echo "$(date): ERROR: Failed to update PowerDNS A record for $domain"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "$(date): DDNS check/update completed"
|
||||||
|
|
||||||
|
# Show recent IP history summary
|
||||||
|
if [[ -f "$IP_HISTORY_FILE" ]]; then
|
||||||
|
echo "$(date): Recent changes (last 5):"
|
||||||
|
tail -5 "$IP_HISTORY_FILE" | grep -E "(change:|created:)" || echo "No recent changes"
|
||||||
|
fi
|
10
pdns-ddns.service
Normal file
10
pdns-ddns.service
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
[Unit]
|
||||||
|
Description=PowerDNS DDNS Updater
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
ExecStart=/usr/local/bin/pdns-ddns
|
||||||
|
User=root
|
||||||
|
StandardOutput=journal
|
||||||
|
StandardError=journal
|
10
pdns-ddns.timer
Normal file
10
pdns-ddns.timer
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
[Unit]
|
||||||
|
Description=Run PowerDNS DDNS update every minute
|
||||||
|
Requires=pdns-ddns.service
|
||||||
|
|
||||||
|
[Timer]
|
||||||
|
OnCalendar=*:*
|
||||||
|
Persistent=true
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=timers.target
|
Loading…
Add table
Add a link
Reference in a new issue