Initial Commit

This commit is contained in:
2025-10-03 01:01:07 -04:00
commit 61418c565f
7 changed files with 455 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
main
.env
cloudflare-dns-updater
cloudflare_updater_util.sh

191
README.md Normal file
View File

@@ -0,0 +1,191 @@
# Cloudflare DNS Updater
A simple Go application that automatically updates Cloudflare DNS records with your current public IP address. Perfect for dynamic IP scenarios where you need to keep your domain pointing to your current IP.
## Features
- 🔄 Automatically detects your current public IP address
- 📝 Updates existing DNS A records or creates new ones
- ⚡ Supports multiple DNS records in a single run
- 🛡️ Uses Cloudflare API with secure authentication
- 🔧 Configurable via environment variables
- 🚀 Lightweight and fast
## Prerequisites
- Go 1.24.0 or later
- A Cloudflare account with API access
- A domain managed by Cloudflare
## Installation
1. Clone or download this repository
2. Build the application:
```bash
go build -o cloudflare-dns-updater main.go config.go dns.go
```
## Configuration
### Environment Variables
The application uses the following environment variables:
| Variable | Description | Required |
|----------|-------------|----------|
| `CLOUDFLARE_EMAIL` | Your Cloudflare account email | Yes |
| `CLOUDFLARE_AUTH_KEY` | Your Cloudflare API token | Yes |
| `CLOUDFLARE_ZONE_ID` | The Zone ID of your domain | Yes |
| `CLOUDFLARE_RECORD_NAMES` | Comma-separated list of DNS record names to update | Yes |
| `CLOUDFLARE_FORCE_IP` | Force a specific IP address (optional) | No |
### Getting Your Cloudflare Credentials
1. **Email**: Your Cloudflare account email address
2. **Auth Key**:
- Go to [Cloudflare Dashboard](https://dash.cloudflare.com/profile/api-tokens)
- Click "Create Token"
- Use "Custom token" template
- Permissions: `Zone:Zone:Read`, `Zone:DNS:Edit`
- Zone Resources: Include specific zone or all zones
3. **Zone ID**:
- Go to your domain's overview page in Cloudflare
- Find "Zone ID" in the right sidebar
4. **Record Names**:
- List of subdomains you want to update (e.g., `subdomain.example.com,www.example.com`)
## Quick Start
1. Copy the example configuration file:
```bash
cp cloudflare_updater_util_example.sh cloudflare_updater_util.sh
```
2. Edit `cloudflare_updater_util.sh` with your actual credentials:
```bash
nano cloudflare_updater_util.sh
```
3. Make the script executable:
```bash
chmod +x cloudflare_updater_util.sh
```
4. Run the updater:
```bash
./cloudflare_updater_util.sh
```
## Automated Updates with Cron
To keep your DNS records updated automatically, set up a cron job:
1. Open your crontab:
```bash
crontab -e
```
2. Add a line to run the updater every 5 minutes:
```bash
*/5 * * * * /path/to/your/cloudflare_updater_util.sh
```
Or every 10 minutes:
```bash
*/10 * * * * /path/to/your/cloudflare_updater_util.sh
```
3. Save and exit. The cron job will now run automatically.
### Example Cron Schedules
- Every 5 minutes: `*/5 * * * *`
- Every 10 minutes: `*/10 * * * *`
- Every hour: `0 * * * *`
- Every 6 hours: `0 */6 * * *`
## Usage Examples
### Basic Usage
```bash
export CLOUDFLARE_EMAIL="your-email@example.com"
export CLOUDFLARE_AUTH_KEY="your-api-token"
export CLOUDFLARE_ZONE_ID="your-zone-id"
export CLOUDFLARE_RECORD_NAMES="home.example.com,server.example.com"
./cloudflare-dns-updater
```
### With Forced IP
```bash
export CLOUDFLARE_EMAIL="your-email@example.com"
export CLOUDFLARE_AUTH_KEY="your-api-token"
export CLOUDFLARE_ZONE_ID="your-zone-id"
export CLOUDFLARE_RECORD_NAMES="home.example.com"
export CLOUDFLARE_FORCE_IP="192.168.1.100"
./cloudflare-dns-updater
```
## How It Works
1. **IP Detection**: The application queries `api.ipify.org` to get your current public IP address
2. **Record Check**: For each specified DNS record, it checks if an A record already exists
3. **Update or Create**:
- If the record exists and the IP is different, it updates the record
- If the record doesn't exist, it creates a new A record
- If the IP is the same, no action is taken
## Logging
The application provides detailed logging:
- Configuration details (without sensitive information)
- Cloudflare API URLs being called
- Record existence status
- Update/create operations
- Error messages
## Error Handling
The application handles various error scenarios:
- Network connectivity issues
- Invalid API credentials
- Cloudflare API errors
- JSON parsing errors
- Missing environment variables
## Security Notes
- Never commit your `cloudflare_updater_util.sh` file with real credentials
- Use Cloudflare API tokens instead of global API keys when possible
- Limit API token permissions to only what's necessary
- Consider using environment variables in production instead of hardcoded values
## Troubleshooting
### Common Issues
1. **"Failed to load config"**: Check that all required environment variables are set
2. **"Failed to check if record exists"**: Verify your API credentials and Zone ID
3. **"Failed to update/create DNS"**: Check API token permissions and record names
### Debug Mode
Add logging to see detailed API responses:
```bash
export CLOUDFLARE_EMAIL="your-email@example.com"
export CLOUDFLARE_AUTH_KEY="your-api-token"
export CLOUDFLARE_ZONE_ID="your-zone-id"
export CLOUDFLARE_RECORD_NAMES="home.example.com"
go run main.go config.go dns.go
```
## License
This project is open source. Feel free to modify and distribute as needed.
## Contributing
Contributions are welcome! Please feel free to submit issues and pull requests.
## Support
If you encounter any issues, please check the troubleshooting section above or create an issue in the repository.

View File

@@ -0,0 +1,13 @@
#!/bin/bash
export CLOUDFLARE_EMAIL="placeholder@gmail.com"
export CLOUDFLARE_AUTH_KEY="placeholder"
export CLOUDFLARE_ZONE_ID="placeholder"
export CLOUDFLARE_RECORD_NAMES="placeholder.com"
export CLOUDFLARE_FORCE_IP="placeholder"
# Build the binary
go build -o cloudflare-dns-updater main.go config.go dns.go
# Run the binary
./cloudflare-dns-updater

26
config.go Normal file
View File

@@ -0,0 +1,26 @@
package main
import (
"os"
"strings"
)
// Email, Auth Key, Zone ID, Record Names
type Config struct {
Email string
AuthKey string
ZoneID string
RecordNames []string
ForceIP string
}
func LoadConfig() (*Config, error) {
return &Config{
Email: os.Getenv("CLOUDFLARE_EMAIL"),
AuthKey: os.Getenv("CLOUDFLARE_AUTH_KEY"),
ZoneID: os.Getenv("CLOUDFLARE_ZONE_ID"),
RecordNames: strings.Split(os.Getenv("CLOUDFLARE_RECORD_NAMES"), ","),
ForceIP: os.Getenv("CLOUDFLARE_FORCE_IP"),
}, nil
}

184
dns.go Normal file
View File

@@ -0,0 +1,184 @@
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
)
type IpResponse struct {
Ip string `json:"ip"`
}
type DnsRecord struct {
Id string `json:"id"`
Name string `json:"name"`
Type string `json:"type"`
Content string `json:"content"`
Ttl int `json:"ttl"`
Proxied bool `json:"proxied"`
}
type DnsRecords struct {
Result []DnsRecord `json:"result"`
}
func getIp() (string, error) {
// Get IP from ipify and return it as a IpResponse
ipUrl := "https://api.ipify.org?format=json"
resp, err := http.Get(ipUrl)
if err != nil {
return "", err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", err
}
var ipResponse IpResponse
if err := json.Unmarshal(body, &ipResponse); err != nil {
return "", err
}
return ipResponse.Ip, nil
}
type DnsUpdater struct {
config *Config
}
func (d *DnsUpdater) CheckIfRecordsExist(recordName string) (DnsRecord, error) {
cloudFlareUrl := "https://api.cloudflare.com/client/v4/zones/" + d.config.ZoneID + "/dns_records?name=" + recordName + "&type=A"
log.Printf("CloudFlare URL: %s", cloudFlareUrl)
req, err := http.NewRequest("GET", cloudFlareUrl, nil)
if err != nil {
return DnsRecord{}, err
}
req.Header.Set("X-Auth-Email", d.config.Email)
req.Header.Set("Authorization", "Bearer "+d.config.AuthKey)
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return DnsRecord{}, err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return DnsRecord{}, err
}
log.Printf("Body: %s", string(body))
var dnsRecords DnsRecords
if err := json.Unmarshal(body, &dnsRecords); err != nil {
return DnsRecord{}, err
}
log.Printf("DNS Records: %+v", dnsRecords)
// Check if the record exists
for _, record := range dnsRecords.Result {
if record.Name == recordName && record.Type == "A" {
return record, nil
}
}
return DnsRecord{}, nil
}
func (d *DnsUpdater) Update(record DnsRecord) error {
ip, err := getIp()
if err != nil {
return err
}
if d.config.ForceIP != "" {
ip = d.config.ForceIP
}
if record.Content == ip {
return nil
}
record.Content = ip
record.Ttl = 3600
cloudFlareUrl := "https://api.cloudflare.com/client/v4/zones/" + d.config.ZoneID + "/dns_records/" + record.Id
body, err := json.Marshal(record)
if err != nil {
return err
}
req, err := http.NewRequest("PATCH", cloudFlareUrl, bytes.NewBuffer(body))
if err != nil {
return err
}
req.Header.Set("X-Auth-Email", d.config.Email)
req.Header.Set("Authorization", "Bearer "+d.config.AuthKey)
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
body, err = io.ReadAll(resp.Body)
if err != nil {
return err
}
if resp.StatusCode != 200 {
return fmt.Errorf("failed to update DNS: %s", string(body))
}
log.Printf("Updated DNS: %s", string(body))
return nil
}
func (d *DnsUpdater) Create(recordName string) error {
ip, err := getIp()
if err != nil {
return err
}
if d.config.ForceIP != "" {
ip = d.config.ForceIP
}
record := DnsRecord{
Name: recordName,
Type: "A",
Content: ip,
Ttl: 3600,
Proxied: false,
}
cloudFlareUrl := "https://api.cloudflare.com/client/v4/zones/" + d.config.ZoneID + "/dns_records"
body, err := json.Marshal(record)
if err != nil {
return err
}
req, err := http.NewRequest("POST", cloudFlareUrl, bytes.NewBuffer(body))
if err != nil {
return err
}
req.Header.Set("X-Auth-Email", d.config.Email)
req.Header.Set("Authorization", "Bearer "+d.config.AuthKey)
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
body, err = io.ReadAll(resp.Body)
if err != nil {
return err
}
if resp.StatusCode != 200 {
return fmt.Errorf("failed to create DNS: %s", string(body))
}
log.Printf("Created DNS: %s", string(body))
return nil
}

3
go.mod Normal file
View File

@@ -0,0 +1,3 @@
module cloudflare-dns-updater
go 1.24.0

34
main.go Normal file
View File

@@ -0,0 +1,34 @@
package main
import "log"
func main() {
config, err := LoadConfig()
if err != nil {
log.Fatalf("Failed to load config: %v", err)
}
log.Printf("Config: %+v", config)
dnsUpdater := DnsUpdater{config: config}
for _, recordName := range config.RecordNames {
record, err := dnsUpdater.CheckIfRecordsExist(recordName)
if err != nil {
log.Fatalf("Failed to check if record exists: %v", err)
}
if record.Id != "" {
log.Printf("Record %s exists", recordName)
err = dnsUpdater.Update(record)
if err != nil {
log.Fatalf("Failed to update DNS: %v", err)
}
} else {
log.Printf("Record %s does not exist. Creating it.....", recordName)
err = dnsUpdater.Create(recordName)
if err != nil {
log.Fatalf("Failed to create DNS: %v", err)
}
}
}
if err != nil {
log.Fatalf("Failed to update or create DNS: %v", err)
}
}