├── .gitignore ├── .vscode └── settings.json ├── go.mod ├── go.sum ├── main.go ├── LICENSE ├── Makefile ├── readme.md └── src └── main.go /.gitignore: -------------------------------------------------------------------------------- 1 | bin -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "go.formatTool": "goimports" 3 | } -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/jsfour/awsdyndns/v1 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/aws/aws-sdk-go v1.17.1 7 | github.com/spf13/pflag v1.0.3 8 | ) 9 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/aws/aws-sdk-go v1.17.1 h1:RoOo57SetcPFGQ6vesLfWIpfnsbpEiuwiHq6aCvjrZw= 2 | github.com/aws/aws-sdk-go v1.17.1/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= 3 | github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= 4 | github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= 5 | github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= 6 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 7 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/jsfour/awsdyndns/v1/src" 8 | "github.com/spf13/pflag" 9 | ) 10 | 11 | var zoneID string 12 | var hostname string 13 | 14 | func init() { 15 | pflag.StringVarP(&zoneID, "zoneid", "z", "", "Route 53 zone id") 16 | pflag.StringVarP(&hostname, "dnshostname", "d", "", "Hostname on the zone id to update") 17 | pflag.Parse() 18 | } 19 | 20 | func main() { 21 | 22 | if zoneID == "" { 23 | fmt.Println("You must specify a zoneid. Run with -h to see usage.") 24 | os.Exit(1) 25 | } 26 | 27 | if hostname == "" { 28 | fmt.Println("You must specify a hostname. Run with -h to see usage.") 29 | os.Exit(1) 30 | } 31 | 32 | ip, err := src.GetPublicIp() 33 | 34 | if err != nil { 35 | fmt.Println(err) 36 | } 37 | 38 | err = src.SetDns(ip, hostname, zoneID) 39 | if err != nil { 40 | fmt.Println(err) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 James Smoot IV 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | BINARY = awsdyndns 2 | GOARCH = amd64 3 | 4 | VERSION=1 5 | COMMIT=$(shell git rev-parse HEAD) 6 | BRANCH=$(shell git rev-parse --abbrev-ref HEAD) 7 | CURRENT_DIR=$(shell pwd) 8 | BUILD_DIR=${CURRENT_DIR}/bin 9 | 10 | # Setup the -ldflags option for go build here, interpolate the variable values 11 | LDFLAGS = -ldflags "-X main.VERSION=${VERSION} -X main.COMMIT=${COMMIT} -X main.BRANCH=${BRANCH}" 12 | 13 | # Build the project 14 | all: clean linux darwin windows 15 | 16 | configure: 17 | mkdir ${BUILD_DIR} 18 | 19 | linux: 20 | GOOS=linux GOARCH=${GOARCH} go build ${LDFLAGS} -o ${BUILD_DIR}/${BINARY}-v${VERSION}-linux-${GOARCH} . 21 | # Build arm version for Raspberry Pi 22 | GOOS=linux GOARCH=arm GOARM=5 go build ${LDFLAGS} -o ${BUILD_DIR}/${BINARY}-v${VERSION}-linux-arm . 23 | 24 | darwin: 25 | GOOS=darwin GOARCH=${GOARCH} go build ${LDFLAGS} -o ${BUILD_DIR}/${BINARY}-v${VERSION}-darwin-${GOARCH} . 26 | 27 | windows: 28 | GOOS=windows GOARCH=${GOARCH} go build ${LDFLAGS} -o ${BUILD_DIR}/${BINARY}-v${VERSION}-windows-${GOARCH}.exe . 29 | 30 | fmt: 31 | go fmt $$(go list ./... | grep -v /vendor/) 32 | 33 | clean: 34 | rm -rf ${BUILD_DIR} 35 | 36 | .PHONY: linux darwin windows fmt clean -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # awsdyndns 2 | 3 | Awsdyndns is a [dynamic dns](https://en.wikipedia.org/wiki/Dynamic_DNS) tool that updates a Route53 hostname with the current public IP of the caller. The way I use awsdyndns is by installing it on a RaspberryPi and running the program on a 10 minute cron. 4 | 5 | Public IP is obtained via [ipify](https://www.ipify.org/). 6 | 7 | ## Installing 8 | 9 | Download the [latest release for your system](https://github.com/jsfour/awsdyndns/releases), rename it to awsdyndns and put it into your path. 10 | 11 | #### macOs example: 12 | ``` 13 | cd ~/Downloads 14 | curl -O https://github.com/jsfour/awsdyndns/releases/download/v1/awsdyndns-v1-darwin-amd64 15 | chmod +x ./awsdyndns-v1-darwin-amd64 16 | sudo mv ./awsdyndns-v1-darwin-amd64 /usr/local/bin/awsdyndns 17 | awsdyndns -h 18 | ``` 19 | 20 | ## Building from source 21 | ``` 22 | git clone git@github.com:jsfour/awsdyndns.git 23 | cd awsdyndns 24 | make all 25 | ``` 26 | 27 | ## Usage 28 | Setup your `AWS_ACCESS_KEY` and `AWS_SECRET_KEY` [from AWS](https://docs.aws.amazon.com/general/latest/gr/aws-security-credentials.html). Make sure that this account has permissions to the Route53 zone that you want to modify. 29 | 30 | Once you have configured the `AWS_ACCESS_KEY`, and `AWS_SECRET_KEY` environment variables just run `awsdyndns`. 31 | 32 | ``` 33 | $ awsdyndns -d my.host.com. -z ZONEID // don't forget the trailing `.` on the hostname. 34 | Updating dns 35 | ``` 36 | 37 | ## Help 38 | ``` 39 | $ awsdyndns -h 40 | Usage of awsdyndns: 41 | -d, --dnshostname string Hostname on the zone id to update 42 | -z, --zoneid string Route 53 zone id 43 | ``` 44 | 45 | 46 | ## Required environment variables: 47 | | Variable | 48 | |----------| 49 | | AWS_ACCESS_KEY_ID | 50 | | AWS_SECRET_ACCESS_KEY | 51 | 52 | -------------------------------------------------------------------------------- /src/main.go: -------------------------------------------------------------------------------- 1 | package src 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "net/http" 7 | 8 | "github.com/aws/aws-sdk-go/aws" 9 | "github.com/aws/aws-sdk-go/aws/session" 10 | "github.com/aws/aws-sdk-go/service/route53" 11 | ) 12 | 13 | const PublicIpEndpoint = "https://api.ipify.org" 14 | 15 | func GetPublicIp() (string, error) { 16 | res, err := http.Get(PublicIpEndpoint) 17 | if err != nil { 18 | return "", err 19 | } 20 | 21 | buf := new(bytes.Buffer) 22 | buf.ReadFrom(res.Body) 23 | s := buf.String() 24 | 25 | return s, nil 26 | } 27 | 28 | func SetDns(ip string, dnsName string, zoneID string) error { 29 | fmt.Println("Updating dns") 30 | sess := session.Must(session.NewSession(&aws.Config{})) 31 | 32 | svc := route53.New(sess) 33 | 34 | var ttl int64 = 300 35 | 36 | recordSetQuery := &route53.ListResourceRecordSetsInput{ 37 | HostedZoneId: aws.String(zoneID), 38 | StartRecordName: aws.String(dnsName), 39 | } 40 | 41 | res, err := svc.ListResourceRecordSets(recordSetQuery) 42 | if err != nil { 43 | return err 44 | } 45 | 46 | var changes []*route53.Change 47 | for _, recSet := range res.ResourceRecordSets { 48 | if *recSet.Name != dnsName { 49 | continue 50 | } 51 | for _, record := range recSet.ResourceRecords { 52 | // Do I need to change the ip? 53 | if *record.Value == ip { 54 | fmt.Println("No change in IP moving along.") 55 | return nil 56 | } 57 | 58 | // Need to changes 59 | 60 | ttl = *recSet.TTL 61 | chg := &route53.Change{ 62 | Action: aws.String("DELETE"), 63 | ResourceRecordSet: recSet, 64 | } 65 | changes = append(changes, chg) 66 | 67 | break 68 | } 69 | } 70 | 71 | newChange := &route53.Change{ 72 | Action: aws.String("CREATE"), 73 | ResourceRecordSet: &route53.ResourceRecordSet{ 74 | Name: aws.String(dnsName), 75 | Type: aws.String("A"), 76 | TTL: aws.Int64(ttl), 77 | ResourceRecords: []*route53.ResourceRecord{ 78 | { 79 | Value: aws.String(ip), 80 | }, 81 | }, 82 | }, 83 | } 84 | 85 | changes = append(changes, newChange) 86 | 87 | changeSet := &route53.ChangeResourceRecordSetsInput{ 88 | ChangeBatch: &route53.ChangeBatch{ 89 | Changes: changes, 90 | }, 91 | HostedZoneId: aws.String(zoneID), 92 | } 93 | 94 | _, err = svc.ChangeResourceRecordSets(changeSet) 95 | if err != nil { 96 | return err 97 | } 98 | 99 | return nil 100 | } 101 | --------------------------------------------------------------------------------