package main import ( "context" "encoding/json" "flag" "fmt" "log" "net" "os" "sort" "strings" "github.com/libdns/cloudflare" "github.com/libdns/libdns" ) var ( domain = os.Getenv("DOMAIN") token = os.Getenv("CF_TOKEN") ip = net.IPv4(127, 0, 0, 1) cname string sub = "" path string del = false ) func genRecord() (libdns.Record, error) { ipv4 := ip.To4() ipv6 := ip.To16() switch { case cname != "": return libdns.Record{ Type: "CNAME", Name: sub, Value: cname, }, nil case ipv4 != nil: return libdns.Record{ Type: "A", Name: sub, Value: ipv4.String(), }, nil case ipv6 != nil: return libdns.Record{ Type: "AAAA", Name: sub, Value: ipv6.String(), }, 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 == "@" { return domain } return fmt.Sprintf("%s.%s", sub, domain) }() recs = func() (result []libdns.Record) { for _, r := range recs { if r.Name == name { result = append(result, r) } } return }() fmt.Printf("Records matching %s\n", name) for i, r := range recs { fmt.Printf("%5d %6s %s\n", i, r.Type, r.Value) } var delRange string fmt.Print("Enter record indexes seperated by comma(,) to delete. Use hyphen(-) for a closed range. Also can be all or none\nIndexes: ") 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) return func() (result []libdns.Record) { for _, index := range indexes { result = append(result, recs[index]) } return }() } } func main() { ctx := context.TODO() flag.StringVar(&domain, "d", domain, "Domain name, e.g. example.com. env var: DOMAIN") flag.StringVar(&sub, "s", sub, "Subdomain, e.g. blog. Use @ for root") flag.StringVar(&token, "a", token, "Cloudflare API Token. env var: CF_TOKEN") flag.TextVar(&ip, "i", ip, "IP address") flag.StringVar(&cname, "c", cname, "CNAME target") flag.StringVar(&path, "o", path, "Path to save all records as json, e.g. ./records.json") flag.BoolVar(&del, "x", del, "Delete records of subdomain") flag.Parse() provider := cloudflare.Provider{APIToken: token} zone := domain + "." if sub != "" { if del { recs, err := provider.GetRecords(ctx, zone) if err != nil { log.Panicln(err) } recs = selectRecordsToDelete(recs) deletedRecords, err := provider.DeleteRecords(ctx, zone, recs) if err != nil { log.Panicln(err) } if len(deletedRecords) == 0 { 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) } } if path != "" { recs, err := provider.GetRecords(ctx, zone) if err != nil { log.Panicln(err) } data, err := json.Marshal(recs) if err != nil { log.Panicln(err) } err = os.WriteFile(path, data, 0644) if err != nil { log.Panicln(err) } } } 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 } } return uniq }