Skip to content

Commit

Permalink
feat: add cron
Browse files Browse the repository at this point in the history
  • Loading branch information
chrispinkney committed Oct 25, 2024
1 parent df6afec commit bc40c52
Show file tree
Hide file tree
Showing 9 changed files with 171 additions and 28 deletions.
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ module woodpecker
go 1.22

require github.com/joho/godotenv v1.5.1

require github.com/robfig/cron/v3 v3.0.1 // indirect
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
1 change: 1 addition & 0 deletions src/cmd/pecker.example.conf → pecker.example.conf
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# General Config
IP_SERVICE=https://api.ipify.org # https://ifconfig.me || https://ipinfo.io/ip
CHECK_INTERVAL=3 # interval in minutes to check IP address

# Porkbun Config
PORKBUN_API_EDIT_URL=https://api.porkbun.com/api/json/v3/dns/editByNameType/
Expand Down
114 changes: 95 additions & 19 deletions src/cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,57 +2,133 @@ package main

import (
"fmt"
"github.com/robfig/cron/v3"
"os"
"woodpecker/src/providers/namecheap"

"path/filepath"
"woodpecker/src/config"
"woodpecker/src/constants"
"woodpecker/src/internal/utils"
"woodpecker/src/providers/namecheap"
"woodpecker/src/providers/porkbun"
"woodpecker/src/services"
)

func main() {
loadConfig, err := config.LoadConfig()
configDir, err := utils.GetAppPath()
if err != nil {
fmt.Println("failed to get config path:", err)
os.Exit(1)
}
configPath := filepath.Join(configDir, constants.ConfigFilename)

loadConfig, err := config.LoadConfig(configPath)
if err != nil {
fmt.Println("error loading environment file:", err)
os.Exit(1)
}

ip, err := services.GetPublicIP(loadConfig)
err = updateDNS(loadConfig, configDir)
if err != nil {
fmt.Printf("error during initial DNS update: %v\n", err)
os.Exit(1)
}

setupCron(loadConfig, configDir)

select {}
}

func setupCron(config *config.Config, configPath string) {
c := cron.New()
checkInterval := fmt.Sprintf("@every %dm", config.CheckInterval)

_, err := c.AddFunc(checkInterval, func() {
err := updateDNS(config, configPath)
if err != nil {
fmt.Printf("error during DNS update: %v\n", err)
}
})

if err != nil {
fmt.Println("failed to retrieve public IP:", err)
fmt.Println("failed to schedule DNS update:", err)
os.Exit(1)
}

c.Start()
}

func updateDNS(config *config.Config, configPath string) error {
ip, err := services.GetPublicIP(config)
if err != nil {
return fmt.Errorf("failed to retrieve public IP: %v", err)
}
fmt.Println("current public IP address:", ip)

storedIP, err := utils.ReadIPFromFile(configPath)
if err != nil {
return fmt.Errorf("failed to read stored IP: %v", err)
}
fmt.Println("current stored public IP address:", storedIP)

if storedIP == ip {
fmt.Println("IP address unchanged, skipping DNS updates")
return nil
}

fmt.Println("IP address has changed, proceeding to update DNS records...")

err = updatePorkbunDNS(config, ip)
if err != nil {
return err
}

err = updateNamecheapDNS(config, ip)
if err != nil {
return err
}

err = utils.WriteIPToFile(ip, configPath)
if err != nil {
return fmt.Errorf("failed to store new updated public IP address: %v", err)
}

fmt.Println("DNS updates completed for both providers.")
return nil
}

func updatePorkbunDNS(config *config.Config, ip string) error {
fmt.Println("checking Porkbun DNS records...")
dnsProvider := porkbun.New(loadConfig)
dnsIP, err := dnsProvider.GetCurrentARecord()
porkbunProvider := porkbun.New(config)

dnsIP, err := porkbunProvider.GetCurrentARecord()
if err != nil {
fmt.Println("failed to retrieve Porkbun DNS A record:", err)
os.Exit(1)
return fmt.Errorf("failed to retrieve Porkbun DNS A record: %v", err)
}
fmt.Println("current Porkbun DNS A record IP address:", dnsIP)

if dnsIP != ip {
fmt.Printf("Porkbun DNS record is outdated- current: %s, expected: %s\n", dnsIP, ip)
err := dnsProvider.UpdateARecord(ip)
err := porkbunProvider.UpdateARecord(ip)
if err != nil {
fmt.Println("failed to update DNS A record:", err)
os.Exit(1)
return fmt.Errorf("failed to update Porkbun DNS A record: %v", err)
}
fmt.Println("Porkbun DNS A record updated successfully.")
} else {
fmt.Println("Porkbun DNS A record already up to date")
fmt.Println("Porkbun DNS A record already up to date.")
}

return nil
}

func updateNamecheapDNS(config *config.Config, ip string) error {
fmt.Println("updating Namecheap DNS records...")
dnsProviderNamecheap := namecheap.New(loadConfig)
namecheapProvider := namecheap.New(config)

err = dnsProviderNamecheap.UpdateARecord(ip)
err := namecheapProvider.UpdateARecord(ip)
if err != nil {
fmt.Printf("failed to update Namecheap DNS A record: %v\n", err)
os.Exit(1)
return fmt.Errorf("failed to update Namecheap DNS A record: %v", err)
}
fmt.Println("Namecheap DNS A record updated successfully.")

fmt.Println("DNS updates completed for both providers.")
os.Exit(0)
return nil
}
21 changes: 15 additions & 6 deletions src/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ package config
import (
"fmt"
"os"
"strconv"
"woodpecker/src/constants"

"github.com/joho/godotenv"
)

type Config struct {
IPService string
CheckInterval int
PorkbunAPIKey string
PorkbunSecretKey string
PorkbunDomain string
Expand All @@ -22,14 +24,21 @@ type Config struct {
NamecheapSubdomain string
}

func LoadConfig() (*Config, error) {
err := godotenv.Load(constants.Filename)
func LoadConfig(configPath string) (*Config, error) {
err := godotenv.Load(configPath)
if err != nil {
return nil, fmt.Errorf("failed to load config file %s: %v", constants.Filename, err)
return nil, fmt.Errorf("failed to load config file %s: %v", constants.ConfigFilename, err)
}

intervalStr := os.Getenv("CHECK_INTERVAL")
interval, err := strconv.Atoi(intervalStr)
if err != nil || interval <= 0 {
interval = 3
}

config := &Config{
IPService: os.Getenv("IP_SERVICE"),
CheckInterval: interval,
PorkbunAPIKey: os.Getenv("PORKBUN_API_KEY"),
PorkbunSecretKey: os.Getenv("PORKBUN_SECRET_KEY"),
PorkbunDomain: os.Getenv("PORKBUN_DOMAIN"),
Expand All @@ -43,15 +52,15 @@ func LoadConfig() (*Config, error) {
}

if config.IPService == "" {
return nil, fmt.Errorf("missing required IP Service field in %s", constants.Filename)
return nil, fmt.Errorf("missing required IP Service field in %s", constants.ConfigFilename)
}

if config.PorkbunAPIKey == "" || config.PorkbunSecretKey == "" || config.PorkbunDomain == "" || config.PorkbunEditByNameTypeURL == "" || config.PorkbunRetrieveByNameTypeURL == "" {
return nil, fmt.Errorf("missing required Porkbun key fields in %s", constants.Filename)
return nil, fmt.Errorf("missing required Porkbun key fields in %s", constants.ConfigFilename)
}

if config.NamecheapPassword == "" || config.NamecheapDomain == "" || config.NamecheapEditURL == "" {
return nil, fmt.Errorf("missing required Namecheap key fields in %s", constants.Filename)
return nil, fmt.Errorf("missing required Namecheap key fields in %s", constants.ConfigFilename)
}

return config, nil
Expand Down
3 changes: 2 additions & 1 deletion src/constants/constants.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
package constants

const Filename = "pecker.conf"
const ConfigFilename = "pecker.conf"
const CurrentIpFilename = "current_ip.conf"
54 changes: 54 additions & 0 deletions src/internal/utils/utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package utils

import (
"fmt"
"os"
"path/filepath"
"runtime"
"woodpecker/src/constants"
)

func GetAppPath() (string, error) {
var dir string

switch runtime.GOOS {
case "windows":
appData := os.Getenv("APPDATA")
dir = filepath.Join(appData, "woodpecker")
case "linux":
home := os.Getenv("HOME")
dir = filepath.Join(home, ".local", "share", "woodpecker")
}

err := os.MkdirAll(dir, os.ModePerm)
if err != nil {
return "", fmt.Errorf("failed to create directory: %v", err)
}

return dir, nil
}

func ReadIPFromFile(configPath string) (string, error) {
ipFile := filepath.Join(configPath, constants.CurrentIpFilename)
data, err := os.ReadFile(ipFile)
if os.IsNotExist(err) {
fmt.Println("IP file does not exist yet, assuming first run")
return "", nil
}

if err != nil {
return "", fmt.Errorf("failed to read IP from file: %v", err)
}

return string(data), nil
}

func WriteIPToFile(ip, configPath string) error {
ipFile := filepath.Join(configPath, constants.CurrentIpFilename)
err := os.WriteFile(ipFile, []byte(ip), 0644)
if err != nil {
return fmt.Errorf("failed to write IP to file: %v", err)
}

return nil
}
1 change: 0 additions & 1 deletion src/providers/namecheap/namecheap.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,5 @@ func (n *Namecheap) UpdateARecord(ip string) error {
return fmt.Errorf("failed to update Namecheap DNS record: %s", string(body))
}

fmt.Println("Namecheap DNS A record updated successfully")
return nil
}
1 change: 0 additions & 1 deletion src/providers/porkbun/porkbun.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,5 @@ func (p *Porkbun) UpdateARecord(ip string) error {
return fmt.Errorf("failed to update DNS record, response: %v", response)
}

fmt.Println("Porkbun DNS A record updated successfully")
return nil
}

0 comments on commit bc40c52

Please sign in to comment.