dnsupdater/main.go

221 lines
5.0 KiB
Go

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" <laurent@lehouerou.net>
From: "Laurent Le Houerou" <laurent@lehouerou.net>
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
}