123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470 |
- package main
- import (
- "encoding/json"
- "errors"
- "fmt"
- "log"
- "math/rand"
- "os"
- "regexp"
- "runtime"
- "sort"
- "strconv"
- "strings"
- "time"
- "github.com/denverdino/aliyungo/common"
- dns "github.com/honwen/aliyun-ddns-cli/alidns"
- "github.com/honwen/golibs/cip"
- "github.com/honwen/golibs/domain"
- "github.com/urfave/cli"
- )
- // AccessKey from https://ak-console.aliyun.com/#/accesskey
- type AccessKey struct {
- ID string
- Secret string
- client *dns.Client
- managedDomains []string
- }
- func (ak *AccessKey) getClient() *dns.Client {
- if len(ak.ID) <= 0 && len(ak.Secret) <= 0 {
- return nil
- }
- if ak.client == nil {
- ak.client = dns.NewClient(ak.ID, ak.Secret)
- ak.client.SetEndpoint(dns.DNSDefaultEndpointNew)
- }
- return ak.client
- }
- func (ak AccessKey) String() string {
- return fmt.Sprintf("Access Key: [ ID: %s ;\t Secret: %s ]", ak.ID, ak.Secret)
- }
- func (ak *AccessKey) ListManagedDomains() (domains []string, err error) {
- var resp []dns.DomainType
- resp, err = ak.getClient().DescribeDomains(
- &dns.DescribeDomainsArgs{
- Pagination: common.Pagination{PageSize: 50},
- })
- if err != nil {
- return
- }
- domains = make([]string, len(resp))
- for i, v := range resp {
- domains[i] = v.DomainName
- }
- return
- }
- func (ak *AccessKey) AutocheckDomainRR(rr, domain string) (r, d string, err error) {
- if contains(ak.managedDomains, domain) {
- return rr, domain, nil
- } else {
- if !strings.Contains(rr, `.`) {
- return "", "", fmt.Errorf("Domain [%s.%s] Not Managed", rr, domain)
- } else {
- rrs := strings.Split(rr, `.`)
- for i := len(rrs) - 1; i > 0; i-- {
- d = strings.Join(append(rrs[i:], domain), `.`)
- if contains(ak.managedDomains, d) {
- r = strings.Join(rrs[:i], `.`)
- return
- }
- }
- }
- }
- return "", "", fmt.Errorf("Domain [%s.%s] Not Managed", rr, domain)
- }
- func (ak *AccessKey) ListRecord(domain string) (dnsRecords []dns.RecordTypeNew, err error) {
- var resp *dns.DescribeDomainRecordsNewResponse
- for idx := 1; idx <= 99; idx++ {
- resp, err = ak.getClient().DescribeDomainRecordsNew(
- &dns.DescribeDomainRecordsNewArgs{
- DomainName: domain,
- Pagination: common.Pagination{PageNumber: idx, PageSize: 50},
- })
- if err != nil {
- return
- }
- dnsRecords = append(dnsRecords, resp.DomainRecords.Record...)
- if len(dnsRecords) >= resp.PaginationResult.TotalCount {
- return
- }
- }
- return
- }
- func (ak *AccessKey) DelRecord(rr, domain string) (err error) {
- var target *dns.RecordTypeNew
- if dnsRecords, err := ak.ListRecord(domain); err == nil {
- for i := range dnsRecords {
- if dnsRecords[i].RR == rr {
- target = &dnsRecords[i]
- _, err = ak.getClient().DeleteDomainRecord(
- &dns.DeleteDomainRecordArgs{
- RecordId: target.RecordId,
- },
- )
- }
- }
- } else {
- return err
- }
- return
- }
- func (ak *AccessKey) UpdateRecord(recordID, rr, dmType, value string, ttl int) (err error) {
- _, err = ak.getClient().UpdateDomainRecord(
- &dns.UpdateDomainRecordArgs{
- RecordId: recordID,
- RR: rr,
- Value: value,
- Type: dmType,
- TTL: json.Number(fmt.Sprint(ttl)),
- })
- return
- }
- func (ak *AccessKey) AddRecord(domain, rr, dmType, value string, ttl int) (err error) {
- _, err = ak.getClient().AddDomainRecord(
- &dns.AddDomainRecordArgs{
- DomainName: domain,
- RR: rr,
- Type: dmType,
- Value: value,
- TTL: json.Number(fmt.Sprint(ttl)),
- })
- return err
- }
- func (ak *AccessKey) CheckAndUpdateRecord(rr, domain, ipaddr, recordType string, ttl int) (err error) {
- fulldomain := strings.Join([]string{rr, domain}, `.`)
- if reslove(fulldomain) == ipaddr {
- return // Skip
- }
- targetCnt := 0
- var target *dns.RecordTypeNew
- if dnsRecords, err := ak.ListRecord(domain); err == nil {
- for i := range dnsRecords {
- if dnsRecords[i].RR == rr && dnsRecords[i].Type == recordType {
- target = &dnsRecords[i]
- targetCnt++
- }
- }
- } else {
- return err
- }
- if targetCnt > 1 {
- ak.DelRecord(rr, domain)
- target = nil
- }
- if target == nil {
- err = ak.AddRecord(domain, rr, recordType, ipaddr, ttl)
- } else if target.Value != ipaddr {
- if target.Type != recordType {
- return fmt.Errorf("record type error! oldType=%s, targetType=%s", target.Type, recordType)
- }
- err = ak.UpdateRecord(target.RecordId, target.RR, target.Type, ipaddr, ttl)
- }
- if err != nil && strings.Contains(err.Error(), `DomainRecordDuplicate`) {
- ak.DelRecord(rr, domain)
- return ak.CheckAndUpdateRecord(rr, domain, ipaddr, recordType, ttl)
- }
- return err
- }
- var (
- accessKey AccessKey
- VersionString = "MISSING build version [git hash]"
- )
- func init() {
- rand.Seed(time.Now().UnixNano())
- }
- func main() {
- app := cli.NewApp()
- app.Name = "aliddns"
- app.Usage = "aliyun-ddns-cli"
- app.Version = fmt.Sprintf("Git:[%s] (%s)", strings.ToUpper(VersionString), runtime.Version())
- app.Commands = []cli.Command{
- {
- Name: "list",
- Category: "DDNS",
- Usage: "List AliYun's DNS DomainRecords Record",
- Flags: []cli.Flag{
- cli.StringFlag{
- Name: "domain, d",
- Usage: "Specific `DomainName`. like aliyun.com",
- },
- },
- Action: func(c *cli.Context) error {
- if err := appInit(c, true); err != nil {
- return err
- }
- // fmt.Println(c.Command.Name, "task: ", accessKey, c.String("domain"))
- domain := c.String("domain")
- if !contains(accessKey.managedDomains, domain) {
- return fmt.Errorf("Domain [%s] Not Managed", domain)
- }
- if dnsRecords, err := accessKey.ListRecord(domain); err != nil {
- fmt.Printf("%+v", err)
- } else {
- for _, v := range dnsRecords {
- fmt.Printf("%20s %-16s %s\n", v.RR+`.`+v.DomainName, fmt.Sprintf("%s(TTL:%4s)", v.Type, v.TTL), v.Value)
- }
- }
- return nil
- },
- },
- {
- Name: "delete",
- Category: "DDNS",
- Usage: "Delete AliYun's DNS DomainRecords Record",
- Flags: []cli.Flag{
- cli.StringFlag{
- Name: "domain, d",
- Usage: "Specific `FullDomainName`. like ddns.aliyun.com",
- },
- },
- Action: func(c *cli.Context) error {
- if err := appInit(c, true); err != nil {
- return err
- }
- // fmt.Println(c.Command.Name, "task: ", accessKey, c.String("domain"))
- rr, domain, err := accessKey.AutocheckDomainRR(domain.SplitDomainToRR(c.String("domain")))
- if err != nil {
- return err
- }
- if err := accessKey.DelRecord(rr, domain); err != nil {
- fmt.Printf("%+v", err)
- } else {
- fmt.Println(c.String("domain"), "Deleted")
- }
- return nil
- },
- },
- {
- Name: "update",
- Category: "DDNS",
- Usage: "Update AliYun's DNS DomainRecords Record, Create Record if not exist",
- Flags: []cli.Flag{
- cli.StringFlag{
- Name: "domain, d",
- Usage: "Specific `DomainName`. like ddns.aliyun.com",
- },
- cli.StringFlag{
- Name: "ipaddr, i",
- Usage: "Specific `IP`. like 1.2.3.4",
- },
- cli.IntFlag{
- Name: "ttl, t",
- Value: 600,
- Usage: "The resolution effective time (in `seconds`)",
- },
- },
- Action: func(c *cli.Context) error {
- if err := appInit(c, true); err != nil {
- return err
- }
- // fmt.Println(c.Command.Name, "task: ", accessKey, c.String("domain"), c.String("ipaddr"))
- rr, domain, err := accessKey.AutocheckDomainRR(domain.SplitDomainToRR(c.String("domain")))
- if err != nil {
- return err
- }
- recordType := "A"
- if c.GlobalBool("ipv6") {
- recordType = "AAAA"
- }
- if err := accessKey.CheckAndUpdateRecord(rr, domain, c.String("ipaddr"), recordType, c.Int("ttl")); err != nil {
- log.Printf("%+v", err)
- } else {
- log.Println(c.String("domain"), c.String("ipaddr"), ip2locCN(c.String("ipaddr")))
- }
- return nil
- },
- },
- {
- Name: "auto-update",
- Category: "DDNS",
- Usage: "Auto-Update AliYun's DNS DomainRecords Record, Get IP using its getip",
- Flags: []cli.Flag{
- cli.StringFlag{
- Name: "domain, d",
- Usage: "Specific `DomainName`. like ddns.aliyun.com",
- },
- cli.StringFlag{
- Name: "redo, r",
- Value: "",
- Usage: "redo Auto-Update, every N `Seconds`; Disable if N less than 10; End with [Rr] enable random delay: [N, 2N]",
- },
- cli.IntFlag{
- Name: "ttl, t",
- Value: 600,
- Usage: "The resolution effective time (in `seconds`)",
- },
- },
- Action: func(c *cli.Context) error {
- if err := appInit(c, true); err != nil {
- return err
- }
- // fmt.Println(c.Command.Name, "task: ", accessKey, c.String("domain"), c.Int64("redo"))
- rr, domain, err := accessKey.AutocheckDomainRR(domain.SplitDomainToRR(c.String("domain")))
- if err != nil {
- return err
- }
- recordType := "A"
- if c.GlobalBool("ipv6") {
- recordType = "AAAA"
- }
- redoDurtionStr := c.String("redo")
- if len(redoDurtionStr) > 0 && !regexp.MustCompile(`\d+[Rr]?$`).MatchString(redoDurtionStr) {
- return errors.New(`redo format: [0-9]+[Rr]?$`)
- }
- randomDelay := regexp.MustCompile(`\d+[Rr]$`).MatchString(redoDurtionStr)
- redoDurtion := 0
- if randomDelay {
- redoDurtion, _ = strconv.Atoi(redoDurtionStr[:len(redoDurtionStr)-1])
- } else {
- redoDurtion, _ = strconv.Atoi(redoDurtionStr)
- }
- // Print Version if exist
- if redoDurtion > 0 && !strings.HasPrefix(VersionString, "MISSING") {
- fmt.Fprintf(os.Stderr, "%s %s\n", strings.ToUpper(c.App.Name), c.App.Version)
- }
- for {
- autoip := myip()
- if len(autoip) == 0 {
- log.Printf("# Err-CheckAndUpdateRecord: [%s]", "IP is empty, PLZ check network")
- } else {
- if err := accessKey.CheckAndUpdateRecord(rr, domain, autoip, recordType, c.Int("ttl")); err != nil {
- log.Printf("# Err-CheckAndUpdateRecord: [%+v]", err)
- } else {
- log.Println(c.String("domain"), autoip, ip2locCN(autoip))
- }
- }
- if redoDurtion < 10 {
- break // Disable if N less than 10
- }
- if randomDelay {
- time.Sleep(time.Duration(redoDurtion+rand.Intn(redoDurtion)) * time.Second)
- } else {
- time.Sleep(time.Duration(redoDurtion) * time.Second)
- }
- }
- return nil
- },
- },
- {
- Name: "getip",
- Category: "GET-IP",
- Usage: fmt.Sprintf(" Get IP Combine 10+ different Web-API"),
- Action: func(c *cli.Context) error {
- if err := appInit(c, false); err != nil {
- return err
- }
- // fmt.Println(c.Command.Name, "task: ", c.Command.Usage)
- ip := myip()
- fmt.Println(ip, ip2locCN(ip))
- return nil
- },
- },
- {
- Name: "resolve",
- Category: "GET-IP",
- Usage: fmt.Sprintf(" Get DNS-IPv4 Combine 4+ DNS Upstream"),
- Flags: []cli.Flag{
- cli.StringFlag{
- Name: "domain, d",
- Usage: "Specific `DomainName`. like ddns.aliyun.com",
- },
- },
- Action: func(c *cli.Context) error {
- if err := appInit(c, false); err != nil {
- return err
- }
- // fmt.Println(c.Command.Name, "task: ", c.Command.Usage)
- ip := reslove(c.String("domain"))
- fmt.Println(ip, ip2locCN(ip))
- return nil
- },
- },
- }
- app.Flags = []cli.Flag{
- cli.StringFlag{
- Name: "access-key-id, id",
- Usage: "AliYun's Access Key ID",
- },
- cli.StringFlag{
- Name: "access-key-secret, secret",
- Usage: "AliYun's Access Key Secret",
- },
- cli.StringSliceFlag{
- Name: "ipapi, api",
- Usage: "Web-API to Get IP, like: http://myip.ipip.net",
- },
- cli.BoolFlag{
- Name: "ipv6, 6",
- Usage: "IPv6",
- },
- }
- app.Action = func(c *cli.Context) error {
- return appInit(c, true)
- }
- app.Run(os.Args)
- }
- func appInit(c *cli.Context, checkAccessKey bool) error {
- akids := []string{c.GlobalString("access-key-id"), os.Getenv("AKID"), os.Getenv("AccessKeyID")}
- akscts := []string{c.GlobalString("access-key-secret"), os.Getenv("AKSCT"), os.Getenv("AccessKeySecret")}
- sort.Sort(sort.Reverse(sort.StringSlice(akids)))
- sort.Sort(sort.Reverse(sort.StringSlice(akscts)))
- accessKey.ID = akids[0]
- accessKey.Secret = akscts[0]
- if checkAccessKey && accessKey.getClient() == nil {
- cli.ShowAppHelp(c)
- return errors.New("access-key is empty")
- }
- if domains, err := accessKey.ListManagedDomains(); err == nil {
- // log.Println(domains)
- accessKey.managedDomains = domains
- } else {
- cli.ShowAppHelp(c)
- return errors.New("No Managed Domains")
- }
- if c.GlobalBool("ipv6") {
- funcs["myip"] = cip.MyIPv6
- funcs["reslove"] = cip.ResloveIPv6
- }
- ipapi := []string{}
- for _, api := range c.GlobalStringSlice("ipapi") {
- if !regexp.MustCompile(`^https?://.*`).MatchString(api) {
- api = "http://" + api
- }
- if regexp.MustCompile(`(https?|ftp|file)://[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]`).MatchString(api) {
- ipapi = append(ipapi, api)
- }
- }
- if len(ipapi) > 0 {
- regx := regexp.MustCompile(cip.RegxIPv4)
- if c.GlobalBoolT("ipv6") {
- regx = regexp.MustCompile(cip.RegxIPv6)
- }
- funcs["myip"] = func() string {
- return cip.FastWGetWithVailder(ipapi, func(s string) string {
- return regx.FindString((s))
- })
- }
- }
- return nil
- }
|