220 lines
5.0 KiB
Go
220 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\n%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
|
|
}
|