Skip to content

Commit

Permalink
Added support for multiple entries
Browse files Browse the repository at this point in the history
- Makefile no longer enforces running install and uninstall as root
- Removed `Priority` parameter as it is incompatible with A and AAAA records
- Oink! No longer attempts to update records with multiple entries. Porkbun API seems to not allow writing annotations so it is better to avoid breaking things and do nothing. (It is not a super common use-case anyways)
- Updated `config.json` and the `README.md` documentation
- Default `interval` changed to 15 min
- Tested Oink! Now it is out of beta
  • Loading branch information
RLado committed Oct 7, 2023
1 parent 11f2864 commit c9092d8
Show file tree
Hide file tree
Showing 4 changed files with 128 additions and 72 deletions.
8 changes: 4 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ clean:

install: build
# check the user is root
@if [ `id -u` -ne 0 ]; then echo "Please run as root"; exit 1; fi
@if [ `id -u` -ne 0 ]; then printf "\033[33mWarning: This operation might require superuser privileges\033[0m\n"; fi

# install binary
@mkdir -p ${DESTDIR}/usr/bin/
Expand All @@ -33,12 +33,12 @@ install: build
@chmod 644 ${DESTDIR}/usr/share/licenses/oink/LICENSE

# advice the user
@echo "\033[38;2;255;133;162mOink installed successfully\033[0m"
@printf "\033[38;2;255;133;162mOink installed successfully\033[0m\n"
@echo "Please remember to edit /etc/oink_ddns/config.json before enabling the DDNS client using 'systemctl enable oink_ddns.service' 'systemctl start oink_ddns.service'"

uninstall:
# check the user is root
@if [ `id -u` -ne 0 ]; then echo "Please run as root"; exit 1; fi
@if [ `id -u` -ne 0 ]; then printf "\033[33mWarning: This operation might require superuser privileges\033[0m\n"; fi

# completely remove the binary and configuration file
@rm ${DESTDIR}/usr/bin/${BINARY_NAME}
Expand All @@ -55,4 +55,4 @@ uninstall:
@rmdir ${DESTDIR}/usr/share/licenses/oink

# notify the user
@echo "\033[38;2;255;133;162mOink uninstalled successfully\033[0m"
@printf "\033[38;2;255;133;162mOink uninstalled successfully\033[0m\n"
51 changes: 41 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

## A lightweight DDNS client for [Porkbun](https://porkbun.com)

> NOTE: **Oink!** is in BETA. If you encounter any bugs please report them at https://github.com/RLado/Oink!

**Oink!** is an unofficial DDNS client for porkbun.com built in Go. **Oink!** only depends on Go's standard library.

Expand Down Expand Up @@ -36,20 +35,52 @@ The setup process is simple:

- If installed correctly you should find **Oink!**'s configuration file in */etc/oink_ddns/config.json*. Open the file with your text editor of choice.
- In the configuration file you should find the following contents that must be filled in:
> ⚠️ In case you do not already have an API key, you will need to request one at: https://porkbun.com/account/api
> ⚠️ *In case you do not already have an API key, you will need to request one at: https://porkbun.com/account/api*
```json
{
"secretapikey": "<your secret api key here>",
"apikey": "<your api key here>",
"domain": "<your domain here>",
"subdomain": "<your subdomain here>",
"ttl": 600,
"priority": 0,
"interval": 300
"global": {
"secretapikey": "<your secret api key here>",
"apikey": "<your api key here>",
"interval": 900,
"ttl": 600
},
"domains": [
{
"domain": "<your domain here>",
"subdomain": "<your subdomain here>"
}
]
}
```

If you want to update more than one domain or subdomain, you can add new domains like so:
```json
{
"global": {
"secretapikey": "<your secret api key here>",
"apikey": "<your api key here>",
"interval": 900,
"ttl": 600
},
"domains": [
{
"secretapikey": "<override secret api key here>",
"apikey": "<override api key here>",
"domain": "<your domain here>",
"subdomain": "<your subdomain here>",
"ttl": 800
},
{
"domain": "<your domain 2 here>",
"subdomain": "<your subdomain 2 here>"
}
]
}
```
> *Entries must at least contain the `domain` and `subdomain` fields.*
- Enable and start the service using `systemd`
> ⚠️ Make sure to **enable** API ACCESS in your porkbun domain's control panel
> ⚠️ *Make sure to **enable** API ACCESS in your porkbun domain's control panel*
```bash
systemctl enable oink_ddns
systemctl start oink_ddns
Expand Down
19 changes: 12 additions & 7 deletions config/config.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
{
"secretapikey": "<your secret api key here>",
"apikey": "<your api key here>",
"domain": "<your domain here>",
"subdomain": "<your subdomain here>",
"ttl": 600,
"priority": 0,
"interval": 300
"global": {
"secretapikey": "<your secret api key here>",
"apikey": "<your api key here>",
"interval": 900,
"ttl": 600
},
"domains": [
{
"domain": "<your domain here>",
"subdomain": "<your subdomain here>"
}
]
}
122 changes: 71 additions & 51 deletions src/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@
* A lightweight DDNS client for porkbun.com
*
* Author: Ricard Lado <ricard@lado.one>
* Repository:
* Repository: https://github.com/RLado/Oink
*
* Version: 0.1
* License: MIT
*/

Expand All @@ -25,13 +24,23 @@ import (
)

type Config struct {
Global GlobConfig
Domains []DomConfig
}

type GlobConfig struct {
Secretapikey string
Apikey string
Interval int
Ttl int
}

type DomConfig struct {
Secretapikey string
Apikey string
Domain string
Subdomain string
Ttl int
Priority int
Interval int
}

type IP struct {
Expand All @@ -41,7 +50,7 @@ type IP struct {

// Get the current IP address
// Requests the IP address from the porkbun API & checks if the API keys are valid
func get_ip(config Config) (IP, error) {
func get_ip(config DomConfig) (IP, error) {
ip := IP{}

client := http.Client{
Expand Down Expand Up @@ -97,7 +106,7 @@ func get_ip(config Config) (IP, error) {
// Update the DNS record
// Updates the DNS record with the current IP address
// Returns true if the record was updated, false if it wasn't
func update_dns(config Config, ip IP) (bool, error) {
func update_dns(config DomConfig, ip IP) (bool, error) {
client := http.Client{
Timeout: 30 * time.Second,
}
Expand Down Expand Up @@ -155,26 +164,8 @@ func update_dns(config Config, ip IP) (bool, error) {
// Save the record ID
record_id = data["records"].([]interface{})[0].(map[string]interface{})["id"].(string)
}
} else if len(data["records"].([]interface{})) > 1 { // Multiple records found. Update if a record with a "ddns" annotation is found
log.Printf("Warning: Multiple records found, make sure to add a \"ddns\" annotation to the record you wish to update")
annotation_found := false

// Look for a record with a notes value of "ddns"
for _, record := range data["records"].([]interface{}) {
if record.(map[string]interface{})["notes"].(string) == "ddns" {
annotation_found = true
// Check if the record needs to be updated
if record.(map[string]interface{})["content"].(string) != ip.Ip {
// Update the record
update_req = true
// Save the record ID
record_id = record.(map[string]interface{})["id"].(string)
}
}
}
if !annotation_found {
log.Fatal("Error: Multiple records found, but none with a \"ddns\" annotation")
}
} else if len(data["records"].([]interface{})) > 1 { // Multiple records found. Avoid updating
log.Printf("Warning: Multiple records found for %s.%s -- Not updating any records", config.Subdomain, config.Domain)
}

// Update the record
Expand All @@ -190,7 +181,6 @@ func update_dns(config Config, ip IP) (bool, error) {
"type": recordType,
"content": ip.Ip,
"ttl": fmt.Sprint(config.Ttl),
"prio": fmt.Sprint(config.Priority),
})
if err != nil {
return false, fmt.Errorf("error building request body: %s", err)
Expand Down Expand Up @@ -224,7 +214,7 @@ func update_dns(config Config, ip IP) (bool, error) {

// Create a new DNS record
// Creates a new DNS record with the current IP address
func create_record(config Config, ip IP) (bool, error) {
func create_record(config DomConfig, ip IP) (bool, error) {
client := http.Client{
Timeout: 30 * time.Second,
}
Expand All @@ -244,7 +234,6 @@ func create_record(config Config, ip IP) (bool, error) {
"type": recordType,
"content": ip.Ip,
"ttl": fmt.Sprint(config.Ttl),
"prio": fmt.Sprint(config.Priority),
})
if err != nil {
return false, fmt.Errorf("error building request body: %s", err)
Expand Down Expand Up @@ -299,35 +288,66 @@ func main() {
log.Fatalf("Error decoding config file: %s", err)
}

// Run the update loop
for {
if *verbose {
log.Printf("Updating record: %s.%s", config.Subdomain, config.Domain)
}
// Get current IP address
current_ip, err := get_ip(config)
if err != nil {
log.Fatalln(err)
}
// Enforce minimum interval of 60 seconds
if config.Global.Interval < 60 {
if *verbose {
log.Printf("Current IP address: %s", current_ip.Ip)
log.Printf("Warning: Minimum interval is 60 seconds, setting interval to 60 seconds")
}
config.Global.Interval = 60
}

// Update DNS record
updated, err := update_dns(config, current_ip)
if err != nil {
log.Fatalln(err)
}
if updated {
log.Printf("Record updated successfully to: %s", current_ip.Ip)
} else if *verbose {
log.Printf("Record is already up to date")
// Run the update loop
for {
// Update domains
for _, domConfig := range config.Domains {
// Fill in missing values from the global config
if domConfig.Secretapikey == "" {
domConfig.Secretapikey = config.Global.Secretapikey
}
if domConfig.Apikey == "" {
domConfig.Apikey = config.Global.Apikey
}
if domConfig.Ttl == 0 {
domConfig.Ttl = config.Global.Ttl
}

// Enforce minimum TTL of 600 seconds (as defined by porkbun)
if domConfig.Ttl < 600 {
if *verbose {
log.Printf("Warning: Minimum TTL is 600 seconds, setting TTL for %s.%s to 600 seconds", domConfig.Subdomain, domConfig.Domain)
}
domConfig.Ttl = 600
}

// Start the update record process
if *verbose {
log.Printf("Updating record: %s.%s", domConfig.Subdomain, domConfig.Domain)
}
// Get current IP address
current_ip, err := get_ip(domConfig)
if err != nil {
log.Fatalln(err)
}
if *verbose {
log.Printf("Current IP address: %s", current_ip.Ip)
}

// Update DNS record
updated, err := update_dns(domConfig, current_ip)
if err != nil {
log.Fatalln(err)
}
if updated {
log.Printf("Record %s.%s updated successfully to: %s", domConfig.Subdomain, domConfig.Domain, current_ip.Ip)
} else if *verbose {
log.Printf("Record is already up to date")
}
}

// Wait for the next update
if *verbose {
log.Printf("Waiting %d seconds for the next update", config.Interval)
log.Printf("Waiting %d seconds for the next update", config.Global.Interval)
}
time.Sleep(time.Duration(config.Interval) * time.Second)
time.Sleep(time.Duration(config.Global.Interval) * time.Second)
}
}

0 comments on commit c9092d8

Please sign in to comment.