first commit
This commit is contained in:
commit
51ca26f5ce
1 changed files with 327 additions and 0 deletions
327
README.md
Normal file
327
README.md
Normal file
|
|
@ -0,0 +1,327 @@
|
||||||
|
# Porkbun Dynamic DNS Update Script
|
||||||
|
|
||||||
|
A bash script for automatically updating DNS A records on Porkbun with your current public IP address. Supports multi-machine failover configurations where multiple servers can independently update their own A records for the same domain.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- ✅ **Automatic IP Detection** - Uses Porkbun's API and multiple fallback methods
|
||||||
|
- ✅ **Multi-Machine Failover** - Multiple machines can maintain A records for the same domain
|
||||||
|
- ✅ **Machine Identification** - Uses notes field to track which machine owns each record
|
||||||
|
- ✅ **Secure** - API credentials stored in `.env` file (excluded from git)
|
||||||
|
- ✅ **Error Handling** - Comprehensive validation and error messages
|
||||||
|
- ✅ **Wildcard Support** - Updates both root and wildcard DNS records
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- `bash` shell
|
||||||
|
- `curl` - for API calls
|
||||||
|
- `jq` - for JSON parsing
|
||||||
|
- Porkbun API access enabled for your domain
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
1. Clone or download this script to your machine
|
||||||
|
|
||||||
|
2. Make the script executable:
|
||||||
|
```bash
|
||||||
|
chmod +x updateDNS.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Copy the `.env` file and configure it (see Configuration below)
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Edit the `.env` file with your settings:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Required: Your Porkbun API credentials
|
||||||
|
API_KEY=pk1_your_api_key_here
|
||||||
|
SECRET_KEY=sk1_your_secret_key_here
|
||||||
|
|
||||||
|
# Required: The domain to update
|
||||||
|
DOMAIN=example.com
|
||||||
|
|
||||||
|
# Required for multi-machine setup: Unique identifier for this machine
|
||||||
|
# Each machine should have a different MACHINE_ID
|
||||||
|
MACHINE_ID=server1
|
||||||
|
```
|
||||||
|
|
||||||
|
### Getting Porkbun API Credentials
|
||||||
|
|
||||||
|
1. Log in to your [Porkbun account](https://porkbun.com/account/api)
|
||||||
|
2. Navigate to **Account > API Access**
|
||||||
|
3. Generate API keys for your domain
|
||||||
|
4. **Important**: Enable API access for your specific domain in the domain settings
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Single Machine (Simple Dynamic DNS)
|
||||||
|
|
||||||
|
Basic configuration on one machine:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# .env
|
||||||
|
API_KEY=pk1_xxxxx
|
||||||
|
SECRET_KEY=sk1_xxxxx
|
||||||
|
DOMAIN=example.com
|
||||||
|
MACHINE_ID=home-server
|
||||||
|
```
|
||||||
|
|
||||||
|
Run the script:
|
||||||
|
```bash
|
||||||
|
./updateDNS.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
This will update:
|
||||||
|
- `example.com` → Your current public IP
|
||||||
|
- `*.example.com` → Your current public IP
|
||||||
|
|
||||||
|
### Multi-Machine Failover Setup
|
||||||
|
|
||||||
|
Configure multiple machines with **the same domain** but **different MACHINE_IDs**:
|
||||||
|
|
||||||
|
**Server 1:**
|
||||||
|
```bash
|
||||||
|
# .env
|
||||||
|
API_KEY=pk1_xxxxx
|
||||||
|
SECRET_KEY=sk1_xxxxx
|
||||||
|
DOMAIN=example.com
|
||||||
|
MACHINE_ID=server1
|
||||||
|
```
|
||||||
|
|
||||||
|
**Server 2:**
|
||||||
|
```bash
|
||||||
|
# .env
|
||||||
|
API_KEY=pk1_xxxxx
|
||||||
|
SECRET_KEY=sk1_xxxxx
|
||||||
|
DOMAIN=example.com
|
||||||
|
MACHINE_ID=server2
|
||||||
|
```
|
||||||
|
|
||||||
|
**Server 3:**
|
||||||
|
```bash
|
||||||
|
# .env
|
||||||
|
API_KEY=pk1_xxxxx
|
||||||
|
SECRET_KEY=sk1_xxxxx
|
||||||
|
DOMAIN=example.com
|
||||||
|
MACHINE_ID=backup-server
|
||||||
|
```
|
||||||
|
|
||||||
|
Each machine runs the same script independently. The result will be:
|
||||||
|
|
||||||
|
```
|
||||||
|
example.com. 300 IN A 203.0.113.10 (server1's IP)
|
||||||
|
example.com. 300 IN A 198.51.100.20 (server2's IP)
|
||||||
|
example.com. 300 IN A 192.0.2.30 (backup-server's IP)
|
||||||
|
```
|
||||||
|
|
||||||
|
DNS clients will receive all three IPs, providing:
|
||||||
|
- **Automatic failover** - if one server is down, clients use others
|
||||||
|
- **Load distribution** - traffic is distributed across servers
|
||||||
|
- **Independent updates** - each machine manages only its own record
|
||||||
|
|
||||||
|
### Managed records and notes tagging
|
||||||
|
|
||||||
|
The script only manages records that it created itself, identified via the DNS **notes** field.
|
||||||
|
|
||||||
|
- Internally it uses a managed-notes prefix:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
NOTES_PREFIX="ddns:"
|
||||||
|
```
|
||||||
|
|
||||||
|
- For each machine, the script sets the notes value to:
|
||||||
|
|
||||||
|
```text
|
||||||
|
ddns:<MACHINE_ID>
|
||||||
|
```
|
||||||
|
|
||||||
|
- When cleaning up or checking for duplicates, it **only** looks at records where:
|
||||||
|
|
||||||
|
- `name` matches the FQDN being updated (e.g. `example.com` or `*.example.com`), and
|
||||||
|
- `type` is `A`, `ALIAS`, or `CNAME`, and
|
||||||
|
- `notes == "ddns:<MACHINE_ID>"`
|
||||||
|
|
||||||
|
This means:
|
||||||
|
|
||||||
|
- Manually managed records without this prefix are left untouched.
|
||||||
|
- Multiple machines (different `MACHINE_ID`s) can safely coexist on the same domain.
|
||||||
|
- If you ever want to manually take control of a record that was created by this script,
|
||||||
|
you can remove or change the `ddns:<MACHINE_ID>` note and the script will stop managing it.
|
||||||
|
|
||||||
|
### Automated Updates with Cron
|
||||||
|
|
||||||
|
To automatically update DNS every 5 minutes:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
crontab -e
|
||||||
|
```
|
||||||
|
|
||||||
|
Add this line (adjust path as needed):
|
||||||
|
```cron
|
||||||
|
*/5 * * * * /home/user/porkbun/updateDNS.sh >> /var/log/porkbun-ddns.log 2>&1
|
||||||
|
```
|
||||||
|
|
||||||
|
Or every hour:
|
||||||
|
```cron
|
||||||
|
0 * * * * /home/user/porkbun/updateDNS.sh >> /var/log/porkbun-ddns.log 2>&1
|
||||||
|
```
|
||||||
|
|
||||||
|
## Dry-run mode
|
||||||
|
|
||||||
|
You can run the script in **dry-run** mode to see what it would do without actually
|
||||||
|
creating or deleting any DNS records.
|
||||||
|
|
||||||
|
Set the `DRY_RUN` environment variable to `1` when invoking the script:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
DRY_RUN=1 ./updateDNS.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
In dry-run mode:
|
||||||
|
|
||||||
|
- The script still loads configuration and detects your public IP.
|
||||||
|
- It still retrieves and displays existing DNS records.
|
||||||
|
- It logs which records **would** be deleted and what **would** be created.
|
||||||
|
- It does **not** call Porkbun's `dns/delete` or `dns/create` endpoints.
|
||||||
|
|
||||||
|
This is useful when first configuring the script or making changes to your DNS setup,
|
||||||
|
so you can verify behavior before applying it.
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
1. **Load Configuration** - Reads API credentials and settings from `.env`
|
||||||
|
2. **Detect Public IP** - Uses Porkbun's `/ping` endpoint or fallback services (api.ipify.org, icanhazip.com)
|
||||||
|
3. **Retrieve DNS Records** - Gets all existing DNS records for the domain and displays them
|
||||||
|
4. **Check for Duplicates** - Detects if multiple records exist for this machine ID
|
||||||
|
5. **Skip if Unchanged** - If IP matches and exactly one record exists, skips update
|
||||||
|
6. **Clean Old Records** - Deletes A/ALIAS/CNAME records with no machine ID (migration cleanup)
|
||||||
|
7. **Delete Own Records** - Removes this machine's previous A records (by matching notes field)
|
||||||
|
8. **Verify Cleanup** - Ensures all old records were deleted before creating new ones
|
||||||
|
9. **Create New Record** - Adds fresh A record with current IP and machine ID in notes
|
||||||
|
10. **Verify Creation** - Confirms exactly one record exists for this machine
|
||||||
|
|
||||||
|
> Note: The script now only deletes and verifies records that it manages
|
||||||
|
> (those whose notes are exactly `ddns:<MACHINE_ID>`). Other records in the
|
||||||
|
> same zone are left untouched.
|
||||||
|
|
||||||
|
## Security Best Practices
|
||||||
|
|
||||||
|
### Protect Your API Keys
|
||||||
|
|
||||||
|
1. **Never commit `.env` to version control**:
|
||||||
|
```bash
|
||||||
|
echo "*.env" >> .gitignore
|
||||||
|
echo ".env" >> .gitignore
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Rotate your API keys regularly** in the Porkbun dashboard
|
||||||
|
|
||||||
|
3. **Use restrictive file permissions**:
|
||||||
|
```bash
|
||||||
|
chmod 600 .env
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Use different API keys** for development/production if possible
|
||||||
|
|
||||||
|
### If You Accidentally Exposed Keys
|
||||||
|
|
||||||
|
1. **Immediately revoke** the API keys in your Porkbun account
|
||||||
|
2. **Generate new keys** and update your `.env` file
|
||||||
|
3. **Remove secrets from git history** if they were committed:
|
||||||
|
```bash
|
||||||
|
git filter-branch --force --index-filter \
|
||||||
|
"git rm --cached --ignore-unmatch .env" \
|
||||||
|
--prune-empty --tag-name-filter cat -- --all
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Error: "dig: command not found"
|
||||||
|
The script now uses `curl` instead of `dig`, so this shouldn't happen. If you see this, update to the latest version.
|
||||||
|
|
||||||
|
### Error: "API access not enabled"
|
||||||
|
1. Log in to Porkbun
|
||||||
|
2. Go to your domain settings
|
||||||
|
3. Enable API access for the specific domain
|
||||||
|
4. Wait a few minutes for changes to propagate
|
||||||
|
|
||||||
|
### Error: "Failed to create DNS record"
|
||||||
|
- Check that API credentials are correct
|
||||||
|
- Verify API access is enabled for your domain
|
||||||
|
- Ensure MACHINE_ID doesn't contain special characters
|
||||||
|
- Check that you're not exceeding Porkbun's rate limits
|
||||||
|
|
||||||
|
### Multiple machines overwriting each other
|
||||||
|
- Make sure each machine has a **unique MACHINE_ID**
|
||||||
|
- Verify each machine is using the latest version of the script
|
||||||
|
- Check DNS records in Porkbun dashboard to see notes field (look for `ddns:<MACHINE_ID>`)
|
||||||
|
|
||||||
|
### IP not updating
|
||||||
|
- Check script is running (add to cron for automatic updates)
|
||||||
|
- Verify your public IP actually changed
|
||||||
|
- Check Porkbun API status
|
||||||
|
- Review logs for error messages
|
||||||
|
|
||||||
|
## API Rate Limits
|
||||||
|
|
||||||
|
Porkbun's API has rate limits. For dynamic DNS:
|
||||||
|
- **Recommended**: Run every 5-15 minutes
|
||||||
|
- **Don't**: Run more than once per minute
|
||||||
|
- The script only makes API calls when it runs, not continuously
|
||||||
|
|
||||||
|
## Example Output
|
||||||
|
|
||||||
|
Example output with the new structured logging format:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Mon Nov 24 12:04:38 PM EST 2025 [INFO] Using environment file: /home/user/porkbun/.env
|
||||||
|
IP source: Porkbun API (/ping)
|
||||||
|
Mon Nov 24 12:04:38 PM EST 2025 [INFO] Current Public IP: 45.73.134.145
|
||||||
|
Mon Nov 24 12:04:38 PM EST 2025 [INFO] Machine ID: KACPER_NIX
|
||||||
|
Mon Nov 24 12:04:38 PM EST 2025 [INFO] Domain: aksal.cloud
|
||||||
|
|
||||||
|
Mon Nov 24 12:04:38 PM EST 2025 [INFO] Processing DNS record for: aksal.cloud (IP: 45.73.134.145)
|
||||||
|
Mon Nov 24 12:04:38 PM EST 2025 [INFO] Machine ID: KACPER_NIX
|
||||||
|
Mon Nov 24 12:04:39 PM EST 2025 [INFO] Existing records for FQDN 'aksal.cloud':
|
||||||
|
Type: A, Name: aksal.cloud, Content: 45.73.134.145, Notes: ddns:KACPER_NIX, Managed: true, ID: 508029248
|
||||||
|
Type: NS, Name: aksal.cloud, Content: curitiba.porkbun.com, Notes: none, Managed: false, ID: 506591857
|
||||||
|
Type: NS, Name: aksal.cloud, Content: fortaleza.porkbun.com, Notes: none, Managed: false, ID: 506591856
|
||||||
|
Type: NS, Name: aksal.cloud, Content: maceio.porkbun.com, Notes: none, Managed: false, ID: 506591854
|
||||||
|
Type: NS, Name: aksal.cloud, Content: salvador.porkbun.com, Notes: none, Managed: false, ID: 506591855
|
||||||
|
Mon Nov 24 12:04:39 PM EST 2025 [INFO] Record already exists with correct IP (45.73.134.145). No update needed.
|
||||||
|
|
||||||
|
Mon Nov 24 12:04:39 PM EST 2025 [INFO] Processing DNS record for: *.aksal.cloud (IP: 45.73.134.145)
|
||||||
|
Mon Nov 24 12:04:39 PM EST 2025 [INFO] Machine ID: KACPER_NIX
|
||||||
|
Mon Nov 24 12:04:40 PM EST 2025 [INFO] Existing records for FQDN '*.aksal.cloud':
|
||||||
|
Type: A, Name: *.aksal.cloud, Content: 45.73.134.145, Notes: ddns:KACPER_NIX, Managed: true, ID: 508029259
|
||||||
|
Mon Nov 24 12:04:40 PM EST 2025 [INFO] Record already exists with correct IP (45.73.134.145). No update needed.
|
||||||
|
|
||||||
|
Mon Nov 24 12:04:40 PM EST 2025 [INFO] DNS update completed successfully
|
||||||
|
```
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|
||||||
|
- `updateDNS.sh` - Main script
|
||||||
|
- `.env` - Configuration file (contains secrets, **DO NOT COMMIT**)
|
||||||
|
- `README.md` - This file
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This script is provided as-is for use with Porkbun DNS services.
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
For issues with:
|
||||||
|
- **This script**: Open an issue or review the code
|
||||||
|
- **Porkbun API**: Contact [Porkbun Support](https://porkbun.com/support)
|
||||||
|
- **DNS concepts**: See [Porkbun DNS Documentation](https://kb.porkbun.com/)
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Improvements welcome! Consider adding:
|
||||||
|
- IPv6 support (AAAA records)
|
||||||
|
- Email notifications on IP change
|
||||||
|
- Systemd service file
|
||||||
|
- Docker container version
|
||||||
|
- Health check endpoint
|
||||||
Loading…
Add table
Add a link
Reference in a new issue