├── .dockerignore ├── .github └── workflows │ └── build.yaml ├── .gitignore ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── alidns ├── AddDomain.go ├── AddDomainGroup.go ├── AddDomainRecord.go ├── ChangeDomainGroup.go ├── DeleteDomain.go ├── DeleteDomainGroup.go ├── DeleteDomainRecord.go ├── DeleteSubDomainRecords.go ├── DescribeDomainGroups.go ├── DescribeDomainInfo.go ├── DescribeDomainRecordInfo.go ├── DescribeDomainRecordInfoNew.go ├── DescribeDomainRecords.go ├── DescribeDomainRecordsNew.go ├── DescribeDomains.go ├── DescribeSubDomainRecords.go ├── GetMainDomainName.go ├── UpdateDomainGroup.go ├── UpdateDomainRecord.go ├── client.go └── record.go ├── build-release.sh ├── example └── Synology_Docker.png ├── go.mod ├── go.sum ├── main.go ├── utils.go └── utils_test.go /.dockerignore: -------------------------------------------------------------------------------- 1 | * 2 | .* 3 | -------------------------------------------------------------------------------- /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | "on": 4 | "push": 5 | "tags": 6 | - "v*" 7 | "branches": 8 | - "*" 9 | "pull_request": 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | env: 15 | GO111MODULE: "on" 16 | steps: 17 | - uses: actions/checkout@v3 18 | 19 | - uses: actions/setup-go@v3 20 | with: 21 | go-version: '1.24.2' 22 | 23 | - name: Prepare environment 24 | run: |- 25 | RELEASE_VERSION="${GITHUB_REF##*/}" 26 | if [[ "${RELEASE_VERSION}" != v* ]]; then RELEASE_VERSION='dev'; fi 27 | echo "RELEASE_VERSION=\"${RELEASE_VERSION}@${GITHUB_SHA:0:10}\"" | tee -a $GITHUB_ENV 28 | go mod vendor 29 | 30 | # Test 31 | - name: Unit Testing 32 | run: go test -v . 33 | 34 | # Win 35 | - run: GOOS=windows GOARCH=386 VERSION=${RELEASE_VERSION} make release 36 | - run: GOOS=windows GOARCH=amd64 VERSION=${RELEASE_VERSION} make release 37 | - run: GOOS=windows GOARCH=amd64 GOAMD64=v3 VERSION=${RELEASE_VERSION} make release 38 | - run: GOOS=windows GOARCH=arm64 VERSION=${RELEASE_VERSION} make release 39 | 40 | # MacOS 41 | - run: GOOS=darwin GOARCH=amd64 VERSION=${RELEASE_VERSION} make release 42 | - run: GOOS=darwin GOARCH=arm64 VERSION=${RELEASE_VERSION} make release 43 | 44 | # Linux X86/AMD64 45 | - run: GOOS=linux GOARCH=386 VERSION=${RELEASE_VERSION} make release 46 | - run: GOOS=linux GOARCH=amd64 VERSION=${RELEASE_VERSION} make release 47 | - run: GOOS=linux GOARCH=amd64 GOAMD64=v3 VERSION=${RELEASE_VERSION} make release 48 | 49 | # Linux ARM 50 | - run: GOOS=linux GOARCH=arm GOARM=5 VERSION=${RELEASE_VERSION} make release 51 | - run: GOOS=linux GOARCH=arm GOARM=6 VERSION=${RELEASE_VERSION} make release 52 | - run: GOOS=linux GOARCH=arm64 VERSION=${RELEASE_VERSION} make release 53 | 54 | # Linux MIPS/MIPSLE 55 | - run: GOOS=linux GOARCH=mips GOMIPS=softfloat VERSION=${RELEASE_VERSION} make release 56 | - run: GOOS=linux GOARCH=mipsle GOMIPS=softfloat VERSION=${RELEASE_VERSION} make release 57 | 58 | # FreeBSD X86 59 | - run: GOOS=freebsd GOARCH=386 VERSION=${RELEASE_VERSION} make release 60 | - run: GOOS=freebsd GOARCH=amd64 VERSION=${RELEASE_VERSION} make release 61 | 62 | # FreeBSD ARM/ARM64 63 | - run: GOOS=freebsd GOARCH=arm GOARM=6 VERSION=${RELEASE_VERSION} make release 64 | - run: GOOS=freebsd GOARCH=arm64 VERSION=${RELEASE_VERSION} make release 65 | 66 | - run: ls -l build/aliddns-* 67 | 68 | - name: Create release 69 | if: startsWith(github.ref, 'refs/tags/v') 70 | id: create_release 71 | uses: actions/create-release@v1 72 | env: 73 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 74 | with: 75 | tag_name: ${{ github.ref }} 76 | release_name: Release ${{ github.ref }} 77 | draft: false 78 | prerelease: false 79 | 80 | - name: Upload 81 | if: startsWith(github.ref, 'refs/tags/v') 82 | uses: xresloader/upload-to-github-release@v1 83 | env: 84 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 85 | with: 86 | file: "build/aliddns-*.tar.gz;build/aliddns-*.zip" 87 | tags: true 88 | draft: false 89 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | .vscode/ 10 | release/ 11 | vendor/ 12 | 13 | # Architecture specific extensions/prefixes 14 | *.[568vq] 15 | [568vq].out 16 | 17 | *.cgo1.go 18 | *.cgo2.c 19 | _cgo_defun.c 20 | _cgo_gotypes.go 21 | _cgo_export.* 22 | 23 | _testmain.go 24 | 25 | *.exe 26 | *.test 27 | *.prof 28 | *.upx 29 | 30 | .version 31 | build/ 32 | debug 33 | aliyun-ddns-cli 34 | aliddns* 35 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM chenhw2/alpine:base 2 | LABEL MAINTAINER honwen 3 | 4 | # /usr/bin/aliyun-ddns-cli 5 | RUN mkdir -p /usr/bin/ \ 6 | && cd /usr/bin/ \ 7 | && curl -skSL $( \ 8 | curl -skSL 'https://api.github.com/repos/honwen/aliyun-ddns-cli/releases/latest' | \ 9 | sed -n '/url.*linux-amd64/{s/.*\(https:.*.gz\).*/\1/p}'\ 10 | ) | tar --strip-components=1 -zx linux-amd64/aliddns \ 11 | && ln -sf aliddns aliyun-ddns-cli \ 12 | && aliyun-ddns-cli -v 13 | 14 | ENV AKID=1234567890 \ 15 | AKSCT=abcdefghijklmn \ 16 | DOMAIN=ddns.example.win \ 17 | IPAPI=[IPAPI-GROUP] \ 18 | REDO=555r \ 19 | TTL=600 20 | 21 | CMD aliyun-ddns-cli \ 22 | --ipapi ${IPAPI} \ 23 | ${IPV6:+-6} \ 24 | auto-update \ 25 | --domain ${DOMAIN} \ 26 | --redo ${REDO} \ 27 | --ttl ${TTL} 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016-2024 honwen 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. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | NAME=aliddns 2 | BASE_BUILDDIR=build 3 | BUILDNAME=$(GOOS)-$(GOARCH)$(GOAMD64)$(GOARM) 4 | BUILDDIR=$(BASE_BUILDDIR)/$(BUILDNAME) 5 | VERSION?=dev 6 | 7 | ifeq ($(GOOS),windows) 8 | ext=.exe 9 | archiveCmd=zip -9 -r $(NAME)-$(BUILDNAME)-$(VERSION).zip $(BUILDNAME) 10 | else 11 | ext= 12 | archiveCmd=tar czpvf $(NAME)-$(BUILDNAME)-$(VERSION).tar.gz $(BUILDNAME) 13 | endif 14 | 15 | .PHONY: default 16 | default: build 17 | 18 | build: clean test 19 | go build -mod=vendor 20 | 21 | release: check-env-release 22 | mkdir -p $(BUILDDIR) 23 | cp LICENSE $(BUILDDIR)/ 24 | cp README.md $(BUILDDIR)/ 25 | CGO_ENABLED=0 GOOS=$(GOOS) GOARCH=$(GOARCH) go build -mod=vendor -ldflags "-s -w -X main.VersionString=$(VERSION)" -o $(BUILDDIR)/$(NAME)$(ext) 26 | cd $(BASE_BUILDDIR) ; $(archiveCmd) 27 | 28 | test: 29 | go test -race -v -bench=. ./... 30 | 31 | clean: 32 | go clean 33 | rm -rf $(BASE_BUILDDIR) 34 | 35 | check-env-release: 36 | @ if [ "$(GOOS)" = "" ]; then \ 37 | echo "Environment variable GOOS not set"; \ 38 | exit 1; \ 39 | fi 40 | @ if [ "$(GOARCH)" = "" ]; then \ 41 | echo "Environment variable GOOS not set"; \ 42 | exit 1; \ 43 | fi 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### Source 2 | 3 | - https://github.com/honwen/aliyun-ddns-cli 4 | 5 | ### Thanks (package alidns) 6 | 7 | - https://github.com/denverdino/aliyungo 8 | 9 | ### Docker 10 | 11 | - https://hub.docker.com/r/chenhw2/aliyun-ddns-cli/ 12 | 13 | ### Usage 14 | 15 | ```shell 16 | $ docker pull chenhw2/aliyun-ddns-cli 17 | 18 | $ docker run -d \ 19 | -e "AKID=[ALIYUN's AccessKey-ID]" \ 20 | -e "AKSCT=[ALIYUN's AccessKey-Secret]" \ 21 | -e "DOMAIN=ddns.aliyun.win" \ 22 | -e "REDO=600" \ 23 | -e "TTL=600" \ 24 | chenhw2/aliyun-ddns-cli 25 | ``` 26 | 27 | ### Example (for Synology) 28 | 29 | - https://github.com/honwen/aliyun-ddns-cli/tree/master/example 30 | 31 | ### Help 32 | 33 | ```shell 34 | $ docker run --rm chenhw2/aliyun-ddns-cli -h 35 | NAME: 36 | aliddns - aliyun-ddns-cli 37 | 38 | USAGE: 39 | aliyun-ddns-cli [global options] command [command options] [arguments...] 40 | 41 | VERSION: 42 | Git:[MISSING BUILD VERSION [GIT HASH]] (go1.21) 43 | 44 | COMMANDS: 45 | help, h Shows a list of commands or help for one command 46 | 47 | DDNS: 48 | list List AliYun's DNS DomainRecords Record 49 | delete Delete AliYun's DNS DomainRecords Record 50 | update Update AliYun's DNS DomainRecords Record, Create Record if not exist 51 | auto-update Auto-Update AliYun's DNS DomainRecords Record, Get IP using its getip 52 | 53 | GET-IP: 54 | getip Get IP Combine 10+ different Web-API 55 | resolve Get DNS-IPv4 Combine 4+ DNS Upstream 56 | 57 | GLOBAL OPTIONS: 58 | --access-key-id value, --id value AliYun's Access Key ID 59 | --access-key-secret value, --secret value AliYun's Access Key Secret 60 | --ipapi value, --api value Web-API to Get IP, like: http://v6r.ipip.net 61 | --ipv6, -6 IPv6 62 | --help, -h show help 63 | --version, -v print the version 64 | ``` 65 | 66 | ### CLI Example: 67 | 68 | ```shell 69 | aliddns --id ${AccessKeyID} --secret ${AccessKeySecret} \ 70 | auto-update --domain ddns.example.win 71 | 72 | aliddns --id ${AccessKeyID} --secret ${AccessKeySecret} \ 73 | update --domain ddns.example.win \ 74 | --ipaddr $(ifconfig pppoe-wan | sed -n '2{s/[^0-9]*://;s/[^0-9.].*//p}') 75 | ``` 76 | -------------------------------------------------------------------------------- /alidns/AddDomain.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/denverdino/aliyungo/common" 7 | ) 8 | 9 | type AddDomainArgs struct { 10 | DomainName string 11 | 12 | // optional 13 | GroupId string 14 | } 15 | 16 | type AddDomainResponse struct { 17 | common.Response 18 | DomainId string 19 | DomainName string 20 | GroupId string 21 | GroupName string 22 | PunyCode string 23 | DnsServers struct { 24 | DnsServer []string 25 | } 26 | } 27 | 28 | // AddDomain 29 | // 30 | // You can read doc at https://help.aliyun.com/document_detail/29749.html?spm=5176.doc29805.6.592.6LMqlG 31 | func (client *Client) AddDomain(args *AddDomainArgs) (response *AddDomainResponse, err error) { 32 | action := "AddDomain" 33 | response = &AddDomainResponse{} 34 | err = client.Invoke(action, args, response) 35 | if err == nil { 36 | return response, nil 37 | } else { 38 | log.Printf("%s error, %v", action, err) 39 | return response, err 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /alidns/AddDomainGroup.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/denverdino/aliyungo/common" 7 | ) 8 | 9 | type AddDomainGroupArgs struct { 10 | GroupName string 11 | } 12 | 13 | type AddDomainGroupResponse struct { 14 | common.Response 15 | GroupId string 16 | GroupName string 17 | } 18 | 19 | // AddDomainGroup 20 | // 21 | // You can read doc at https://help.aliyun.com/document_detail/29762.html?spm=5176.doc29749.6.604.PJtwG1 22 | func (client *Client) AddDomainGroup(args *AddDomainGroupArgs) (response *AddDomainGroupResponse, err error) { 23 | action := "AddDomainGroup" 24 | response = &AddDomainGroupResponse{} 25 | err = client.Invoke(action, args, response) 26 | if err == nil { 27 | return response, nil 28 | } else { 29 | log.Printf("%s error, %v", action, err) 30 | return response, err 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /alidns/AddDomainRecord.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "encoding/json" 5 | "log" 6 | 7 | "github.com/denverdino/aliyungo/common" 8 | ) 9 | 10 | type AddDomainRecordArgs struct { 11 | DomainName string 12 | RR string 13 | Type string 14 | Value string 15 | 16 | // optional 17 | TTL json.Number 18 | Line string 19 | } 20 | 21 | type AddDomainRecordResponse struct { 22 | common.Response 23 | InstanceId string 24 | RecordId string 25 | } 26 | 27 | // AddDomainRecord 28 | // 29 | // You can read doc at https://docs.aliyun.com/#/pub/dns/api-reference/record-related&AddDomainRecord 30 | func (client *Client) AddDomainRecord(args *AddDomainRecordArgs) (response *AddDomainRecordResponse, err error) { 31 | action := "AddDomainRecord" 32 | response = &AddDomainRecordResponse{} 33 | err = client.Invoke(action, args, response) 34 | if err == nil { 35 | return response, nil 36 | } else { 37 | log.Printf("%s error, %v", action, err) 38 | return response, err 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /alidns/ChangeDomainGroup.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/denverdino/aliyungo/common" 7 | ) 8 | 9 | type ChangeDomainGroupArgs struct { 10 | DomainName string 11 | GroupId string 12 | } 13 | 14 | type ChangeDomainGroupResponse struct { 15 | common.Response 16 | GroupId string 17 | GroupName string 18 | } 19 | 20 | // ChangeDomainGroup 21 | // 22 | // You can read doc at https://help.aliyun.com/document_detail/29765.html?spm=5176.doc29764.6.607.WUJQgE 23 | func (client *Client) ChangeDomainGroup(args *ChangeDomainGroupArgs) (response *ChangeDomainGroupResponse, err error) { 24 | action := "ChangeDomainGroup" 25 | response = &ChangeDomainGroupResponse{} 26 | err = client.Invoke(action, args, response) 27 | if err == nil { 28 | return response, nil 29 | } else { 30 | log.Printf("%s error, %v", action, err) 31 | return response, err 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /alidns/DeleteDomain.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/denverdino/aliyungo/common" 7 | ) 8 | 9 | type DeleteDomainArgs struct { 10 | DomainName string 11 | } 12 | 13 | type DeleteDomainResponse struct { 14 | common.Response 15 | DomainName string 16 | } 17 | 18 | // DeleteDomain 19 | // 20 | // You can read doc at https://help.aliyun.com/document_detail/29750.html?spm=5176.doc29766.6.593.eELaZ7 21 | func (client *Client) DeleteDomain(args *DeleteDomainArgs) (response *DeleteDomainResponse, err error) { 22 | action := "DeleteDomain" 23 | response = &DeleteDomainResponse{} 24 | err = client.Invoke(action, args, response) 25 | if err == nil { 26 | return response, nil 27 | } else { 28 | log.Printf("%s error, %v", action, err) 29 | return response, err 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /alidns/DeleteDomainGroup.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/denverdino/aliyungo/common" 7 | ) 8 | 9 | type DeleteDomainGroupArgs struct { 10 | GroupId string 11 | } 12 | 13 | type DeleteDomainGroupResponse struct { 14 | common.Response 15 | GroupName string 16 | } 17 | 18 | // DeleteDomainGroup 19 | // 20 | // You can read doc at https://help.aliyun.com/document_detail/29764.html?spm=5176.doc29763.6.606.Vm3FyC 21 | func (client *Client) DeleteDomainGroup(args *DeleteDomainGroupArgs) (response *DeleteDomainGroupResponse, err error) { 22 | action := "DeleteDomainGroup" 23 | response = &DeleteDomainGroupResponse{} 24 | err = client.Invoke(action, args, response) 25 | if err == nil { 26 | return response, nil 27 | } else { 28 | log.Printf("%s error, %v", action, err) 29 | return response, err 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /alidns/DeleteDomainRecord.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import "github.com/denverdino/aliyungo/common" 4 | 5 | type DeleteDomainRecordArgs struct { 6 | RecordId string 7 | } 8 | 9 | type DeleteDomainRecordResponse struct { 10 | common.Response 11 | InstanceId string 12 | RecordId string 13 | } 14 | 15 | // DeleteDomainRecord 16 | // 17 | // You can read doc at https://docs.aliyun.com/#/pub/dns/api-reference/record-related&DeleteDomainRecord 18 | func (client *Client) DeleteDomainRecord(args *DeleteDomainRecordArgs) (response *DeleteDomainRecordResponse, err error) { 19 | action := "DeleteDomainRecord" 20 | response = &DeleteDomainRecordResponse{} 21 | err = client.Invoke(action, args, response) 22 | if err == nil { 23 | return response, nil 24 | } else { 25 | return nil, err 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /alidns/DeleteSubDomainRecords.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import "github.com/denverdino/aliyungo/common" 4 | 5 | type DeleteSubDomainRecordsArgs struct { 6 | DomainName string 7 | RR string 8 | 9 | // optional 10 | Type string 11 | } 12 | 13 | type DeleteSubDomainRecordsResponse struct { 14 | common.Response 15 | InstanceId string 16 | RR string 17 | // TotalCount int32 18 | } 19 | 20 | // DeleteSubDomainRecords 21 | // 22 | // You can read doc at https://docs.aliyun.com/#/pub/dns/api-reference/record-related&DeleteSubDomainRecords 23 | func (client *Client) DeleteSubDomainRecords(args *DeleteSubDomainRecordsArgs) (response *DeleteSubDomainRecordsResponse, err error) { 24 | action := "DeleteSubDomainRecords" 25 | response = &DeleteSubDomainRecordsResponse{} 26 | err = client.Invoke(action, args, response) 27 | if err == nil { 28 | return response, nil 29 | } else { 30 | return nil, err 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /alidns/DescribeDomainGroups.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/denverdino/aliyungo/common" 7 | ) 8 | 9 | type DomainGroupType struct { 10 | GroupId string 11 | GroupName string 12 | } 13 | 14 | type DescribeDomainGroupsArgs struct { 15 | // optional 16 | common.Pagination 17 | KeyWord string 18 | } 19 | 20 | type DescribeDomainGroupsResponse struct { 21 | response common.Response 22 | common.PaginationResult 23 | DomainGroups struct { 24 | DomainGroup []DomainGroupType 25 | } 26 | } 27 | 28 | // DescribeDomainGroups 29 | // 30 | // You can read doc at https://help.aliyun.com/document_detail/29766.html?spm=5176.doc29765.6.608.qcQr2R 31 | func (client *Client) DescribeDomainGroups(args *DescribeDomainGroupsArgs) (groups []DomainGroupType, err error) { 32 | action := "DescribeDomainGroups" 33 | response := &DescribeDomainGroupsResponse{} 34 | err = client.Invoke(action, args, response) 35 | if err != nil { 36 | log.Printf("%s error, %v", action, err) 37 | return nil, err 38 | } 39 | 40 | return response.DomainGroups.DomainGroup, nil 41 | } 42 | -------------------------------------------------------------------------------- /alidns/DescribeDomainInfo.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/denverdino/aliyungo/common" 7 | ) 8 | 9 | type DomainType struct { 10 | DomainId string 11 | DomainName string 12 | AliDomain bool 13 | GroupId string 14 | GroupName string 15 | InstanceId string 16 | VersionCode string 17 | PunyCode string 18 | DnsServers struct { 19 | DnsServer []string 20 | } 21 | } 22 | 23 | type DescribeDomainInfoArgs struct { 24 | DomainName string 25 | } 26 | 27 | type DescribeDomainInfoResponse struct { 28 | response common.Response 29 | DomainType 30 | } 31 | 32 | // DescribeDomainInfo 33 | // 34 | // You can read doc at https://help.aliyun.com/document_detail/29752.html?spm=5176.doc29751.6.595.VJM3Gy 35 | func (client *Client) DescribeDomainInfo(args *DescribeDomainInfoArgs) (domain DomainType, err error) { 36 | action := "DescribeDomainInfo" 37 | response := &DescribeDomainInfoResponse{} 38 | err = client.Invoke(action, args, response) 39 | if err != nil { 40 | log.Printf("%s error, %v", action, err) 41 | return DomainType{}, err 42 | } 43 | 44 | return response.DomainType, nil 45 | } 46 | -------------------------------------------------------------------------------- /alidns/DescribeDomainRecordInfo.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import "github.com/denverdino/aliyungo/common" 4 | 5 | type DescribeDomainRecordInfoArgs struct { 6 | RecordId string 7 | } 8 | 9 | type DescribeDomainRecordInfoResponse struct { 10 | common.Response 11 | RecordType 12 | } 13 | 14 | // DescribeDomainRecordInfo 15 | // 16 | // You can read doc at https://docs.aliyun.com/#/pub/dns/api-reference/record-related&DescribeDomainRecordInfo 17 | func (client *Client) DescribeDomainRecordInfo(args *DescribeDomainRecordInfoArgs) (response *DescribeDomainRecordInfoResponse, err error) { 18 | action := "DescribeDomainRecordInfo" 19 | response = &DescribeDomainRecordInfoResponse{} 20 | err = client.Invoke(action, args, response) 21 | if err == nil { 22 | return response, nil 23 | } else { 24 | return nil, err 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /alidns/DescribeDomainRecordInfoNew.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/denverdino/aliyungo/common" 7 | ) 8 | 9 | // endpoint change to 'http://alidns.aliyuncs.com' then record ttl and priority change to string 10 | type RecordTypeNew struct { 11 | DomainName string 12 | RecordId string 13 | RR string 14 | Type string 15 | Value string 16 | TTL json.Number 17 | Line string 18 | Status string 19 | Locked bool 20 | } 21 | 22 | type DescribeDomainRecordInfoNewArgs struct { 23 | RecordId string 24 | } 25 | 26 | type DescribeDomainRecordInfoNewResponse struct { 27 | common.Response 28 | RecordTypeNew 29 | } 30 | 31 | // DescribeDomainRecordInformation 32 | // 33 | // You can read doc at https://docs.aliyun.com/#/pub/dns/api-reference/record-related&DescribeDomainRecordInfo 34 | func (client *Client) DescribeDomainRecordInfoNew(args *DescribeDomainRecordInfoNewArgs) (response *DescribeDomainRecordInfoNewResponse, err error) { 35 | action := "DescribeDomainRecordInfo" 36 | response = &DescribeDomainRecordInfoNewResponse{} 37 | err = client.Invoke(action, args, response) 38 | if err == nil { 39 | return response, nil 40 | } else { 41 | return nil, err 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /alidns/DescribeDomainRecords.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import "github.com/denverdino/aliyungo/common" 4 | 5 | type DescribeDomainRecordsArgs struct { 6 | DomainName string 7 | 8 | // optional 9 | common.Pagination 10 | RRKeyWord string 11 | TypeKeyWord string 12 | ValueKeyWord string 13 | } 14 | 15 | type DescribeDomainRecordsResponse struct { 16 | common.Response 17 | common.PaginationResult 18 | InstanceId string 19 | DomainRecords struct { 20 | Record []RecordType 21 | } 22 | } 23 | 24 | // DescribeDomainRecords 25 | // 26 | // You can read doc at https://docs.aliyun.com/#/pub/dns/api-reference/record-related&DescribeDomainRecords 27 | func (client *Client) DescribeDomainRecords(args *DescribeDomainRecordsArgs) (response *DescribeDomainRecordsResponse, err error) { 28 | action := "DescribeDomainRecords" 29 | response = &DescribeDomainRecordsResponse{} 30 | err = client.Invoke(action, args, response) 31 | if err == nil { 32 | return response, nil 33 | } else { 34 | return nil, err 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /alidns/DescribeDomainRecordsNew.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import "github.com/denverdino/aliyungo/common" 4 | 5 | type DescribeDomainRecordsNewArgs struct { 6 | DomainName string 7 | 8 | // optional 9 | common.Pagination 10 | RRKeyWord string 11 | TypeKeyWord string 12 | ValueKeyWord string 13 | } 14 | 15 | type DescribeDomainRecordsNewResponse struct { 16 | common.Response 17 | common.PaginationResult 18 | InstanceId string 19 | DomainRecords struct { 20 | Record []RecordTypeNew 21 | } 22 | } 23 | 24 | // DescribeDomainRecordsNew 25 | // 26 | // You can read doc at https://docs.aliyun.com/#/pub/dns/api-reference/record-related&DescribeDomainRecords 27 | func (client *Client) DescribeDomainRecordsNew(args *DescribeDomainRecordsNewArgs) (response *DescribeDomainRecordsNewResponse, err error) { 28 | action := "DescribeDomainRecords" 29 | response = &DescribeDomainRecordsNewResponse{} 30 | err = client.Invoke(action, args, response) 31 | if err == nil { 32 | return response, nil 33 | } else { 34 | return nil, err 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /alidns/DescribeDomains.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/denverdino/aliyungo/common" 7 | ) 8 | 9 | type DescribeDomainsArgs struct { 10 | // optional 11 | common.Pagination 12 | KeyWord string 13 | GroupId string 14 | } 15 | 16 | type DescribeDomainsResponse struct { 17 | response common.Response 18 | common.PaginationResult 19 | Domains struct { 20 | Domain []DomainType 21 | } 22 | } 23 | 24 | // DescribeDomains 25 | // 26 | // You can read doc at https://help.aliyun.com/document_detail/29751.html?spm=5176.doc29750.6.594.dvyRJV 27 | func (client *Client) DescribeDomains(args *DescribeDomainsArgs) (domains []DomainType, err error) { 28 | action := "DescribeDomains" 29 | response := &DescribeDomainsResponse{} 30 | err = client.Invoke(action, args, response) 31 | if err != nil { 32 | log.Printf("%s error, %v", action, err) 33 | return nil, err 34 | } 35 | 36 | return response.Domains.Domain, err 37 | } 38 | -------------------------------------------------------------------------------- /alidns/DescribeSubDomainRecords.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import "github.com/denverdino/aliyungo/common" 4 | 5 | type DescribeSubDomainRecordsArgs struct { 6 | SubDomain string 7 | 8 | // optional 9 | PageNumber int32 10 | PageSize int32 11 | Type string 12 | } 13 | 14 | type DescribeSubDomainRecordsResponse struct { 15 | common.Response 16 | InstanceId string 17 | TotalCount int32 18 | PageNumber int32 19 | PageSize int32 20 | DomainRecords struct { 21 | Record []RecordType 22 | } 23 | } 24 | 25 | // DescribeSubDomainRecords 26 | // 27 | // You can read doc at https://docs.aliyun.com/#/pub/dns/api-reference/record-related&DescribeSubDomainRecords 28 | func (client *Client) DescribeSubDomainRecords(args *DescribeSubDomainRecordsArgs) (response *DescribeSubDomainRecordsResponse, err error) { 29 | action := "DescribeSubDomainRecords" 30 | response = &DescribeSubDomainRecordsResponse{} 31 | err = client.Invoke(action, args, response) 32 | if err == nil { 33 | return response, nil 34 | } else { 35 | return nil, err 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /alidns/GetMainDomainName.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import "github.com/denverdino/aliyungo/common" 4 | 5 | type GetMainDomainNameArgs struct { 6 | InputString string 7 | } 8 | 9 | type GetMainDomainNameResponse struct { 10 | common.Response 11 | InstanceId string 12 | DomainName string 13 | RR string 14 | DomainLevel int32 15 | } 16 | 17 | // GetMainDomainName 18 | // 19 | // You can read doc at https://docs.aliyun.com/#/pub/dns/api-reference/domain-related&GetMainDomainName 20 | func (client *Client) GetMainDomainName(args *GetMainDomainNameArgs) (response *GetMainDomainNameResponse, err error) { 21 | action := "GetMainDomainName" 22 | response = &GetMainDomainNameResponse{} 23 | err = client.Invoke(action, args, response) 24 | if err == nil { 25 | return response, nil 26 | } else { 27 | return nil, err 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /alidns/UpdateDomainGroup.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/denverdino/aliyungo/common" 7 | ) 8 | 9 | type UpdateDomainGroupArgs struct { 10 | GroupId string 11 | GroupName string 12 | } 13 | 14 | type UpdateDomainGroupResponse struct { 15 | common.Response 16 | GroupId string 17 | GroupName string 18 | } 19 | 20 | // UpdateDomainGroup 21 | // 22 | // You can read doc at https://help.aliyun.com/document_detail/29763.html?spm=5176.doc29762.6.605.iFRKjn 23 | func (client *Client) UpdateDomainGroup(args *UpdateDomainGroupArgs) (response *UpdateDomainGroupResponse, err error) { 24 | action := "UpdateDomainGroup" 25 | response = &UpdateDomainGroupResponse{} 26 | err = client.Invoke(action, args, response) 27 | if err == nil { 28 | return response, nil 29 | } else { 30 | log.Printf("%s error, %v", action, err) 31 | return response, err 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /alidns/UpdateDomainRecord.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/denverdino/aliyungo/common" 7 | ) 8 | 9 | type UpdateDomainRecordArgs struct { 10 | RecordId string 11 | RR string 12 | Type string 13 | Value string 14 | 15 | // optional 16 | TTL json.Number 17 | Priority json.Number 18 | Line string 19 | } 20 | 21 | type UpdateDomainRecordResponse struct { 22 | common.Response 23 | InstanceId string 24 | RecordId string 25 | } 26 | 27 | // UpdateDomainRecord 28 | // 29 | // You can read doc at https://docs.aliyun.com/#/pub/dns/api-reference/record-related&UpdateDomainRecord 30 | func (client *Client) UpdateDomainRecord(args *UpdateDomainRecordArgs) (response *UpdateDomainRecordResponse, err error) { 31 | action := "UpdateDomainRecord" 32 | response = &UpdateDomainRecordResponse{} 33 | err = client.Invoke(action, args, response) 34 | if err == nil { 35 | return response, nil 36 | } else { 37 | return nil, err 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /alidns/client.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/denverdino/aliyungo/common" 7 | ) 8 | 9 | type Client struct { 10 | common.Client 11 | } 12 | 13 | const ( 14 | // DNSDefaultEndpoint is the default API endpoint of DNS services 15 | DNSDefaultEndpoint = "http://dns.aliyuncs.com" 16 | DNSAPIVersion = "2015-01-09" 17 | 18 | DNSDefaultEndpointNew = "http://alidns.aliyuncs.com" 19 | ) 20 | 21 | // NewClient creates a new instance of DNS client 22 | func NewClient(accessKeyId, accessKeySecret string) *Client { 23 | endpoint := os.Getenv("DNS_ENDPOINT") 24 | if endpoint == "" { 25 | endpoint = DNSDefaultEndpoint 26 | } 27 | return NewClientWithEndpoint(endpoint, accessKeyId, accessKeySecret) 28 | } 29 | 30 | // NewClientNew creates a new instance of DNS client, with http://alidns.aliyuncs.com as default endpoint 31 | func NewClientNew(accessKeyId, accessKeySecret string) *Client { 32 | endpoint := os.Getenv("DNS_ENDPOINT") 33 | if endpoint == "" { 34 | endpoint = DNSDefaultEndpointNew 35 | } 36 | return NewClientWithEndpoint(endpoint, accessKeyId, accessKeySecret) 37 | } 38 | 39 | // NewCustomClient creates a new instance of ECS client with customized API endpoint 40 | func NewCustomClient(accessKeyId, accessKeySecret string, endpoint string) *Client { 41 | client := &Client{} 42 | client.Init(endpoint, DNSAPIVersion, accessKeyId, accessKeySecret) 43 | return client 44 | } 45 | 46 | func NewClientWithEndpoint(endpoint string, accessKeyId, accessKeySecret string) *Client { 47 | client := &Client{} 48 | client.Init(endpoint, DNSAPIVersion, accessKeyId, accessKeySecret) 49 | return client 50 | } 51 | -------------------------------------------------------------------------------- /alidns/record.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import "encoding/json" 4 | 5 | // you can read doc at https://docs.aliyun.com/#/pub/dns/api-reference/enum-type&record-format 6 | const ( 7 | ARecord = "A" 8 | NSRecord = "NS" 9 | MXRecord = "MX" 10 | TXTRecord = "TXT" 11 | CNAMERecord = "CNAME" 12 | SRVRecord = "SRV" 13 | AAAARecord = "AAAA" 14 | RedirectURLRecord = "REDIRECT_URL" 15 | ForwordURLRecord = "FORWORD_URL" 16 | ) 17 | 18 | type RecordType struct { 19 | DomainName string 20 | RecordId string 21 | RR string 22 | Type string 23 | Value string 24 | TTL json.Number 25 | Priority json.Number 26 | Line string 27 | Status string 28 | Locked bool 29 | } 30 | -------------------------------------------------------------------------------- /build-release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | name='aliddns' 4 | 5 | MD5='md5sum' 6 | if [[ "$(uname)" == 'Darwin' ]]; then 7 | MD5='md5' 8 | fi 9 | 10 | # UPX=false 11 | # if hash upx 2>/dev/null; then 12 | # UPX=true 13 | # fi 14 | 15 | VERSION=$(curl -sSL https://api.github.com/repos/honwen/aliyun-ddns-cli/commits/master | sed -n '{/sha/p; /date/p;}'| sed 's/.* \"//g' | cut -c1-10 | tr '[:lower:]' '[:upper:]' | sed 'N;s/\n/@/g' | head -1) 16 | LDFLAGS="-X main.version=$VERSION -s -w" 17 | 18 | # X86 19 | OSES=(windows linux darwin freebsd) 20 | # ARCHS=(amd64 386) 21 | ARCHS=(amd64) 22 | rm -rf ./release 23 | mkdir -p ./release 24 | for os in ${OSES[@]}; do 25 | for arch in ${ARCHS[@]}; do 26 | suffix="" 27 | if [ "$os" == "windows" ]; then 28 | suffix=".exe" 29 | fi 30 | env CGO_ENABLED=0 GOOS=$os GOARCH=$arch go build -ldflags "$LDFLAGS" -o ./release/${name}_${os}_${arch}${suffix} . 31 | # if $UPX; then upx -9 ./release/${name}_${os}_${arch}${suffix} -o ./release/${name}_${os}_${arch}${suffix}_upx; fi 32 | tar -C ./release -zcf ./release/${name}_${os}-${arch}-$VERSION.tar.gz ./${name}_${os}_${arch}${suffix} 33 | $MD5 ./release/${name}_${os}-${arch}-$VERSION.tar.gz 34 | done 35 | done 36 | 37 | # ARM64 38 | env CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags "$LDFLAGS" -o ./release/${name}_arm64 . 39 | # ARM 40 | ARMS=(5 6 7) 41 | for v in ${ARMS[@]}; do 42 | env CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=$v go build -ldflags "$LDFLAGS" -o ./release/${name}_arm$v . 43 | done 44 | # if $UPX; then upx -9 ./release/${name}_arm*; fi 45 | tar -C ./release -zcf ./release/${name}_arm-$VERSION.tar.gz $(for v in ${ARMS[@]}; do echo -n "./${name}_arm$v ";done) 46 | $MD5 ./release/${name}_arm-$VERSION.tar.gz 47 | 48 | # MIPS/hardfloat 49 | env CGO_ENABLED=0 GOOS=linux GOARCH=mipsle go build -ldflags "$LDFLAGS" -o ./release/${name}_mipsle . 50 | env CGO_ENABLED=0 GOOS=linux GOARCH=mips go build -ldflags "$LDFLAGS" -o ./release/${name}_mips . 51 | # MIPS/softfloat 52 | env CGO_ENABLED=0 GOOS=linux GOARCH=mipsle GOMIPS=softfloat go build -ldflags "$LDFLAGS" -o ./release/${name}_mipsle_sf . 53 | env CGO_ENABLED=0 GOOS=linux GOARCH=mips GOMIPS=softfloat go build -ldflags "$LDFLAGS" -o ./release/${name}_mips_sf . 54 | # if $UPX; then upx -9 ./release/${name}_mips**; fi 55 | tar -C ./release -zcf ./release/${name}_mipsle-$VERSION.tar.gz ./${name}_mipsle 56 | tar -C ./release -zcf ./release/${name}_mips-$VERSION.tar.gz ./${name}_mips 57 | tar -C ./release -zcf ./release/${name}_mipsle-sf-$VERSION.tar.gz ./${name}_mipsle_sf 58 | tar -C ./release -zcf ./release/${name}_mips-sf-$VERSION.tar.gz ./${name}_mips_sf 59 | $MD5 ./release/${name}_mipsle-$VERSION.tar.gz 60 | -------------------------------------------------------------------------------- /example/Synology_Docker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honwen/aliyun-ddns-cli/fb757691c4436dbbbf828517fea3e8459645c43e/example/Synology_Docker.png -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/honwen/aliyun-ddns-cli 2 | 3 | go 1.24.1 4 | 5 | require ( 6 | // locked before tracing/logging https://github.com/denverdino/aliyungo/commits/master/go.mod 7 | github.com/denverdino/aliyungo v0.0.0-20230411124812-ab98a9173ace 8 | github.com/honwen/golibs v0.8.0 9 | github.com/honwen/ip2loc v0.4.5 10 | github.com/urfave/cli v1.22.16 11 | github.com/ysmood/got v0.40.0 12 | ) 13 | 14 | require ( 15 | github.com/AdguardTeam/golibs v0.32.6 // indirect 16 | github.com/PuerkitoBio/goquery v1.10.2 // indirect 17 | github.com/Workiva/go-datastructures v1.1.5 // indirect 18 | github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect 19 | github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 // indirect 20 | github.com/ameshkov/dnscrypt/v2 v2.3.0 // indirect 21 | github.com/ameshkov/dnsstamps v1.0.3 // indirect 22 | github.com/andybalholm/cascadia v1.3.3 // indirect 23 | github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect 24 | github.com/go-task/slim-sprig/v3 v3.0.0 // indirect 25 | github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad // indirect 26 | github.com/miekg/dns v1.1.64 // indirect 27 | github.com/mr-karan/doggo v1.0.5 // indirect 28 | github.com/onsi/ginkgo/v2 v2.23.3 // indirect 29 | github.com/opentracing/opentracing-go v1.2.0 // indirect 30 | github.com/quic-go/quic-go v0.50.1 // indirect 31 | github.com/russross/blackfriday/v2 v2.1.0 // indirect 32 | github.com/tidwall/pretty v1.2.1 // indirect 33 | github.com/ysmood/gop v0.2.0 // indirect 34 | go.uber.org/mock v0.5.0 // indirect 35 | golang.org/x/crypto v0.36.0 // indirect 36 | golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect 37 | golang.org/x/mod v0.24.0 // indirect 38 | golang.org/x/net v0.38.0 // indirect 39 | golang.org/x/sync v0.12.0 // indirect 40 | golang.org/x/sys v0.31.0 // indirect 41 | golang.org/x/text v0.23.0 // indirect 42 | golang.org/x/tools v0.31.0 // indirect 43 | ) 44 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/AdguardTeam/golibs v0.32.6 h1:J/s6BiDHIJa5VsumGAzgI6S1SVLOaVB3YIvFlBK79HU= 2 | github.com/AdguardTeam/golibs v0.32.6/go.mod h1:bE8KV1zqTzgZjmjFyBJ9f9O5DEKO717r7e57j1HclJA= 3 | github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= 4 | github.com/PuerkitoBio/goquery v1.10.2 h1:7fh2BdHcG6VFZsK7toXBT/Bh1z5Wmy8Q9MV9HqT2AM8= 5 | github.com/PuerkitoBio/goquery v1.10.2/go.mod h1:0guWGjcLu9AYC7C1GHnpysHy056u9aEkUHwhdnePMCU= 6 | github.com/Workiva/go-datastructures v1.1.5 h1:5YfhQ4ry7bZc2Mc7R0YZyYwpf5c6t1cEFvdAhd6Mkf4= 7 | github.com/Workiva/go-datastructures v1.1.5/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A= 8 | github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY= 9 | github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA= 10 | github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 h1:52m0LGchQBBVqJRyYYufQuIbVqRawmubW3OFGqK1ekw= 11 | github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635/go.mod h1:lmLxL+FV291OopO93Bwf9fQLQeLyt33VJRUg5VJ30us= 12 | github.com/ameshkov/dnscrypt/v2 v2.3.0 h1:pDXDF7eFa6Lw+04C0hoMh8kCAQM8NwUdFEllSP2zNLs= 13 | github.com/ameshkov/dnscrypt/v2 v2.3.0/go.mod h1:N5hDwgx2cNb4Ay7AhvOSKst+eUiOZ/vbKRO9qMpQttE= 14 | github.com/ameshkov/dnsstamps v1.0.3 h1:Srzik+J9mivH1alRACTbys2xOxs0lRH9qnTA7Y1OYVo= 15 | github.com/ameshkov/dnsstamps v1.0.3/go.mod h1:Ii3eUu73dx4Vw5O4wjzmT5+lkCwovjzaEZZ4gKyIH5A= 16 | github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM= 17 | github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA= 18 | github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 19 | github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0= 20 | github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= 21 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 22 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 23 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 24 | github.com/denverdino/aliyungo v0.0.0-20230411124812-ab98a9173ace h1:1SnCTPFh2AADpm7ti864EYaugexyiDFt55BW188+d6k= 25 | github.com/denverdino/aliyungo v0.0.0-20230411124812-ab98a9173ace/go.mod h1:TK05uvk4XXfK2kdvRwfcZ1NaxjDxmm7H3aQLko0mJxA= 26 | github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= 27 | github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 28 | github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= 29 | github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= 30 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 31 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 32 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 33 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 34 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 35 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 36 | github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg= 37 | github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= 38 | github.com/honwen/golibs v0.8.0 h1:IcbrycrDM2vVJJ+q29+Ol0oG4uUI+zJUsF9I9zwx/ok= 39 | github.com/honwen/golibs v0.8.0/go.mod h1:eFYQQ2Lo9GxkThzkR3YtsTegZpNv0/RbeYPevBJdcs4= 40 | github.com/honwen/ip2loc v0.4.5 h1:Kv9yFLcvkPC7Dp+Cj/P7Lt3H2clLtTwwmz+nRjARQdU= 41 | github.com/honwen/ip2loc v0.4.5/go.mod h1:KGX5SIb1L6jT74GeprHpWfHGqXiUEUcEnZ+sWYWbuVw= 42 | github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= 43 | github.com/miekg/dns v1.1.64 h1:wuZgD9wwCE6XMT05UU/mlSko71eRSXEAm2EbjQXLKnQ= 44 | github.com/miekg/dns v1.1.64/go.mod h1:Dzw9769uoKVaLuODMDZz9M6ynFU6Em65csPuoi8G0ck= 45 | github.com/mr-karan/doggo v1.0.5 h1:y/SUD4HczLF0caIq8OIph4KXzTG2knuxXxVoBq3XYZo= 46 | github.com/mr-karan/doggo v1.0.5/go.mod h1:K6vbQ4tXPTcYmf8r/hCwWV9+v8ZMztxdxr5St+vE8Aw= 47 | github.com/onsi/ginkgo/v2 v2.23.3 h1:edHxnszytJ4lD9D5Jjc4tiDkPBZ3siDeJJkUZJJVkp0= 48 | github.com/onsi/ginkgo/v2 v2.23.3/go.mod h1:zXTP6xIp3U8aVuXN8ENK9IXRaTjFnpVB9mGmaSRvxnM= 49 | github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8= 50 | github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY= 51 | github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= 52 | github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= 53 | github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= 54 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 55 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 56 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 57 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 58 | github.com/quic-go/quic-go v0.50.1 h1:unsgjFIUqW8a2oopkY7YNONpV1gYND6Nt9hnt1PN94Q= 59 | github.com/quic-go/quic-go v0.50.1/go.mod h1:Vim6OmUvlYdwBhXP9ZVrtGmCMWa3wEqhq3NgYrI8b4E= 60 | github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= 61 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 62 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 63 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 64 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 65 | github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= 66 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 67 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 68 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 69 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 70 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 71 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 72 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 73 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 74 | github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= 75 | github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= 76 | github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg= 77 | github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q= 78 | github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaOOb6ThwMmTEbhRwtKR97o= 79 | github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= 80 | github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg= 81 | github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= 82 | github.com/urfave/cli v1.22.16 h1:MH0k6uJxdwdeWQTwhSO42Pwr4YLrNLwBtg1MRgTqPdQ= 83 | github.com/urfave/cli v1.22.16/go.mod h1:EeJR6BKodywf4zciqrdw6hpCPk68JO9z5LazXZMn5Po= 84 | github.com/ysmood/gop v0.2.0 h1:+tFrG0TWPxT6p9ZaZs+VY+opCvHU8/3Fk6BaNv6kqKg= 85 | github.com/ysmood/gop v0.2.0/go.mod h1:rr5z2z27oGEbyB787hpEcx4ab8cCiPnKxn0SUHt6xzk= 86 | github.com/ysmood/got v0.40.0 h1:ZQk1B55zIvS7zflRrkGfPDrPG3d7+JOza1ZkNxcc74Q= 87 | github.com/ysmood/got v0.40.0/go.mod h1:W7DdpuX6skL3NszLmAsC5hT7JAhuLZhByVzHTq874Qg= 88 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 89 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 90 | go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= 91 | go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 92 | go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= 93 | go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= 94 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 95 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 96 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 97 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 98 | golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= 99 | golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= 100 | golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= 101 | golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= 102 | golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= 103 | golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= 104 | golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw= 105 | golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM= 106 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 107 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 108 | golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 109 | golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 110 | golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 111 | golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 112 | golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= 113 | golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= 114 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 115 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 116 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 117 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 118 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 119 | golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 120 | golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= 121 | golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= 122 | golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= 123 | golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= 124 | golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= 125 | golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= 126 | golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= 127 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 128 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 129 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 130 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 131 | golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= 132 | golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 133 | golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 134 | golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 135 | golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= 136 | golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 137 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 138 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 139 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 140 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 141 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 142 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 143 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 144 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 145 | golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 146 | golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 147 | golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 148 | golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 149 | golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 150 | golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= 151 | golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 152 | golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= 153 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 154 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 155 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 156 | golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= 157 | golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= 158 | golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= 159 | golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= 160 | golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= 161 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 162 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 163 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 164 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 165 | golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 166 | golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 167 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 168 | golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 169 | golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= 170 | golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= 171 | golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= 172 | golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= 173 | golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= 174 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 175 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 176 | golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 177 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 178 | golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 179 | golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= 180 | golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= 181 | golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU= 182 | golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ= 183 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 184 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 185 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 186 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 187 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 188 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 189 | google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= 190 | google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 191 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 192 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 193 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 194 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 195 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 196 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "log" 8 | "math/rand" 9 | "os" 10 | "regexp" 11 | "runtime" 12 | "sort" 13 | "strconv" 14 | "strings" 15 | "time" 16 | 17 | "github.com/denverdino/aliyungo/common" 18 | "github.com/honwen/golibs/cip" 19 | "github.com/honwen/golibs/domain" 20 | "github.com/honwen/ip2loc" 21 | "github.com/urfave/cli" 22 | 23 | dns "github.com/honwen/aliyun-ddns-cli/alidns" 24 | ) 25 | 26 | const RR_AT = `@` 27 | 28 | // AccessKey from https://ak-console.aliyun.com/#/accesskey 29 | type AccessKey struct { 30 | ID string 31 | Secret string 32 | client *dns.Client 33 | managedDomains []string 34 | } 35 | 36 | func (ak *AccessKey) getClient() *dns.Client { 37 | if len(ak.ID) <= 0 && len(ak.Secret) <= 0 { 38 | return nil 39 | } 40 | if ak.client == nil { 41 | ak.client = dns.NewClient(ak.ID, ak.Secret) 42 | ak.client.SetEndpoint(dns.DNSDefaultEndpointNew) 43 | } 44 | return ak.client 45 | } 46 | 47 | func (ak AccessKey) String() string { 48 | return fmt.Sprintf("Access Key: [ ID: %s ;\t Secret: %s ]", ak.ID, ak.Secret) 49 | } 50 | 51 | func (ak *AccessKey) ListManagedDomains() (domains []string, err error) { 52 | var resp []dns.DomainType 53 | resp, err = ak.getClient().DescribeDomains( 54 | &dns.DescribeDomainsArgs{ 55 | Pagination: common.Pagination{PageSize: 50}, 56 | }) 57 | if err != nil { 58 | return 59 | } 60 | domains = make([]string, len(resp)) 61 | for i, v := range resp { 62 | domains[i] = v.DomainName 63 | } 64 | return 65 | } 66 | 67 | func (ak *AccessKey) SplitDomainToRR(fulldomain string) (r, d string) { 68 | if r, d, err := accessKey.AutocheckDomainRR("", fulldomain); err == nil { 69 | if len(r) == 0 { 70 | r = RR_AT 71 | } 72 | return r, d 73 | } else { 74 | return domain.SplitDomainToRR(fulldomain) 75 | } 76 | } 77 | 78 | func (ak *AccessKey) AutocheckDomainRR(rr, domain string) (r, d string, err error) { 79 | if contains(ak.managedDomains, domain) { 80 | return rr, domain, nil 81 | } else { 82 | if !strings.Contains(rr, `.`) { 83 | return "", "", fmt.Errorf("domain [%s.%s] Not Managed", rr, domain) 84 | } else { 85 | rrs := strings.Split(rr, `.`) 86 | for i := len(rrs) - 1; i > 0; i-- { 87 | d = strings.Join(append(rrs[i:], domain), `.`) 88 | if contains(ak.managedDomains, d) { 89 | r = strings.Join(rrs[:i], `.`) 90 | return 91 | } 92 | } 93 | } 94 | } 95 | return "", "", fmt.Errorf("domain [%s.%s] Not Managed", rr, domain) 96 | } 97 | 98 | func (ak *AccessKey) ListRecord(domain string) (dnsRecords []dns.RecordTypeNew, err error) { 99 | var resp *dns.DescribeDomainRecordsNewResponse 100 | for idx := 1; idx <= 99; idx++ { 101 | resp, err = ak.getClient().DescribeDomainRecordsNew( 102 | &dns.DescribeDomainRecordsNewArgs{ 103 | DomainName: domain, 104 | Pagination: common.Pagination{PageNumber: idx, PageSize: 50}, 105 | }) 106 | if err != nil { 107 | return 108 | } 109 | dnsRecords = append(dnsRecords, resp.DomainRecords.Record...) 110 | if len(dnsRecords) >= resp.PaginationResult.TotalCount { 111 | return 112 | } 113 | } 114 | return 115 | } 116 | 117 | func (ak *AccessKey) DelRecord(rr, domain string) (err error) { 118 | var target *dns.RecordTypeNew 119 | if dnsRecords, err := ak.ListRecord(domain); err == nil { 120 | for i := range dnsRecords { 121 | if dnsRecords[i].RR == rr { 122 | target = &dnsRecords[i] 123 | _, err = ak.getClient().DeleteDomainRecord( 124 | &dns.DeleteDomainRecordArgs{ 125 | RecordId: target.RecordId, 126 | }, 127 | ) 128 | if err != nil { 129 | return err 130 | } 131 | } 132 | } 133 | } else { 134 | return err 135 | } 136 | return 137 | } 138 | 139 | func (ak *AccessKey) UpdateRecord(recordID, rr, dmType, value string, ttl int) (err error) { 140 | _, err = ak.getClient().UpdateDomainRecord( 141 | &dns.UpdateDomainRecordArgs{ 142 | RecordId: recordID, 143 | RR: rr, 144 | Value: value, 145 | Type: dmType, 146 | TTL: json.Number(fmt.Sprint(ttl)), 147 | }) 148 | return 149 | } 150 | 151 | func (ak *AccessKey) AddRecord(domain, rr, dmType, value string, ttl int) (err error) { 152 | _, err = ak.getClient().AddDomainRecord( 153 | &dns.AddDomainRecordArgs{ 154 | DomainName: domain, 155 | RR: rr, 156 | Type: dmType, 157 | Value: value, 158 | TTL: json.Number(fmt.Sprint(ttl)), 159 | }) 160 | return err 161 | } 162 | 163 | func (ak *AccessKey) CheckAndUpdateRecord(rr, domain, ipaddr, recordType string, ttl int) (err error) { 164 | fulldomain := strings.Join([]string{rr, domain}, `.`) 165 | if reslove(fulldomain) == ipaddr { 166 | return // Skip 167 | } 168 | targetCnt := 0 169 | var target *dns.RecordTypeNew 170 | if dnsRecords, err := ak.ListRecord(domain); err == nil { 171 | for i := range dnsRecords { 172 | if dnsRecords[i].RR == rr && dnsRecords[i].Type == recordType { 173 | target = &dnsRecords[i] 174 | targetCnt++ 175 | } 176 | } 177 | } else { 178 | return err 179 | } 180 | 181 | if targetCnt > 1 { 182 | ak.DelRecord(rr, domain) 183 | target = nil 184 | } 185 | 186 | if target == nil { 187 | err = ak.AddRecord(domain, rr, recordType, ipaddr, ttl) 188 | } else if target.Value != ipaddr { 189 | if target.Type != recordType { 190 | return fmt.Errorf("record type error! oldType=%s, targetType=%s", target.Type, recordType) 191 | } 192 | err = ak.UpdateRecord(target.RecordId, target.RR, target.Type, ipaddr, ttl) 193 | } 194 | if err != nil && strings.Contains(err.Error(), `DomainRecordDuplicate`) { 195 | ak.DelRecord(rr, domain) 196 | return ak.CheckAndUpdateRecord(rr, domain, ipaddr, recordType, ttl) 197 | } 198 | return err 199 | } 200 | 201 | var ( 202 | accessKey AccessKey 203 | VersionString = "MISSING build version [git hash]" 204 | ) 205 | 206 | func joinDomain(rr, domain string) string { 207 | if rr == RR_AT { 208 | return domain 209 | } else { 210 | return rr + `.` + domain 211 | } 212 | } 213 | 214 | func main() { 215 | defer func() { 216 | if recovered := recover(); recovered != nil { 217 | fmt.Println("# Err: Exit unexpected!", recovered) 218 | } 219 | }() 220 | 221 | app := cli.NewApp() 222 | app.Name = "aliddns" 223 | app.Usage = "aliyun-ddns-cli" 224 | app.Version = fmt.Sprintf("Git:[%s] (%s)", strings.ToUpper(VersionString), runtime.Version()) 225 | app.Commands = []cli.Command{ 226 | { 227 | Name: "list", 228 | Category: "DDNS", 229 | Usage: "List AliYun's DNS DomainRecords Record", 230 | Flags: []cli.Flag{ 231 | cli.StringFlag{ 232 | Name: "domain, d", 233 | Usage: "Specific `DomainName`. like aliyun.com", 234 | }, 235 | }, 236 | Action: func(c *cli.Context) error { 237 | if err := appInit(c, true); err != nil { 238 | return err 239 | } 240 | // fmt.Println(c.Command.Name, "task: ", accessKey, c.String("domain")) 241 | domain := c.String("domain") 242 | if !contains(accessKey.managedDomains, domain) { 243 | return fmt.Errorf("domain [%s] Not Managed", domain) 244 | } 245 | if dnsRecords, err := accessKey.ListRecord(domain); err != nil { 246 | fmt.Printf("%+v", err) 247 | } else { 248 | for _, v := range dnsRecords { 249 | fmt.Printf("%20s %-16s %s\n", joinDomain(v.RR, v.DomainName), fmt.Sprintf("%s(TTL:%4s)", v.Type, v.TTL), v.Value) 250 | } 251 | } 252 | return nil 253 | }, 254 | }, 255 | { 256 | Name: "delete", 257 | Category: "DDNS", 258 | Usage: "Delete AliYun's DNS DomainRecords Record", 259 | Flags: []cli.Flag{ 260 | cli.StringFlag{ 261 | Name: "domain, d", 262 | Usage: "Specific `FullDomainName`. like ddns.aliyun.com", 263 | }, 264 | }, 265 | Action: func(c *cli.Context) error { 266 | if err := appInit(c, true); err != nil { 267 | return err 268 | } 269 | // fmt.Println(c.Command.Name, "task: ", accessKey, c.String("domain")) 270 | rr, domain, err := accessKey.AutocheckDomainRR(accessKey.SplitDomainToRR(c.String("domain"))) 271 | if err != nil { 272 | return err 273 | } 274 | if err := accessKey.DelRecord(rr, domain); err != nil { 275 | fmt.Printf("%+v", err) 276 | } else { 277 | fmt.Println(c.String("domain"), "Deleted") 278 | } 279 | return nil 280 | }, 281 | }, 282 | { 283 | Name: "update", 284 | Category: "DDNS", 285 | Usage: "Update AliYun's DNS DomainRecords Record, Create Record if not exist", 286 | Flags: []cli.Flag{ 287 | cli.StringFlag{ 288 | Name: "domain, d", 289 | Usage: "Specific `DomainName`. like ddns.aliyun.com", 290 | }, 291 | cli.StringFlag{ 292 | Name: "ipaddr, i", 293 | Usage: "Specific `IP`. like 1.2.3.4", 294 | }, 295 | cli.IntFlag{ 296 | Name: "ttl, t", 297 | Value: 600, 298 | Usage: "The resolution effective time (in `seconds`)", 299 | }, 300 | }, 301 | Action: func(c *cli.Context) error { 302 | if err := appInit(c, true); err != nil { 303 | return err 304 | } 305 | // fmt.Println(c.Command.Name, "task: ", accessKey, c.String("domain"), c.String("ipaddr")) 306 | rr, domain, err := accessKey.AutocheckDomainRR(accessKey.SplitDomainToRR(c.String("domain"))) 307 | if err != nil { 308 | return err 309 | } 310 | recordType := "A" 311 | if c.GlobalBool("ipv6") { 312 | recordType = "AAAA" 313 | } 314 | if err := accessKey.CheckAndUpdateRecord(rr, domain, c.String("ipaddr"), recordType, c.Int("ttl")); err != nil { 315 | log.Printf("%+v", err) 316 | } else { 317 | log.Println(c.String("domain"), c.String("ipaddr"), ip2loc.IP2locCHS(c.String("ipaddr"))) 318 | } 319 | return nil 320 | }, 321 | }, 322 | { 323 | Name: "auto-update", 324 | Category: "DDNS", 325 | Usage: "Auto-Update AliYun's DNS DomainRecords Record, Get IP using its getip", 326 | Flags: []cli.Flag{ 327 | cli.StringFlag{ 328 | Name: "domain, d", 329 | Usage: "Specific `DomainName`. like ddns.aliyun.com", 330 | }, 331 | cli.StringFlag{ 332 | Name: "redo, r", 333 | Value: "", 334 | Usage: "redo Auto-Update, every N `Seconds`; Disable if N less than 10; End with [Rr] enable random delay: [N, 2N]", 335 | }, 336 | cli.IntFlag{ 337 | Name: "ttl, t", 338 | Value: 600, 339 | Usage: "The resolution effective time (in `seconds`)", 340 | }, 341 | }, 342 | Action: func(c *cli.Context) error { 343 | if err := appInit(c, true); err != nil { 344 | return err 345 | } 346 | // fmt.Println(c.Command.Name, "task: ", accessKey, c.String("domain"), c.Int64("redo")) 347 | rr, domain, err := accessKey.AutocheckDomainRR(accessKey.SplitDomainToRR(c.String("domain"))) 348 | if err != nil { 349 | return err 350 | } 351 | recordType := "A" 352 | if c.GlobalBool("ipv6") { 353 | recordType = "AAAA" 354 | } 355 | redoDurtionStr := c.String("redo") 356 | if len(redoDurtionStr) > 0 && !regexp.MustCompile(`\d+[Rr]?$`).MatchString(redoDurtionStr) { 357 | return errors.New(`redo format: [0-9]+[Rr]?$`) 358 | } 359 | randomDelay := regexp.MustCompile(`\d+[Rr]$`).MatchString(redoDurtionStr) 360 | redoDurtion := 0 361 | if randomDelay { 362 | redoDurtion, _ = strconv.Atoi(redoDurtionStr[:len(redoDurtionStr)-1]) 363 | } else { 364 | redoDurtion, _ = strconv.Atoi(redoDurtionStr) 365 | } 366 | // Print Version if exist 367 | if redoDurtion > 0 && !strings.HasPrefix(VersionString, "MISSING") { 368 | fmt.Fprintf(os.Stderr, "%s %s\n", strings.ToUpper(c.App.Name), c.App.Version) 369 | } 370 | for { 371 | autoip := myip() 372 | if len(autoip) == 0 { 373 | log.Printf("# Err-CheckAndUpdateRecord: [%s]", "IP is empty, PLZ check network") 374 | } else { 375 | if err := accessKey.CheckAndUpdateRecord(rr, domain, autoip, recordType, c.Int("ttl")); err != nil { 376 | log.Printf("# Err-CheckAndUpdateRecord: [%+v]", err) 377 | } else { 378 | log.Println(c.String("domain"), autoip, ip2loc.IP2locCHS(autoip)) 379 | } 380 | } 381 | if redoDurtion < 10 { 382 | break // Disable if N less than 10 383 | } 384 | if randomDelay { 385 | time.Sleep(time.Duration(redoDurtion+rand.Intn(redoDurtion)) * time.Second) 386 | } else { 387 | time.Sleep(time.Duration(redoDurtion) * time.Second) 388 | } 389 | } 390 | return nil 391 | }, 392 | }, 393 | { 394 | Name: "getip", 395 | Category: "GET-IP", 396 | Usage: " Get IP Combine 10+ different Web-API", 397 | Action: func(c *cli.Context) error { 398 | if err := appInit(c, false); err != nil { 399 | return err 400 | } 401 | // fmt.Println(c.Command.Name, "task: ", c.Command.Usage) 402 | ip := myip() 403 | fmt.Println(ip, ip2loc.IP2locCHS(ip)) 404 | return nil 405 | }, 406 | }, 407 | { 408 | Name: "resolve", 409 | Category: "GET-IP", 410 | Usage: " Get DNS-IPv4 Combine 4+ DNS Upstream", 411 | Flags: []cli.Flag{ 412 | cli.StringFlag{ 413 | Name: "domain, d", 414 | Required: true, 415 | Usage: "Specific `DomainName`. like ddns.aliyun.com", 416 | }, 417 | }, 418 | Action: func(c *cli.Context) error { 419 | if err := appInit(c, false); err != nil { 420 | return err 421 | } 422 | // fmt.Println(c.Command.Name, "task: ", c.Command.Usage) 423 | ip := reslove(c.String("domain")) 424 | fmt.Println(ip, ip2loc.IP2locCHS(ip)) 425 | return nil 426 | }, 427 | }, 428 | } 429 | app.Flags = []cli.Flag{ 430 | cli.StringFlag{ 431 | Name: "access-key-id, id", 432 | Usage: "AliYun's Access Key ID", 433 | }, 434 | cli.StringFlag{ 435 | Name: "access-key-secret, secret", 436 | Usage: "AliYun's Access Key Secret", 437 | }, 438 | cli.StringSliceFlag{ 439 | Name: "ipapi, api", 440 | Usage: "Web-API to Get IP, like: http://v6r.ipip.net", 441 | }, 442 | cli.BoolFlag{ 443 | Name: "ipv6, 6", 444 | Usage: "IPv6", 445 | }, 446 | } 447 | app.Action = func(c *cli.Context) error { 448 | return appInit(c, true) 449 | } 450 | app.Run(os.Args) 451 | } 452 | 453 | func appInit(c *cli.Context, checkAccessKey bool) error { 454 | akids := []string{c.GlobalString("access-key-id"), os.Getenv("AKID"), os.Getenv("AccessKeyID")} 455 | akscts := []string{c.GlobalString("access-key-secret"), os.Getenv("AKSCT"), os.Getenv("AccessKeySecret")} 456 | sort.Sort(sort.Reverse(sort.StringSlice(akids))) 457 | sort.Sort(sort.Reverse(sort.StringSlice(akscts))) 458 | accessKey.ID = akids[0] 459 | accessKey.Secret = akscts[0] 460 | if checkAccessKey { 461 | if accessKey.getClient() == nil { 462 | cli.ShowAppHelp(c) 463 | return errors.New("access-key is empty") 464 | } 465 | if domains, err := accessKey.ListManagedDomains(); err == nil { 466 | // log.Println(domains) 467 | accessKey.managedDomains = domains 468 | } else { 469 | cli.ShowAppHelp(c) 470 | return errors.New("no Managed Domains") 471 | } 472 | } 473 | 474 | if c.GlobalBool("ipv6") { 475 | funcs["myip"] = cip.MyIPv6 476 | funcs["reslove"] = cip.ResloveIPv6 477 | } 478 | 479 | ipapi := []string{} 480 | for _, api := range c.GlobalStringSlice("ipapi") { 481 | if !regexp.MustCompile(`^https?://.*`).MatchString(api) { 482 | api = "http://" + api 483 | } 484 | if regexp.MustCompile(`(https?|ftp|file)://[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]`).MatchString(api) { 485 | ipapi = append(ipapi, api) 486 | } 487 | } 488 | if len(ipapi) > 0 { 489 | regx := regexp.MustCompile(cip.RegxIPv4) 490 | if c.GlobalBoolT("ipv6") { 491 | regx = regexp.MustCompile(cip.RegxIPv6) 492 | } 493 | funcs["myip"] = func() string { 494 | return cip.FastWGetWithVailder(ipapi, func(s string) string { 495 | return regx.FindString((s)) 496 | }) 497 | } 498 | } 499 | 500 | return nil 501 | } 502 | -------------------------------------------------------------------------------- /utils.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "net" 6 | "reflect" 7 | 8 | "github.com/honwen/golibs/cip" 9 | ) 10 | 11 | var ipBlacklist = []string{ 12 | "127.0.0.0/8", "0.0.0.0/24", 13 | } 14 | 15 | var funcs = map[string]interface{}{ 16 | "myip": cip.MyIPv4, 17 | "reslove": cip.ResloveIPv4, 18 | } 19 | 20 | func contains(slice []string, item string) bool { 21 | set := make(map[string]struct{}, len(slice)) 22 | for _, s := range slice { 23 | set[s] = struct{}{} 24 | } 25 | _, ok := set[item] 26 | return ok 27 | } 28 | 29 | func Call(m map[string]interface{}, name string, params ...interface{}) (result []reflect.Value, err error) { 30 | f := reflect.ValueOf(m[name]) 31 | if len(params) != f.Type().NumIn() { 32 | err = errors.New("The number of params is not adapted.") 33 | return 34 | } 35 | 36 | in := make([]reflect.Value, len(params)) 37 | for k, param := range params { 38 | in[k] = reflect.ValueOf(param) 39 | } 40 | result = f.Call(in) 41 | return 42 | } 43 | 44 | func containsCIDR(a, b *net.IPNet) bool { 45 | ones1, _ := a.Mask.Size() 46 | ones2, _ := b.Mask.Size() 47 | return ones1 <= ones2 && a.Contains(b.IP) 48 | } 49 | 50 | func containsCIDRString(a, b string) (bool, error) { 51 | _, net1, err := net.ParseCIDR(a) 52 | if err != nil { 53 | return false, err 54 | } 55 | _, net2, err := net.ParseCIDR(b) 56 | if err != nil { 57 | return false, err 58 | } 59 | result := containsCIDR(net1, net2) 60 | return result, err 61 | } 62 | 63 | func myip() (ip string) { 64 | if result, err := Call(funcs, "myip"); err == nil { 65 | for _, r := range result { 66 | ip = r.String() 67 | for _, it := range ipBlacklist { 68 | ok, _ := containsCIDRString(it, ip+`/32`) 69 | if ok { 70 | ip = "" 71 | break 72 | } 73 | } 74 | } 75 | } 76 | return 77 | } 78 | 79 | func reslove(domain string) (ip string) { 80 | if result, err := Call(funcs, "reslove", domain); err == nil { 81 | for _, r := range result { 82 | return r.String() 83 | } 84 | } 85 | return 86 | } 87 | -------------------------------------------------------------------------------- /utils_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "regexp" 5 | "testing" 6 | 7 | "github.com/honwen/golibs/cip" 8 | "github.com/honwen/golibs/domain" 9 | "github.com/ysmood/got" 10 | ) 11 | 12 | func TestContainsCIDRString(t *testing.T) { 13 | _, err := containsCIDRString("127.0.0.0/8", "128.0.0.1") 14 | got.T(t).NotNil(err) 15 | is, _ := containsCIDRString("127.0.0.0/8", "128.0.0.1/32") 16 | got.T(t).False(is) 17 | is, _ = containsCIDRString("127.0.0.0/8", "127.0.0.99/32") 18 | got.T(t).True(is) 19 | } 20 | 21 | func TestGetIPv4(t *testing.T) { 22 | funcs["myip"] = cip.MyIPv4 23 | ip4 := myip() 24 | got.T(t).True(regexp.MustCompile(cip.RegxIPv4).MatchString(ip4) || len(ip4) == 0) 25 | } 26 | 27 | func TestGetIPv6(t *testing.T) { 28 | funcs["myip"] = cip.MyIPv6 29 | ip6 := myip() 30 | got.T(t).True(regexp.MustCompile(cip.RegxIPv6).MatchString(ip6) || len(ip6) == 0) 31 | } 32 | 33 | func TestResloveIPv4(t *testing.T) { 34 | funcs["reslove"] = cip.ResloveIPv4 35 | got.T(t).Has([]string{"8.8.8.8", "8.8.4.4"}, reslove("dns.google")) 36 | got.T(t).Has([]string{"223.6.6.6", "223.5.5.5"}, reslove("dns.alidns.com")) 37 | } 38 | 39 | func TestResloveIPv6(t *testing.T) { 40 | funcs["reslove"] = cip.ResloveIPv6 41 | got.T(t).Has([]string{"2001:4860:4860::8844", "2001:4860:4860::8888"}, reslove("dns.google")) 42 | got.T(t).Has([]string{"2400:3200::1", "2400:3200:baba::1"}, reslove("dns.alidns.com")) 43 | } 44 | 45 | func TestSplitDomain001(t *testing.T) { 46 | rr, domain := domain.SplitDomainToRR("a.example.com") 47 | 48 | got.T(t).Eq(rr, "a") 49 | got.T(t).Eq(domain, "example.com") 50 | } 51 | 52 | func TestSplitDomain002(t *testing.T) { 53 | rr, domain := domain.SplitDomainToRR("example.com") 54 | 55 | got.T(t).Eq(rr, "@") 56 | got.T(t).Eq(domain, "example.com") 57 | } 58 | 59 | func TestSplitDomain003(t *testing.T) { 60 | rr, domain := domain.SplitDomainToRR("*.example.com") 61 | 62 | got.T(t).Eq(rr, "*") 63 | got.T(t).Eq(domain, "example.com") 64 | } 65 | 66 | func TestSplitDomain004(t *testing.T) { 67 | rr, domain := domain.SplitDomainToRR("*.a.example.com") 68 | 69 | got.T(t).Eq(rr, "*.a") 70 | got.T(t).Eq(domain, "example.com") 71 | } 72 | 73 | func TestSplitDomain005(t *testing.T) { 74 | rr, domain := domain.SplitDomainToRR("*.b.a.example.com") 75 | 76 | got.T(t).Eq(rr, "*.b.a") 77 | got.T(t).Eq(domain, "example.com") 78 | } 79 | 80 | func TestSplitDomain006(t *testing.T) { 81 | rr, domain := domain.SplitDomainToRR("a.example.co.kr") 82 | 83 | got.T(t).Eq(rr, "a") 84 | got.T(t).Eq(domain, "example.co.kr") 85 | } 86 | 87 | func TestSplitDomain007(t *testing.T) { 88 | rr, domain := domain.SplitDomainToRR("*.a.example.co.kr") 89 | 90 | got.T(t).Eq(rr, "*.a") 91 | got.T(t).Eq(domain, "example.co.kr") 92 | } 93 | 94 | func TestSplitDomain008(t *testing.T) { 95 | rr, domain := domain.SplitDomainToRR("example.co.kr") 96 | 97 | got.T(t).Eq(rr, "@") 98 | got.T(t).Eq(domain, "example.co.kr") 99 | } 100 | --------------------------------------------------------------------------------