Skip to content

Commit 3fce9e2

Browse files
committed
Initial commit
0 parents  commit 3fce9e2

File tree

7 files changed

+259
-0
lines changed

7 files changed

+259
-0
lines changed

.editorconfig

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
root = true
2+
3+
[*]
4+
end_of_line = lf
5+
charset = utf-8
6+
trim_trailing_whitespace = true
7+
insert_final_newline = true
8+
indent_style = space
9+
indent_size = 4
10+
tab_width = 4
11+
12+
[*.md]
13+
trim_trailing_whitespace = false
14+
15+
[*.{js,json}]
16+
tab_width = 2
17+
indent_size = 2

.goreleaser.yml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# .goreleaser.yml
2+
# Build customization
3+
builds:
4+
-
5+
main: cmd/cloudflare-ddns/main.go
6+
binary: cfddns
7+
goos:
8+
- windows
9+
- darwin
10+
- linux
11+
- freebsd
12+
goarch:
13+
- amd64
14+
- 386
15+
- arm
16+
- arm64
17+
goarm:
18+
- 5
19+
- 6
20+
- 7
21+
22+
ignore:
23+
- goos: darwin
24+
goarch: 386
25+
archive:
26+
format: tar.gz
27+
format_overrides:
28+
- goos: windows
29+
format: zip
30+
files:
31+
- LICENSE
32+
- CREDITS.md

CREDITS.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# CloudFlare-DDNS includes code from:
2+
3+
### failover (MIT License)
4+
Copyright (c) 2018 Miguel Dorta
5+
6+
Permission is hereby granted, free of charge, to any person obtaining a copy
7+
of this software and associated documentation files (the "Software"), to deal
8+
in the Software without restriction, including without limitation the rights
9+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
copies of the Software, and to permit persons to whom the Software is
11+
furnished to do so, subject to the following conditions:
12+
13+
The above copyright notice and this permission notice shall be included in all
14+
copies or substantial portions of the Software.
15+
16+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
SOFTWARE.

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2018 claudio4
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.

cmd/cloudflare-ddns/main.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package main
2+
3+
import (
4+
"github.com/claudio4/cloudflare-ddns/pkg/cloudflare"
5+
"flag"
6+
"os"
7+
"fmt"
8+
"github.com/claudio4/cloudflare-ddns/pkg/ip"
9+
)
10+
11+
func main() {
12+
var email, apikey, zoneID, domain string
13+
flag.StringVar(&email, "email", "", "Your Cloudflare email")
14+
flag.StringVar(&apikey, "key", "", "Your Cloudflare API Key")
15+
flag.StringVar(&zoneID, "zoneid", "", "Domain's zone ID")
16+
flag.StringVar(&domain, "domain", "", "Domain to change")
17+
flag.Parse()
18+
if email == "" || apikey == "" || zoneID == "" || domain == "" {
19+
fmt.Println("Missing parameters: All the following parameters are required")
20+
flag.PrintDefaults()
21+
os.Exit(1)
22+
}
23+
24+
IP, err := ip.Get()
25+
if err != nil {
26+
fmt.Fprintln(os.Stderr, err.Error())
27+
os.Exit(10)
28+
}
29+
30+
id, content, ttl, err := cloudflare.GetRecordDetails(email, apikey, zoneID, domain)
31+
if err != nil {
32+
fmt.Fprintln(os.Stderr, err.Error())
33+
os.Exit(20)
34+
}
35+
36+
if content == IP {
37+
os.Exit(0)
38+
}
39+
40+
err = cloudflare.SetRecord(email, apikey, zoneID, id, "A", domain, IP, ttl)
41+
if err != nil {
42+
fmt.Fprintln(os.Stderr, err.Error())
43+
os.Exit(30)
44+
}
45+
}

pkg/cloudflare/record.go

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package cloudflare
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"github.com/pkg/errors"
7+
"io/ioutil"
8+
"net/http"
9+
"strings"
10+
"time"
11+
)
12+
13+
const baseuri = "https://api.cloudflare.com/client/v4/"
14+
15+
type listRecordSResponse struct {
16+
Success bool `json:"success"`
17+
Errors []string `json:"errors"`
18+
Messages []string `json:"messages"`
19+
Result []struct {
20+
ID string `json:"id"`
21+
Name string `json:"name"`
22+
Content string `json:"content"`
23+
TTL int `json:"ttl"`
24+
} `json:"result"`
25+
}
26+
27+
//GetRecordDetails Gets record's id and ttl
28+
func GetRecordDetails(email, apikey, zonedID, name string) (string, string, int, error) {
29+
client := &http.Client{Timeout: time.Second * 2}
30+
req, err := http.NewRequest("GET", fmt.Sprintf("%szones/%s/dns_records", baseuri, zonedID), nil)
31+
if err != nil {
32+
return "", "", 0, errors.Wrap(err, "Error creating GET request")
33+
}
34+
req.Header.Set("X-Auth-Email", email)
35+
req.Header.Set("X-Auth-Key", apikey)
36+
37+
res, err := client.Do(req)
38+
if err != nil {
39+
return "", "", 0, errors.Wrap(err, "Error doing request to Cloudflare")
40+
}
41+
defer res.Body.Close()
42+
43+
body, err := ioutil.ReadAll(res.Body)
44+
if err != nil {
45+
return "", "", 0, errors.Wrap(err, "Error reading response body")
46+
}
47+
48+
var r listRecordSResponse
49+
json.Unmarshal(body, &r)
50+
if !r.Success {
51+
return "", "", 0, fmt.Errorf("Cloudflare Errors: %+v - Cloudflare Messages: %+v", r.Errors, r.Messages)
52+
}
53+
var id, content string
54+
var ttl int
55+
for _, record := range r.Result {
56+
if record.Name == name {
57+
id = record.ID
58+
content = record.Content
59+
ttl = record.TTL
60+
break
61+
}
62+
}
63+
64+
return id, content, ttl, nil
65+
}
66+
67+
//SetRecord sets record
68+
func SetRecord(email, apikey, zonedID, recordID, recordType, name, content string, ttl int) error {
69+
client := &http.Client{Timeout: time.Second * 2}
70+
71+
data := fmt.Sprintf(`{"type":"%s","name":"%s","content":"%s","ttl":%d}`, recordType, name, content, ttl)
72+
req, err := http.NewRequest("PUT", fmt.Sprintf("%szones/%s/dns_records/%s", baseuri, zonedID, recordID), strings.NewReader(data))
73+
if err != nil {
74+
return errors.Wrap(err, "Error creating PUT request")
75+
}
76+
77+
req.ContentLength = int64(len(data))
78+
req.Header.Set("X-Auth-Email", email)
79+
req.Header.Set("X-Auth-Key", apikey)
80+
req.Header.Set("Content-Type", "application/json")
81+
res, err := client.Do(req)
82+
if err != nil {
83+
return errors.Wrap(err, "Error doing request to Cloudflare")
84+
}
85+
defer res.Body.Close()
86+
87+
body, err := ioutil.ReadAll(res.Body)
88+
if err != nil {
89+
return errors.Wrap(err, "Error reading response body")
90+
}
91+
92+
var resMap map[string]interface{}
93+
json.Unmarshal(body, &resMap)
94+
if !resMap["success"].(bool) {
95+
return fmt.Errorf("Cloudflare Errors: %+v - Cloudflare Messages: %+v", resMap["errors"], resMap["messages"])
96+
}
97+
return nil
98+
}

pkg/ip/ip.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package ip
2+
3+
import (
4+
"strings"
5+
"github.com/pkg/errors"
6+
"io/ioutil"
7+
"net/http"
8+
)
9+
10+
//Get Returns your ip
11+
func Get() (string, error) {
12+
res, err := http.Get("https://checkip.amazonaws.com/")
13+
if err != nil {
14+
return "", errors.Wrap(err, "Error requesting the ip")
15+
}
16+
defer res.Body.Close()
17+
18+
ip, err := ioutil.ReadAll(res.Body)
19+
if err != nil {
20+
return "", errors.Wrap(err, "Error reading the request")
21+
}
22+
23+
return strings.TrimSpace(string(ip)), nil
24+
}

0 commit comments

Comments
 (0)