cloudflare-dns-cli/main.go

219 lines
4.5 KiB
Go
Raw Permalink Normal View History

2023-03-20 01:01:44 -04:00
package main
import (
"context"
"encoding/json"
"flag"
"fmt"
2023-03-23 15:30:55 -04:00
"log"
2023-03-20 01:01:44 -04:00
"net"
"os"
2023-03-23 18:57:30 -04:00
"sort"
"strings"
2023-03-20 01:01:44 -04:00
"github.com/libdns/cloudflare"
"github.com/libdns/libdns"
)
var (
domain = os.Getenv("DOMAIN")
token = os.Getenv("CF_TOKEN")
2023-03-23 18:57:30 -04:00
ip = net.IPv4(127, 0, 0, 1)
cname string
sub = "<UNSET>"
path string
del = false
2023-09-21 17:07:58 -04:00
mx = false
2023-03-20 01:01:44 -04:00
)
2023-03-23 18:57:30 -04:00
func genRecord() (libdns.Record, error) {
2023-04-10 14:16:23 -04:00
ipv4 := ip.To4()
ipv6 := ip.To16()
2023-03-23 18:57:30 -04:00
switch {
case cname != "":
2023-09-21 17:07:58 -04:00
if mx {
return libdns.Record{
2024-04-22 23:13:42 -04:00
Type: "MX",
Name: sub,
Value: cname,
Priority: 10,
2023-09-21 17:07:58 -04:00
}, nil
} else {
return libdns.Record{
Type: "CNAME",
Name: sub,
Value: cname,
}, nil
}
2023-04-10 14:16:23 -04:00
case ipv4 != nil:
2023-03-23 18:57:30 -04:00
return libdns.Record{
Type: "A",
Name: sub,
2023-04-10 14:16:23 -04:00
Value: ipv4.String(),
2023-03-23 18:57:30 -04:00
}, nil
2023-04-10 14:16:23 -04:00
case ipv6 != nil:
2023-03-23 18:57:30 -04:00
return libdns.Record{
Type: "AAAA",
Name: sub,
2023-04-10 14:16:23 -04:00
Value: ipv6.String(),
2023-03-23 18:57:30 -04:00
}, nil
}
return libdns.Record{}, fmt.Errorf("neither cname nor valid ip is set")
}
func selectRecordsToDelete(recs []libdns.Record) []libdns.Record {
name := func() string {
if sub == "@" {
2024-03-31 12:45:54 -04:00
return ""
2023-03-23 18:57:30 -04:00
}
2024-03-31 12:45:54 -04:00
return sub
2023-03-23 18:57:30 -04:00
}()
recs = func() (result []libdns.Record) {
for _, r := range recs {
if r.Name == name {
result = append(result, r)
}
}
return
}()
2024-01-18 16:18:27 -05:00
if len(recs) == 0 {
fmt.Printf("No Records match %s\n", name)
return nil
}
fmt.Printf("Records matching %s\n\n", name)
2023-03-23 18:57:30 -04:00
for i, r := range recs {
fmt.Printf("%5d %6s %s\n", i, r.Type, r.Value)
}
var delRange string
2024-01-18 16:18:27 -05:00
fmt.Print("\nEnter record indexes seperated by comma(,) to delete. Use hyphen(-) for a closed range. Also can be all or none\nIndexes: ")
2023-03-23 18:57:30 -04:00
if _, err := fmt.Scanln(&delRange); err != nil {
log.Panicln(err)
}
switch {
case delRange == "all":
return recs
case delRange == "none":
return nil
default:
indexes := parseRange(delRange, len(recs)-1)
2023-03-23 18:57:30 -04:00
return func() (result []libdns.Record) {
for _, index := range indexes {
result = append(result, recs[index])
2023-03-23 18:57:30 -04:00
}
return
}()
}
}
2023-03-20 01:01:44 -04:00
func main() {
ctx := context.TODO()
2023-04-10 12:17:42 -04:00
flag.StringVar(&domain, "d", domain, "Domain name, e.g. example.com. env var: DOMAIN")
2023-03-23 18:57:30 -04:00
flag.StringVar(&sub, "s", sub, "Subdomain, e.g. blog. Use @ for root")
2023-04-10 12:17:42 -04:00
flag.StringVar(&token, "a", token, "Cloudflare API Token. env var: CF_TOKEN")
2023-03-20 01:01:44 -04:00
flag.TextVar(&ip, "i", ip, "IP address")
flag.StringVar(&cname, "c", cname, "CNAME target")
2024-03-31 12:06:21 -04:00
flag.StringVar(&path, "o", path, "Path to save all records as json, e.g. ./records.json, '-' for stdout")
2023-03-23 18:57:30 -04:00
flag.BoolVar(&del, "x", del, "Delete records of subdomain")
2023-09-21 17:07:58 -04:00
flag.BoolVar(&mx, "m", mx, "Set mx record with cname value")
2023-03-20 01:01:44 -04:00
flag.Parse()
if token == "" {
flag.Usage()
log.Panicln("empty cloudflare api token")
}
if domain == "" {
flag.Usage()
log.Panicln("empty domain name")
}
2023-03-20 01:01:44 -04:00
provider := cloudflare.Provider{APIToken: token}
zone := domain + "."
if sub != "<UNSET>" {
2023-03-23 18:57:30 -04:00
if del {
recs, err := provider.GetRecords(ctx, zone)
if err != nil {
log.Panicln(err)
}
recs = selectRecordsToDelete(recs)
deletedRecords, err := provider.DeleteRecords(ctx, zone, recs)
2023-03-23 18:57:30 -04:00
if err != nil {
log.Panicln(err)
}
if len(deletedRecords) == 0 {
2023-03-23 18:57:30 -04:00
fmt.Println("Nothing was deleted")
} else {
fmt.Println("Records deleted:")
for i, r := range recs {
fmt.Printf("%5d %6s %s\n", i, r.Type, r.Value)
}
}
} else {
record, err := genRecord()
if err != nil {
log.Panicln(err)
}
log.Printf("setting record, %+v", record)
newRecs, err := provider.SetRecords(ctx, zone, []libdns.Record{record})
if err != nil {
log.Panicln(err)
}
fmt.Println(newRecs)
2023-03-20 01:01:44 -04:00
}
}
if path != "" {
recs, err := provider.GetRecords(ctx, zone)
if err != nil {
2023-03-23 18:57:30 -04:00
log.Panicln(err)
2023-03-20 01:01:44 -04:00
}
2024-03-31 12:06:21 -04:00
data, err := json.Marshal(recs)
2023-03-20 01:01:44 -04:00
if err != nil {
2023-03-23 18:57:30 -04:00
log.Panicln(err)
2023-03-20 01:01:44 -04:00
}
2024-03-31 12:06:21 -04:00
if path == "-" {
if _, err = os.Stdout.Write(data); err != nil {
log.Panicln(err)
}
} else {
if err = os.WriteFile(path, data, 0644); err != nil {
log.Panicln(err)
}
2023-03-23 18:57:30 -04:00
}
}
}
func parseRange(rs string, max int) []int {
var indexes []int
m := uint(max)
for _, s := range strings.Split(rs, ",") {
s = strings.TrimSpace(s)
var i, a, b uint
if _, err := fmt.Sscanf(s, "%v-%v", &a, &b); err == nil {
for s := a; s <= b && s <= m; {
indexes = append(indexes, int(s))
s++
}
} else {
if _, err := fmt.Sscanf(s, "%v", &i); err == nil && i <= m {
indexes = append(indexes, int(i))
}
}
}
sort.Ints(indexes)
var uniq []int
last := -1
for _, i := range indexes {
if i != last {
uniq = append(uniq, i)
last = i
2023-03-20 01:01:44 -04:00
}
}
2023-03-23 18:57:30 -04:00
return uniq
2023-03-20 01:01:44 -04:00
}