diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 87fe3b6..0f346c0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -18,8 +18,17 @@ jobs: with: go-version: 1.18 + # - name: Generate build files + # uses: thatisuday/go-cross-build@v1 + # with: + # platforms: 'linux/amd64, darwin/amd64, darwin/arm64, windows/amd64' + # package: 'demo' + # name: 'program' + # compress: 'true' + # dest: 'dist' + - name: Build run: go build -v ./... - - name: Test - run: go test -v ./... + # - name: Test + # run: go test -v ./... diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..61ff975 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "yaml.schemas": { + "https://json.schemastore.org/github-workflow.json": "file:///home/lakshman/projects/gddns/.github/workflows/build.yml" + } +} \ No newline at end of file diff --git a/common/constants.go b/common/constants.go index b1a3e1f..d82d81b 100644 --- a/common/constants.go +++ b/common/constants.go @@ -1,3 +1,4 @@ package common -const GoogleDDNSUrl = "https://domains.google.com/nic/update" +const GOOGLE_URL_DDNS_UPDATE = "https://domains.google.com/nic/update" +const GOOGLE_URL_IP_CHECK = "https://domains.google.com/checkip" diff --git a/ddns/gddns.go b/ddns/gddns.go index 428b188..ae5a4c4 100644 --- a/ddns/gddns.go +++ b/ddns/gddns.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "net/http" + "os" "time" "github.com/plkumar/gddns/common" @@ -13,15 +14,47 @@ import ( type GoogleDDNS struct { HostConfig config.Params + tempFile string } func (g *GoogleDDNS) SetHost(cfg *config.Params) { g.HostConfig = *cfg } +func (g *GoogleDDNS) writeCurrentIP(tmpFile string, ip string) error { + f, err := os.CreateTemp("", tmpFile) + if err == nil { + defer f.Close() + f.WriteString(ip) + g.tempFile = f.Name() + return nil + } else { + return err + } +} + +func (g *GoogleDDNS) readIPFromTmpFile() (string, error) { + data, err := os.ReadFile(g.tempFile) + if err == nil { + return string(data), nil + } else { + return "", err + } +} + +func (g *GoogleDDNS) checkIPChanged(tmpFile string, ip string) bool { + lastIPAddress, err := g.readIPFromTmpFile() + if err == nil { + if lastIPAddress == ip { + return false + } + } + return true +} + func (g *GoogleDDNS) GetIP() (string, error) { - resp, err := http.Get("https://domains.google.com/checkip") + resp, err := http.Get(common.GOOGLE_URL_IP_CHECK) if err == nil { scanner := bufio.NewScanner(resp.Body) if scanner.Scan() { @@ -44,7 +77,7 @@ func (g *GoogleDDNS) UpdateDDNSIp() (string, error) { Timeout: time.Second * 10, } - req, err := http.NewRequest("GET", common.GoogleDDNSUrl, nil) + req, err := http.NewRequest("GET", common.GOOGLE_URL_DDNS_UPDATE, nil) if err != nil { //fmt.Print("Got error %s", err.Error()) return "", err diff --git a/go.mod b/go.mod index 32e5f9b..38a889a 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,9 @@ module github.com/plkumar/gddns go 1.18 -require gopkg.in/yaml.v2 v2.4.0 +require ( + github.com/takama/daemon v1.0.0 + gopkg.in/yaml.v2 v2.4.0 +) + +require golang.org/x/sys v0.0.0-20200722175500-76b94024e4b6 // indirect diff --git a/go.sum b/go.sum index 7534661..7928dce 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,8 @@ +github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k= +github.com/takama/daemon v1.0.0 h1:XS3VLnFKmqw2Z7fQ/dHRarrVjdir9G3z7BEP8osjizQ= +github.com/takama/daemon v1.0.0/go.mod h1:gKlhcjbqtBODg5v9H1nj5dU1a2j2GemtuWSNLD5rxOE= +golang.org/x/sys v0.0.0-20200722175500-76b94024e4b6 h1:X9xIZ1YU8bLZA3l6gqDUHSFiD0GFI9S548h6C8nDtOY= +golang.org/x/sys v0.0.0-20200722175500-76b94024e4b6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/main.go b/main.go index dd6ca23..8bbe174 100644 --- a/main.go +++ b/main.go @@ -2,48 +2,132 @@ package main import ( "flag" - "fmt" + "log" + "os" + "os/signal" "strings" + "syscall" + "time" common "github.com/plkumar/gddns/common" config "github.com/plkumar/gddns/config" "github.com/plkumar/gddns/ddns" + "github.com/takama/daemon" ) +const ( + name = "gddns" + description = "Google Dynamic DNS Client Daemon" +) + +var dependencies = []string{"dummy.service"} + +var stdlog, errlog *log.Logger + +type Service struct { + daemon.Daemon +} + +func (service *Service) Manage(configFile *string) (string, error) { + + usage := "Usage: myservice install | remove | start | stop | status" + + if len(os.Args) > 1 { + command := os.Args[1] + switch command { + case "install": + return service.Install() + case "remove": + return service.Remove() + case "start": + return service.Start() + case "stop": + return service.Stop() + case "status": + return service.Status() + default: + return usage, nil + } + } + + interrupt := make(chan os.Signal, 1) + signal.Notify(interrupt, os.Interrupt, os.Kill, syscall.SIGTERM) + timer1 := time.NewTimer(5 * time.Minute) + for { + select { + case <-timer1.C: + updateIP(configFile) + case killSignal := <-interrupt: + stdlog.Println("Got signal:", killSignal) + + if killSignal == os.Interrupt { + return "Daemon was interrupted by system signal", nil + } + return "Daemon was killed", nil + } + } +} + +func init() { + stdlog = log.New(os.Stdout, "", 0) + errlog = log.New(os.Stderr, "", 0) +} + +func updateIP(configFile *string) { + + y, err := config.GetConfig(*configFile) + if err == nil { + gd := ddns.GoogleDDNS{} + for key, host := range y.Gddns { + stdlog.Println("Updating for: ", key) + hostParams := host["params"] + gd.SetHost(&hostParams) + + status, err := gd.UpdateDDNSIp() + if err != nil { + errlog.Println(err.Error()) + } else { + + if strings.Contains(status, "success") { + stdlog.Println("DNS Updated successfully.") + } else if strings.Contains(status, "nochg") { + stdlog.Println("No Change") + } else { + // DNS Update failed, log and stop processing current host + // TODO: Stop further DNS update attempts to ensure google is not blocking the client + stdlog.Println(status, common.DDNSStatusMap[status]) + } + } + } + } else { + errlog.Println("Error reading configuration :: ", err) + } + +} + func main() { - fmt.Println("Google Dynamic DNS Client") - standalone := flag.Bool("standalone", true, "Run in standalone mode.") + //stdlog.Println("Google Dynamic DNS Client") + + standalone := flag.Bool("standalone", false, "Run in standalone mode.") configFile := flag.String("config", "gddns.yml", "configuration file path.") flag.Parse() if *standalone { - y, err := config.GetConfig(*configFile) - if err == nil { - gd := ddns.GoogleDDNS{} - for key, host := range y.Gddns { - fmt.Println("Updating for: ", key) - hostParams := host["params"] - gd.SetHost(&hostParams) - - status, err := gd.UpdateDDNSIp() - if err != nil { - fmt.Println(err.Error()) - } else { + updateIP(configFile) + } else { - if strings.Contains(status, "success") { - fmt.Println("DNS Updated successfully.") - } else if strings.Contains(status, "nochg") { - fmt.Println("No Change") - } else { - // DNS Update failed, log and stop processing current host - // TODO: Stop further DNS update attempts to ensure google is not blocking the client - fmt.Println(status, common.DDNSStatusMap[status]) - } - } - } - } else { - fmt.Println("Error reading configuration :: ", err) + srv, err := daemon.New(name, description, daemon.SystemDaemon, dependencies...) + if err != nil { + errlog.Println("Error: ", err) + os.Exit(1) + } + service := &Service{srv} + status, err := service.Manage(configFile) + if err != nil { + errlog.Println(status, "\nError: ", err) + os.Exit(1) } + stdlog.Println(status) } }