package main import ( "bytes" "encoding/json" "fmt" log "github.com/sirupsen/logrus" "github.com/urfave/cli" "io/ioutil" "net/http" "net/smtp" "os" "path" ) type Configuration struct { Username string `json:"name"` Email string `json:"email"` GandiKey string `json:"gandikey"` Domains []string `json:"domains"` } type DnsResponse struct { RecordType string `json:"rrset_type"` Ttl int `json:"rrset_ttl"` Name string `json:"rrset_name"` Values []string `json:"rrset_values"` } var ( configPath string ) func main() { app := cli.NewApp() app.Name = "dnsupdater" app.Version = "0.0.1" app.Usage = "Automatically update dns" app.Action = start app.Flags = []cli.Flag{ cli.StringFlag{ Name: "config, C", Usage: "path to config file", Value: "/config/conf.json", EnvVar: "CONFIG_PATH", }, } if err := app.Run(os.Args); err != nil { log.Fatal(err) } } func start(c *cli.Context) { if err := update(c); err != nil { log.Errorln(err) SendStatusMail(fmt.Sprintf("Error during update process : %s", err)) } } func update(c *cli.Context) error { configPath = c.String("config") if confPathExists, _ := exists(configPath); !confPathExists { configPath = "./data" } file, _ := os.Open(path.Join(configPath, "conf.json")) defer file.Close() decoder := json.NewDecoder(file) configuration := Configuration{} err := decoder.Decode(&configuration) newIp, err := GetOutboundIP() if err != nil { return err } updatedDomains := "" for _, domain := range configuration.Domains { currentIp, err := GetCurrentIp(configuration, domain) if err != nil { return err } if currentIp == newIp { continue } fmt.Printf("%s -> Ip has changed %s -> %s\n", domain, currentIp, newIp) err = SetCurrentIp(newIp, domain, configuration) if err != nil { return err } updatedDomains += fmt.Sprintf("\t- %s\n", domain) } if updatedDomains != "" { SendStatusMail(fmt.Sprintf(`Home IP has changed : %s Dns updated successfully for domains %s`, newIp, updatedDomains)) } return nil } func SendStatusMail(messageText string) { // user we are authorizing as from := "laurent@lehouerou.net" // use we are sending email to to := "laurent@lehouerou.net" // server we are authorized to send email through host := "smtp.fastmail.com" // Create the authentication for the SendMail() // using PlainText, but other authentication methods are encouraged auth := smtp.PlainAuth("", from, "c9bd8fb8l4bhs2f8", host) // NOTE: Using the backtick here ` works like a heredoc, which is why all the // rest of the lines are forced to the beginning of the line, otherwise the // formatting is wrong for the RFC 822 style message := fmt.Sprintf(`To: "Laurent Le Houerou" From: "Laurent Le Houerou" Subject: DnsUpdater Status %s `, messageText) if err := smtp.SendMail(host+":587", auth, from, []string{to}, []byte(message)); err != nil { fmt.Println("Error SendMail: ", err) } else { fmt.Println("Email Sent!") } } func GetCurrentIp(configuration Configuration, domain string) (string, error) { url := fmt.Sprintf("https://dns.api.gandi.net/api/v5/domains/%s/records/@/A", domain) req, err := http.NewRequest("GET", url, nil) if err != nil { return "", err } req.Header.Set("X-Api-Key", configuration.GandiKey) client := &http.Client{} resp, err := client.Do(req) if err != nil { return "", err } defer resp.Body.Close() decoder := json.NewDecoder(resp.Body) dnsresponse := DnsResponse{} err = decoder.Decode(&dnsresponse) if err != nil { return "", err } return dnsresponse.Values[0], nil } func SetCurrentIp(newip string, domain string, configuration Configuration) error { url := fmt.Sprintf("https://dns.api.gandi.net/api/v5/domains/%s/records/@/A", domain) fmt.Println("URL:>", url) var str = fmt.Sprintf("{\"rrset_ttl\": %d,\"rrset_values\": [\"%s\"]}", 600, newip) fmt.Println("json:", str) var jsonStr = []byte(str) req, err := http.NewRequest("PUT", url, bytes.NewBuffer(jsonStr)) if err != nil { return err } req.Header.Set("X-Api-Key", configuration.GandiKey) req.Header.Set("Content-Type", "application/json") client := &http.Client{} resp, err := client.Do(req) if err != nil { return err } defer resp.Body.Close() fmt.Println("response Status:", resp.Status) fmt.Println("response Headers:", resp.Header) body, _ := ioutil.ReadAll(resp.Body) fmt.Println("response Body:", string(body)) return nil } func GetOutboundIP() (string, error) { url := "https://api.ipify.org" req, err := http.NewRequest("GET", url, nil) if err != nil { return "", err } res, err := http.DefaultClient.Do(req) if err != nil { return "", err } defer res.Body.Close() body, err := ioutil.ReadAll(res.Body) if err != nil { return "", err } return string(body), nil } func exists(path string) (bool, error) { _, err := os.Stat(path) if err == nil { return true, nil } if os.IsNotExist(err) { return false, nil } return true, err }