-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Showing
21 changed files
with
1,151 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
# Binaries for programs and plugins | ||
*.exe | ||
*.exe~ | ||
*.dll | ||
*.so | ||
*.dylib | ||
|
||
# Test binary, built with `go test -c` | ||
*.test | ||
|
||
# Output of the go coverage tool, specifically when used with LiteIDE | ||
*.out | ||
|
||
# Dependency directories (remove the comment below to include it) | ||
# vendor/ | ||
|
||
# old tokens of cf | ||
secret.txt | ||
dist/ | ||
cfagent | ||
\.idea |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
# This is an example .goreleaser.yml file with some sensible defaults. | ||
# Make sure to check the documentation at https://goreleaser.com | ||
project_name: cfagent | ||
before: | ||
hooks: | ||
# You may remove this if you don't use go modules. | ||
- go mod tidy | ||
# you may remove this if you don't need go generate | ||
- go generate ./... | ||
builds: | ||
- binary: "bin/cfagent" | ||
# Custom flags templates. | ||
- flags: | ||
- -v | ||
- -trimpath | ||
- asmflags: | ||
- all=-trimpath={{.Env.GOPATH}} | ||
- gcflags: | ||
- all=-trimpath={{.Env.GOPATH}} | ||
# Default is `-s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}} -X main.builtBy=goreleaser`. | ||
- ldflags: | ||
- -s -extldflags=-static -w -X main.build={{.Version}} | ||
- tags: | ||
- osusergo | ||
- netgo | ||
- env: | ||
- CGO_ENABLED=0 | ||
- goos: | ||
- linux | ||
- freebsd | ||
- windows | ||
- darwin | ||
- goarch: | ||
- amd64 | ||
- arm | ||
- arm64 | ||
- 386 | ||
- goarm: | ||
- 6 | ||
- 7 | ||
- ignore: | ||
- goos: windows | ||
goarch: arm64 | ||
archives: | ||
- replacements: | ||
darwin: Darwin | ||
linux: Linux | ||
windows: Windows | ||
386: i386 | ||
amd64: x86_64 | ||
checksum: | ||
name_template: 'checksums.txt' | ||
snapshot: | ||
name_template: "{{ incpatch .Version }}-next" | ||
changelog: | ||
sort: asc | ||
filters: | ||
exclude: | ||
- '^docs:' | ||
- '^test:' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
# Cloudflare DDNS Agent | ||
|
||
> This is an unofficial development. Cloudflare has not official support for it. | ||
## Goal | ||
|
||
It updates your device (raspberr pi, beaglebone, linux, container, vm, etc) IP address on a Dynamic | ||
DNS provider like Cloudflare so that you can forget about device IP and use it a DNS name. | ||
|
||
## Requirements | ||
|
||
* A owned DNS. | ||
* A Cloudflare account. | ||
* A Token created using Cloudflare dashboard to allow this application to edit your DNS Zone info. | ||
|
||
## Usage | ||
|
||
```bash | ||
X_CF_AGENT_TOKEN=yourtoken \ | ||
X_CF_AGENT_ZONE=example.com \ | ||
X_CF_AGENT_DNS_A_RECORD=rpi \ | ||
cfagent update | ||
``` | ||
|
||
Previous command will try to update the DNS A Record of rpi.example.com with the host actual public IP | ||
|
||
### Customization of DDNS Cloudflare Agent | ||
|
||
* X_CF_AGENT_TOKEN: token provided by Cloudflare in order to authenticate API calls | ||
* X_CF_AGENT_ZONE: name of the DNS zone you want to edit | ||
* X_CF_AGENT_DNS_A_RECORD: name of the DNS Record name you want to edit. | ||
|
||
## Troubleshooting | ||
|
||
### API Token must not be empty | ||
|
||
```bash | ||
2021/10/20 20:27:09 Updating device IP. Please wait... | ||
2021/10/20 20:27:09 Requesting IP check for: . | ||
2021/10/20 20:27:09 Reading current device IP. Please wait... | ||
2021/10/20 20:27:09 Readed IP: X.Y.Z.A | ||
2021/10/20 20:27:09 Connecting with Cloudflare services... | ||
2021/10/20 20:27:09 cloudflare ddns updater took 101.71621ms | ||
invalid credentials: API Token must not be empty | ||
``` | ||
|
||
Make sure you have successfully set the `X_CF_AGENT_TOKEN` environment variable with a valid Cloudflare token. | ||
|
||
### | ||
|
||
```bash | ||
2021/10/20 20:28:56 Updating device IP. Please wait... | ||
2021/10/20 20:28:56 Requesting IP check for: example.com.rpi | ||
2021/10/20 20:28:56 Reading current device IP. Please wait... | ||
2021/10/20 20:28:57 Readed IP: X.Y.Z.A | ||
2021/10/20 20:28:57 Connecting with Cloudflare services... | ||
2021/10/20 20:28:58 cloudflare ddns updater took 1.070368081s | ||
|
||
ListZonesContext command failed: HTTP status 400: Invalid request headers (6003) | ||
``` | ||
Make sure you have successfully set the `X_CF_AGENT_TOKEN` and the token has the ability to manage your DNS Zone data. | ||
If you set the wrong scope of the token, you have to create a new one. | ||
|
||
## License | ||
|
||
MIT |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package api | ||
|
||
import ( | ||
"log" | ||
"time" | ||
|
||
"github.com/zerjioang/cf-agent/datatypes" | ||
) | ||
|
||
// Start executes the IP update method | ||
func Start() error { | ||
defer measureTime(time.Now(), "cloudflare ddns updater") | ||
log.Println("Updating device IP. Please wait...") | ||
p := datatypes.NewPayload() | ||
log.Println("Requesting IP check for: ", p.Zone+"."+p.DNSRecord) | ||
log.Println("Reading current device IP. Please wait...") | ||
currIp := externalIP() | ||
log.Println("Readed IP: ", currIp) | ||
log.Println("Connecting with Cloudflare services...") | ||
return triggerDDNSUpdate(p.Token, p.Zone, "console", currIp) | ||
} | ||
|
||
func measureTime(start time.Time, name string) { | ||
elapsed := time.Since(start) | ||
log.Printf("%s took %s", name, elapsed) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package api | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestStart(t *testing.T) { | ||
t.Run("example", func(t *testing.T) { | ||
assert.NoError(t, Start()) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
package api | ||
|
||
import ( | ||
"context" | ||
"log" | ||
|
||
"github.com/cloudflare/cloudflare-go" | ||
"github.com/pkg/errors" | ||
) | ||
|
||
var ( | ||
// Most API calls require a Context | ||
ctx = context.Background() | ||
) | ||
|
||
// triggerDDNSUpdate returns CLoudFLare zone identifier for given zone name | ||
func triggerDDNSUpdate(token string, zone string, aName string, currIp string) error { | ||
// Construct a new API object | ||
api, err := cloudflare.NewWithAPIToken(token) | ||
if err != nil { | ||
return err | ||
} | ||
// Fetch the zone ID | ||
// Assuming example.com exists in your Cloudflare account already | ||
zoneId, err := api.ZoneIDByName(zone) | ||
if err != nil { | ||
return err | ||
} | ||
log.Println("Cloudflare ZONE ID: ", zoneId) | ||
rr, err := api.DNSRecords(ctx, zoneId, cloudflare.DNSRecord{ | ||
Name: aName + "." + zone, | ||
Type: "A", | ||
}) | ||
if err != nil { | ||
return err | ||
} | ||
log.Println("Cloudflare DNS RECORD found: ", len(rr), "records") | ||
if len(rr) == 0 { | ||
return errors.New("Could not get a valid DNS A RECORD for given configuration") | ||
} | ||
dnsa := rr[0] | ||
if currIp != dnsa.Content { | ||
log.Println("Last registered IP found with value : ", dnsa.Content) | ||
log.Println("New device IP value will be set to : ", currIp) | ||
dnsa.Content = currIp | ||
updateErr := api.UpdateDNSRecord(ctx, zoneId, dnsa.ID, dnsa) | ||
if updateErr != nil { | ||
log.Println("failed to update DNS A record due to: ", err) | ||
return err | ||
} | ||
log.Println("Update successful") | ||
return nil | ||
} | ||
log.Println("No need to update DNS A Record") | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
package api | ||
|
||
import ( | ||
"encoding/json" | ||
"io/ioutil" | ||
"net" | ||
"net/http" | ||
"strings" | ||
) | ||
|
||
// OutboundIP returns external IP of current device | ||
// | ||
// NOTE: Unfortunately, this will only work on networks | ||
// that don't employ the use of NAT. | ||
func OutboundIP() string { | ||
conn, err := net.Dial("udp", "8.8.8.8:80") | ||
if err != nil { | ||
return "" | ||
} | ||
defer conn.Close() | ||
localAddr := conn.LocalAddr().String() | ||
idx := strings.LastIndex(localAddr, ":") | ||
return localAddr[0:idx] | ||
} | ||
|
||
// externalIP returns external IP of current device | ||
// | ||
// NOTE: Unfortunately, this will only work on networks | ||
// that don't employ the use of NAT. | ||
func externalIP() string { | ||
req, err := http.Get("http://ip-api.com/json/") | ||
if err != nil { | ||
return err.Error() | ||
} | ||
defer req.Body.Close() | ||
body, err := ioutil.ReadAll(req.Body) | ||
if err != nil { | ||
return err.Error() | ||
} | ||
type IP struct { | ||
Query string | ||
} | ||
var ip IP | ||
_ = json.Unmarshal(body, &ip) | ||
return ip.Query | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
package api | ||
|
||
import ( | ||
"log" | ||
"net" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestIp(t *testing.T) { | ||
t.Run("external", func(t *testing.T) { | ||
external := externalIP() | ||
log.Println("External IP is", external) | ||
assert.NotNil(t, external) | ||
assert.NotEmpty(t, external) | ||
ip := net.ParseIP(external) | ||
assert.NotNil(t, ip) | ||
assert.NotEmpty(t, ip) | ||
}) | ||
t.Run("outbound", func(t *testing.T) { | ||
external := OutboundIP() | ||
log.Println("Outbound IP is", external) | ||
assert.NotNil(t, external) | ||
assert.NotEmpty(t, external) | ||
ip := net.ParseIP(external) | ||
assert.NotNil(t, ip) | ||
assert.NotEmpty(t, ip) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
package cmd | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
|
||
"github.com/spf13/cobra" | ||
"github.com/zerjioang/cf-agent/api" | ||
) | ||
|
||
var rootCmd = &cobra.Command{ | ||
Use: "cfagent", | ||
Short: "CloudFlare DDNS Agent", | ||
Long: `CloudFlare DDNS Agent`, | ||
Run: func(cmd *cobra.Command, args []string) { | ||
if err := cmd.Help(); err != nil { | ||
_, _ = fmt.Fprintln(os.Stderr, err) | ||
os.Exit(1) | ||
} | ||
}, | ||
} | ||
|
||
var startCmd = &cobra.Command{ | ||
Use: "update", | ||
Short: "start service", | ||
Long: `start service`, | ||
Run: func(cmd *cobra.Command, args []string) { | ||
if err := api.Start(); err != nil { | ||
_, _ = fmt.Fprintln(os.Stderr, err) | ||
os.Exit(1) | ||
} | ||
}, | ||
} | ||
|
||
func init() { | ||
rootCmd.AddCommand(startCmd) | ||
} | ||
func Execute() error { | ||
return rootCmd.Execute() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package datatypes | ||
|
||
// ErrorResponse Cloudflare API error response message format | ||
type ErrorResponse struct { | ||
Success bool `json:"success"` | ||
Errors []struct { | ||
Code int `json:"code"` | ||
Message string `json:"message"` | ||
} `json:"errors"` | ||
Messages []interface{} `json:"messages"` | ||
Result interface{} `json:"result"` | ||
} |
Oops, something went wrong.