Add DDNS Profiles, use publicsuffixlist domain parser (#350)
* Add DDNS Profiles, use publicsuffixlist domain parser * Add Tencent Cloud DNS Provider * Restore validate DDNS provider function * chore: fmt & upgrade dependencies
This commit is contained in:
parent
890616f52a
commit
5c7652f047
@ -304,6 +304,7 @@ type serverForm struct {
|
|||||||
EnableIPv4 string
|
EnableIPv4 string
|
||||||
EnableIpv6 string
|
EnableIpv6 string
|
||||||
DDNSDomain string
|
DDNSDomain string
|
||||||
|
DDNSProfile string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ma *memberAPI) addOrEditServer(c *gin.Context) {
|
func (ma *memberAPI) addOrEditServer(c *gin.Context) {
|
||||||
@ -323,6 +324,7 @@ func (ma *memberAPI) addOrEditServer(c *gin.Context) {
|
|||||||
s.EnableIPv4 = sf.EnableIPv4 == "on"
|
s.EnableIPv4 = sf.EnableIPv4 == "on"
|
||||||
s.EnableIpv6 = sf.EnableIpv6 == "on"
|
s.EnableIpv6 = sf.EnableIpv6 == "on"
|
||||||
s.DDNSDomain = sf.DDNSDomain
|
s.DDNSDomain = sf.DDNSDomain
|
||||||
|
s.DDNSProfile = sf.DDNSProfile
|
||||||
if s.ID == 0 {
|
if s.ID == 0 {
|
||||||
s.Secret, err = utils.GenerateRandomString(18)
|
s.Secret, err = utils.GenerateRandomString(18)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
10
go.mod
10
go.mod
@ -3,8 +3,8 @@ module github.com/naiba/nezha
|
|||||||
go 1.20
|
go 1.20
|
||||||
|
|
||||||
require (
|
require (
|
||||||
code.cloudfoundry.org/bytefmt v0.0.0-20240405144452-ebb2996022ca
|
code.cloudfoundry.org/bytefmt v0.0.0-20240425163905-bcdc1ad063ea
|
||||||
code.gitea.io/sdk/gitea v0.17.1
|
code.gitea.io/sdk/gitea v0.18.0
|
||||||
github.com/BurntSushi/toml v1.3.2
|
github.com/BurntSushi/toml v1.3.2
|
||||||
github.com/gin-contrib/pprof v1.4.0
|
github.com/gin-contrib/pprof v1.4.0
|
||||||
github.com/gin-gonic/gin v1.9.1
|
github.com/gin-gonic/gin v1.9.1
|
||||||
@ -20,15 +20,16 @@ require (
|
|||||||
github.com/samber/lo v1.39.0
|
github.com/samber/lo v1.39.0
|
||||||
github.com/spf13/pflag v1.0.5
|
github.com/spf13/pflag v1.0.5
|
||||||
github.com/spf13/viper v1.18.2
|
github.com/spf13/viper v1.18.2
|
||||||
github.com/xanzy/go-gitlab v0.102.0
|
github.com/xanzy/go-gitlab v0.103.0
|
||||||
golang.org/x/crypto v0.22.0
|
golang.org/x/crypto v0.22.0
|
||||||
|
golang.org/x/net v0.24.0
|
||||||
golang.org/x/oauth2 v0.19.0
|
golang.org/x/oauth2 v0.19.0
|
||||||
golang.org/x/sync v0.7.0
|
golang.org/x/sync v0.7.0
|
||||||
golang.org/x/text v0.14.0
|
golang.org/x/text v0.14.0
|
||||||
google.golang.org/grpc v1.63.0
|
google.golang.org/grpc v1.63.0
|
||||||
google.golang.org/protobuf v1.33.0
|
google.golang.org/protobuf v1.33.0
|
||||||
gorm.io/driver/sqlite v1.5.5
|
gorm.io/driver/sqlite v1.5.5
|
||||||
gorm.io/gorm v1.25.9
|
gorm.io/gorm v1.25.10
|
||||||
sigs.k8s.io/yaml v1.4.0
|
sigs.k8s.io/yaml v1.4.0
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -73,7 +74,6 @@ require (
|
|||||||
go.uber.org/multierr v1.9.0 // indirect
|
go.uber.org/multierr v1.9.0 // indirect
|
||||||
golang.org/x/arch v0.3.0 // indirect
|
golang.org/x/arch v0.3.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
|
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
|
||||||
golang.org/x/net v0.24.0 // indirect
|
|
||||||
golang.org/x/sys v0.19.0 // indirect
|
golang.org/x/sys v0.19.0 // indirect
|
||||||
golang.org/x/time v0.5.0 // indirect
|
golang.org/x/time v0.5.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect
|
||||||
|
48
go.sum
48
go.sum
@ -1,7 +1,7 @@
|
|||||||
code.cloudfoundry.org/bytefmt v0.0.0-20240405144452-ebb2996022ca h1:pyybVFhefCroOyqu71Rrnm0eORYFJMvTDsAsyPLjvEI=
|
code.cloudfoundry.org/bytefmt v0.0.0-20240425163905-bcdc1ad063ea h1:1tgMNDgo8PjpsHhlaxdibj28C0WyLeOW2SPJ7GGdc9A=
|
||||||
code.cloudfoundry.org/bytefmt v0.0.0-20240405144452-ebb2996022ca/go.mod h1:21qd3QlOUZT1oQ2RccnLvWRu7CZWSN+3ZlUh2mzoE10=
|
code.cloudfoundry.org/bytefmt v0.0.0-20240425163905-bcdc1ad063ea/go.mod h1:3+xXJBOD8PsGHDqHedtCLalbaVJ+yi1OW+mXx9IcNxI=
|
||||||
code.gitea.io/sdk/gitea v0.17.1 h1:3jCPOG2ojbl8AcfaUCRYLT5MUcBMFwS0OSK2mA5Zok8=
|
code.gitea.io/sdk/gitea v0.18.0 h1:+zZrwVmujIrgobt6wVBWCqITz6bn1aBjnCUHmpZrerI=
|
||||||
code.gitea.io/sdk/gitea v0.17.1/go.mod h1:aCnBqhHpoEWA180gMbaCtdX9Pl6BWBAuuP2miadoTNM=
|
code.gitea.io/sdk/gitea v0.18.0/go.mod h1:IG9xZJoltDNeDSW0qiF2Vqx5orMWa7OhVWrjvrd5NpI=
|
||||||
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
|
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
|
||||||
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||||
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
||||||
@ -57,7 +57,7 @@ github.com/google/go-github/v47 v47.1.0/go.mod h1:VPZBXNbFSJGjyjFRUKo9vZGawTajnW
|
|||||||
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
||||||
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/pprof v0.0.0-20240402174815-29b9bb013b0f h1:f00RU+zOX+B3rLAmMMkzHUF2h1z4DeYR9tTCvEq2REY=
|
github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 h1:k7nVchz72niMH6YLQNvHSdIE7iqsQxK1P41mySCvssg=
|
||||||
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
|
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
|
||||||
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
|
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
|
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
|
||||||
@ -112,7 +112,7 @@ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjY
|
|||||||
github.com/nicksnyder/go-i18n/v2 v2.4.0 h1:3IcvPOAvnCKwNm0TB0dLDTuawWEj+ax/RERNC+diLMM=
|
github.com/nicksnyder/go-i18n/v2 v2.4.0 h1:3IcvPOAvnCKwNm0TB0dLDTuawWEj+ax/RERNC+diLMM=
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.4.0/go.mod h1:nxYSZE9M0bf3Y70gPQjN9ha7XNHX7gMc814+6wVyEI4=
|
github.com/nicksnyder/go-i18n/v2 v2.4.0/go.mod h1:nxYSZE9M0bf3Y70gPQjN9ha7XNHX7gMc814+6wVyEI4=
|
||||||
github.com/onsi/ginkgo/v2 v2.17.1 h1:V++EzdbhI4ZV4ev0UTIj0PzhzOcReJFyJaLjtSF55M8=
|
github.com/onsi/ginkgo/v2 v2.17.1 h1:V++EzdbhI4ZV4ev0UTIj0PzhzOcReJFyJaLjtSF55M8=
|
||||||
github.com/onsi/gomega v1.32.0 h1:JRYU78fJ1LPxlckP6Txi/EYqJvjtMrDC04/MM5XRHPk=
|
github.com/onsi/gomega v1.33.0 h1:snPCflnZrpMsy94p4lXVEkHo12lmPnc3vY5XBbreexE=
|
||||||
github.com/ory/graceful v0.1.3 h1:FaeXcHZh168WzS+bqruqWEw/HgXWLdNv2nJ+fbhxbhc=
|
github.com/ory/graceful v0.1.3 h1:FaeXcHZh168WzS+bqruqWEw/HgXWLdNv2nJ+fbhxbhc=
|
||||||
github.com/ory/graceful v0.1.3/go.mod h1:4zFz687IAF7oNHHiB586U4iL+/4aV09o/PYLE34t2bA=
|
github.com/ory/graceful v0.1.3/go.mod h1:4zFz687IAF7oNHHiB586U4iL+/4aV09o/PYLE34t2bA=
|
||||||
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
||||||
@ -168,9 +168,8 @@ github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6
|
|||||||
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
|
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
|
||||||
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
|
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
|
||||||
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||||
github.com/xanzy/go-gitlab v0.102.0 h1:ExHuJ1OTQ2yt25zBMMj0G96ChBirGYv8U7HyUiYkZ+4=
|
github.com/xanzy/go-gitlab v0.103.0 h1:J9pTQoq0GsEFqzd6srCM1QfdfKAxSNz6mT6ntrpNF2w=
|
||||||
github.com/xanzy/go-gitlab v0.102.0/go.mod h1:ETg8tcj4OhrB84UEgeE8dSuV/0h4BBL1uOV/qK0vlyI=
|
github.com/xanzy/go-gitlab v0.103.0/go.mod h1:ETg8tcj4OhrB84UEgeE8dSuV/0h4BBL1uOV/qK0vlyI=
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
|
||||||
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
|
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
|
||||||
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||||
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
|
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
|
||||||
@ -182,27 +181,16 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
|
|||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
|
||||||
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
|
||||||
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
|
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
|
||||||
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
|
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
|
||||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
|
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
|
||||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
|
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
|
||||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
|
||||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
|
||||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
|
||||||
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
|
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
|
||||||
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
|
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
|
||||||
golang.org/x/oauth2 v0.19.0 h1:9+E/EZBCbTLNrbN35fHv/a/d/mOBatymz1zbtQrXpIg=
|
golang.org/x/oauth2 v0.19.0 h1:9+E/EZBCbTLNrbN35fHv/a/d/mOBatymz1zbtQrXpIg=
|
||||||
golang.org/x/oauth2 v0.19.0/go.mod h1:vYi7skDa1x015PmRRYZ7+s1cWyPgrPiSYRe4rnsexc8=
|
golang.org/x/oauth2 v0.19.0/go.mod h1:vYi7skDa1x015PmRRYZ7+s1cWyPgrPiSYRe4rnsexc8=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
@ -211,37 +199,21 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
|
||||||
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
||||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
|
||||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
|
||||||
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
|
|
||||||
golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q=
|
golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
|
||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
|
||||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
|
||||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
|
||||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
|
||||||
golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY=
|
golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de h1:cZGRis4/ot9uVm639a+rHCUaG0JJHEsdyzSQTMX+suY=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de h1:cZGRis4/ot9uVm639a+rHCUaG0JJHEsdyzSQTMX+suY=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:H4O17MA/PE9BsGx3w+a+W2VOLLD1Qf7oJneAoU6WktY=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:H4O17MA/PE9BsGx3w+a+W2VOLLD1Qf7oJneAoU6WktY=
|
||||||
@ -267,8 +239,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
|||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gorm.io/driver/sqlite v1.5.5 h1:7MDMtUZhV065SilG62E0MquljeArQZNfJnjd9i9gx3E=
|
gorm.io/driver/sqlite v1.5.5 h1:7MDMtUZhV065SilG62E0MquljeArQZNfJnjd9i9gx3E=
|
||||||
gorm.io/driver/sqlite v1.5.5/go.mod h1:6NgQ7sQWAIFsPrJJl1lSNSu2TABh0ZZ/zm5fosATavE=
|
gorm.io/driver/sqlite v1.5.5/go.mod h1:6NgQ7sQWAIFsPrJJl1lSNSu2TABh0ZZ/zm5fosATavE=
|
||||||
gorm.io/gorm v1.25.9 h1:wct0gxZIELDk8+ZqF/MVnHLkA1rvYlBWUMv2EdsK1g8=
|
gorm.io/gorm v1.25.10 h1:dQpO+33KalOA+aFYGlK+EfxcI5MbO7EP2yYygwh9h+s=
|
||||||
gorm.io/gorm v1.25.9/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
gorm.io/gorm v1.25.10/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||||
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
|
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
|
||||||
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
|
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
|
||||||
|
@ -125,9 +125,20 @@ type Config struct {
|
|||||||
WebhookRequestBody string
|
WebhookRequestBody string
|
||||||
WebhookHeaders string
|
WebhookHeaders string
|
||||||
MaxRetries uint32
|
MaxRetries uint32
|
||||||
|
Profiles map[string]DDNSProfile
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DDNSProfile struct {
|
||||||
|
Provider string
|
||||||
|
AccessID string
|
||||||
|
AccessSecret string
|
||||||
|
WebhookURL string
|
||||||
|
WebhookMethod string
|
||||||
|
WebhookRequestBody string
|
||||||
|
WebhookHeaders string
|
||||||
|
}
|
||||||
|
|
||||||
// Read 读取配置文件并应用
|
// Read 读取配置文件并应用
|
||||||
func (c *Config) Read(path string) error {
|
func (c *Config) Read(path string) error {
|
||||||
c.v = viper.New()
|
c.v = viper.New()
|
||||||
@ -166,12 +177,6 @@ func (c *Config) Read(path string) error {
|
|||||||
if c.AvgPingCount == 0 {
|
if c.AvgPingCount == 0 {
|
||||||
c.AvgPingCount = 2
|
c.AvgPingCount = 2
|
||||||
}
|
}
|
||||||
if c.DDNS.Provider == "" {
|
|
||||||
c.DDNS.Provider = "webhook"
|
|
||||||
}
|
|
||||||
if c.DDNS.WebhookMethod == "" {
|
|
||||||
c.DDNS.WebhookMethod = "POST"
|
|
||||||
}
|
|
||||||
if c.DDNS.MaxRetries == 0 {
|
if c.DDNS.MaxRetries == 0 {
|
||||||
c.DDNS.MaxRetries = 3
|
c.DDNS.MaxRetries = 3
|
||||||
}
|
}
|
||||||
|
@ -19,4 +19,3 @@ type MonitorHistory struct {
|
|||||||
Down uint64 // 检查状态异常计数
|
Down uint64 // 检查状态异常计数
|
||||||
Data string
|
Data string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,6 +21,7 @@ type Server struct {
|
|||||||
EnableIPv4 bool // 是否启用DDNS IPv4
|
EnableIPv4 bool // 是否启用DDNS IPv4
|
||||||
EnableIpv6 bool // 是否启用DDNS IPv6
|
EnableIpv6 bool // 是否启用DDNS IPv6
|
||||||
DDNSDomain string // DDNS中的前缀 如基础域名为abc.oracle DDNSName为mjj 就会把mjj.abc.oracle解析服务器IP 为空则停用
|
DDNSDomain string // DDNS中的前缀 如基础域名为abc.oracle DDNSName为mjj 就会把mjj.abc.oracle解析服务器IP 为空则停用
|
||||||
|
DDNSProfile string // DDNS配置
|
||||||
|
|
||||||
Host *Host `gorm:"-"`
|
Host *Host `gorm:"-"`
|
||||||
State *HostState `gorm:"-"`
|
State *HostState `gorm:"-"`
|
||||||
@ -56,5 +57,6 @@ func (s Server) Marshal() template.JS {
|
|||||||
note, _ := utils.Json.Marshal(s.Note)
|
note, _ := utils.Json.Marshal(s.Note)
|
||||||
secret, _ := utils.Json.Marshal(s.Secret)
|
secret, _ := utils.Json.Marshal(s.Secret)
|
||||||
ddnsDomain, _ := utils.Json.Marshal(s.DDNSDomain)
|
ddnsDomain, _ := utils.Json.Marshal(s.DDNSDomain)
|
||||||
return template.JS(fmt.Sprintf(`{"ID":%d,"Name":%s,"Secret":%s,"DisplayIndex":%d,"Tag":%s,"Note":%s,"HideForGuest": %s,"EnableDDNS": %s,"EnableIPv4": %s,"EnableIpv6": %s,"DDNSDomain": %s}`, s.ID, name, secret, s.DisplayIndex, tag, note, boolToString(s.HideForGuest), boolToString(s.EnableDDNS), boolToString(s.EnableIPv4), boolToString(s.EnableIpv6), ddnsDomain)) // #nosec
|
ddnsProfile, _ := utils.Json.Marshal(s.DDNSProfile)
|
||||||
|
return template.JS(fmt.Sprintf(`{"ID":%d,"Name":%s,"Secret":%s,"DisplayIndex":%d,"Tag":%s,"Note":%s,"HideForGuest": %s,"EnableDDNS": %s,"EnableIPv4": %s,"EnableIpv6": %s,"DDNSDomain": %s,"DDNSProfile": %s}`, s.ID, name, secret, s.DisplayIndex, tag, note, boolToString(s.HideForGuest), boolToString(s.EnableDDNS), boolToString(s.EnableIPv4), boolToString(s.EnableIpv6), ddnsDomain, ddnsProfile)) // #nosec
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package ddns
|
package ddns
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"golang.org/x/net/publicsuffix"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
@ -33,29 +33,8 @@ func SetStringHeadersToRequest(req *http.Request, headers []string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SplitDomain 分割域名为前缀和一级域名
|
// SplitDomain 分割域名为前缀和一级域名
|
||||||
func SplitDomain(domain string) (prefix string, topLevelDomain string) {
|
func SplitDomain(domain string) (prefix string, realDomain string) {
|
||||||
// 带有二级TLD的一些常见例子,需要特别处理
|
realDomain, _ = publicsuffix.EffectiveTLDPlusOne(domain)
|
||||||
secondLevelTLDs := map[string]bool{
|
prefix = domain[:len(domain)-len(realDomain)-1]
|
||||||
".co.uk": true, ".com.cn": true, ".gov.cn": true, ".net.cn": true, ".org.cn": true,
|
return prefix, realDomain
|
||||||
}
|
|
||||||
|
|
||||||
// 分割域名为"."的各部分
|
|
||||||
parts := strings.Split(domain, ".")
|
|
||||||
|
|
||||||
// 处理特殊情况,例如 ".co.uk"
|
|
||||||
for i := len(parts) - 2; i > 0; i-- {
|
|
||||||
potentialTLD := fmt.Sprintf(".%s.%s", parts[i], parts[i+1])
|
|
||||||
if secondLevelTLDs[potentialTLD] {
|
|
||||||
if i > 1 {
|
|
||||||
return strings.Join(parts[:i-1], "."), strings.Join(parts[i-1:], ".")
|
|
||||||
}
|
|
||||||
return "", domain // 当域名仅为二级TLD时,无前缀
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 常规处理,查找最后一个"."前的所有内容作为前缀
|
|
||||||
if len(parts) > 2 {
|
|
||||||
return strings.Join(parts[:len(parts)-2], "."), strings.Join(parts[len(parts)-2:], ".")
|
|
||||||
}
|
|
||||||
return "", domain // 当域名不包含子域名时,无前缀
|
|
||||||
}
|
}
|
||||||
|
228
pkg/ddns/tencentcloud.go
Normal file
228
pkg/ddns/tencentcloud.go
Normal file
@ -0,0 +1,228 @@
|
|||||||
|
package ddns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
url = "https://dnspod.tencentcloudapi.com"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ProviderTencentCloud struct {
|
||||||
|
SecretID string
|
||||||
|
SecretKey string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider ProviderTencentCloud) UpdateDomain(domainConfig *DomainConfig) bool {
|
||||||
|
if domainConfig == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 当IPv4和IPv6同时成功才算作成功
|
||||||
|
var resultV4 = true
|
||||||
|
var resultV6 = true
|
||||||
|
if domainConfig.EnableIPv4 {
|
||||||
|
if !provider.addDomainRecord(domainConfig, true) {
|
||||||
|
resultV4 = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if domainConfig.EnableIpv6 {
|
||||||
|
if !provider.addDomainRecord(domainConfig, false) {
|
||||||
|
resultV6 = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resultV4 && resultV6
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider ProviderTencentCloud) addDomainRecord(domainConfig *DomainConfig, isIpv4 bool) bool {
|
||||||
|
record, err := provider.findDNSRecord(domainConfig.FullDomain, isIpv4)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("查找 DNS 记录时出错: %s\n", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if errResponse, ok := record["Error"].(map[string]interface{}); ok {
|
||||||
|
if errCode, ok := errResponse["Code"].(string); ok && errCode == "ResourceNotFound.NoDataOfRecord" { // 没有找到 DNS 记录
|
||||||
|
// 添加 DNS 记录
|
||||||
|
return provider.createDNSRecord(domainConfig.FullDomain, domainConfig, isIpv4)
|
||||||
|
} else {
|
||||||
|
log.Printf("查询 DNS 记录时出错,错误代码为: %s\n", errCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 默认情况下更新 DNS 记录
|
||||||
|
return provider.updateDNSRecord(domainConfig.FullDomain, record["RecordList"].([]interface{})[0].(map[string]interface{})["RecordId"].(float64), domainConfig, isIpv4)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider ProviderTencentCloud) findDNSRecord(domain string, isIPv4 bool) (map[string]interface{}, error) {
|
||||||
|
var ipType = "A"
|
||||||
|
if !isIPv4 {
|
||||||
|
ipType = "AAAA"
|
||||||
|
}
|
||||||
|
_, realDomain := SplitDomain(domain)
|
||||||
|
prefix, _ := SplitDomain(domain)
|
||||||
|
data := map[string]interface{}{
|
||||||
|
"RecordType": ipType,
|
||||||
|
"Domain": realDomain,
|
||||||
|
"RecordLine": "默认",
|
||||||
|
"Subdomain": prefix,
|
||||||
|
}
|
||||||
|
jsonData, _ := json.Marshal(data)
|
||||||
|
body, err := provider.sendRequest("DescribeRecordList", jsonData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var res map[string]interface{}
|
||||||
|
err = json.Unmarshal(body, &res)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result := res["Response"].(map[string]interface{})
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider ProviderTencentCloud) createDNSRecord(domain string, domainConfig *DomainConfig, isIPv4 bool) bool {
|
||||||
|
var ipType = "A"
|
||||||
|
var ipAddr = domainConfig.Ipv4Addr
|
||||||
|
if !isIPv4 {
|
||||||
|
ipType = "AAAA"
|
||||||
|
ipAddr = domainConfig.Ipv6Addr
|
||||||
|
}
|
||||||
|
_, realDomain := SplitDomain(domain)
|
||||||
|
prefix, _ := SplitDomain(domain)
|
||||||
|
data := map[string]interface{}{
|
||||||
|
"RecordType": ipType,
|
||||||
|
"RecordLine": "默认",
|
||||||
|
"Domain": realDomain,
|
||||||
|
"SubDomain": prefix,
|
||||||
|
"Value": ipAddr,
|
||||||
|
"TTL": 600,
|
||||||
|
}
|
||||||
|
jsonData, _ := json.Marshal(data)
|
||||||
|
_, err := provider.sendRequest("CreateRecord", jsonData)
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider ProviderTencentCloud) updateDNSRecord(domain string, recordID float64, domainConfig *DomainConfig, isIPv4 bool) bool {
|
||||||
|
var ipType = "A"
|
||||||
|
var ipAddr = domainConfig.Ipv4Addr
|
||||||
|
if !isIPv4 {
|
||||||
|
ipType = "AAAA"
|
||||||
|
ipAddr = domainConfig.Ipv6Addr
|
||||||
|
}
|
||||||
|
_, realDomain := SplitDomain(domain)
|
||||||
|
prefix, _ := SplitDomain(domain)
|
||||||
|
data := map[string]interface{}{
|
||||||
|
"RecordType": ipType,
|
||||||
|
"RecordLine": "默认",
|
||||||
|
"Domain": realDomain,
|
||||||
|
"SubDomain": prefix,
|
||||||
|
"Value": ipAddr,
|
||||||
|
"TTL": 600,
|
||||||
|
"RecordId": recordID,
|
||||||
|
}
|
||||||
|
jsonData, _ := json.Marshal(data)
|
||||||
|
_, err := provider.sendRequest("ModifyRecord", jsonData)
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 以下为辅助方法,如发送 HTTP 请求等
|
||||||
|
func (provider ProviderTencentCloud) sendRequest(action string, data []byte) ([]byte, error) {
|
||||||
|
client := &http.Client{}
|
||||||
|
req, err := http.NewRequest("POST", url, bytes.NewBuffer(data))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
req.Header.Set("X-TC-Version", "2021-03-23")
|
||||||
|
|
||||||
|
provider.signRequest(provider.SecretID, provider.SecretKey, req, action, string(data))
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer func(Body io.ReadCloser) {
|
||||||
|
err := Body.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("NEZHA>> 无法关闭HTTP响应体流: %s\n", err.Error())
|
||||||
|
}
|
||||||
|
}(resp.Body)
|
||||||
|
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return body, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/jeessy2/ddns-go/blob/master/util/tencent_cloud_signer.go
|
||||||
|
|
||||||
|
func (provider ProviderTencentCloud) sha256hex(s string) string {
|
||||||
|
b := sha256.Sum256([]byte(s))
|
||||||
|
return hex.EncodeToString(b[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider ProviderTencentCloud) hmacsha256(s, key string) string {
|
||||||
|
hashed := hmac.New(sha256.New, []byte(key))
|
||||||
|
hashed.Write([]byte(s))
|
||||||
|
return string(hashed.Sum(nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider ProviderTencentCloud) WriteString(strs ...string) string {
|
||||||
|
var b strings.Builder
|
||||||
|
for _, str := range strs {
|
||||||
|
b.WriteString(str)
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider ProviderTencentCloud) signRequest(secretId string, secretKey string, r *http.Request, action string, payload string) {
|
||||||
|
algorithm := "TC3-HMAC-SHA256"
|
||||||
|
service := "dnspod"
|
||||||
|
host := provider.WriteString(service, ".tencentcloudapi.com")
|
||||||
|
timestamp := time.Now().Unix()
|
||||||
|
timestampStr := strconv.FormatInt(timestamp, 10)
|
||||||
|
|
||||||
|
// 步骤 1:拼接规范请求串
|
||||||
|
canonicalHeaders := provider.WriteString("content-type:application/json\nhost:", host, "\nx-tc-action:", strings.ToLower(action), "\n")
|
||||||
|
signedHeaders := "content-type;host;x-tc-action"
|
||||||
|
hashedRequestPayload := provider.sha256hex(payload)
|
||||||
|
canonicalRequest := provider.WriteString("POST\n/\n\n", canonicalHeaders, "\n", signedHeaders, "\n", hashedRequestPayload)
|
||||||
|
|
||||||
|
// 步骤 2:拼接待签名字符串
|
||||||
|
date := time.Unix(timestamp, 0).UTC().Format("2006-01-02")
|
||||||
|
credentialScope := provider.WriteString(date, "/", service, "/tc3_request")
|
||||||
|
hashedCanonicalRequest := provider.sha256hex(canonicalRequest)
|
||||||
|
string2sign := provider.WriteString(algorithm, "\n", timestampStr, "\n", credentialScope, "\n", hashedCanonicalRequest)
|
||||||
|
|
||||||
|
// 步骤 3:计算签名
|
||||||
|
secretDate := provider.hmacsha256(date, provider.WriteString("TC3", secretKey))
|
||||||
|
secretService := provider.hmacsha256(service, secretDate)
|
||||||
|
secretSigning := provider.hmacsha256("tc3_request", secretService)
|
||||||
|
signature := hex.EncodeToString([]byte(provider.hmacsha256(string2sign, secretSigning)))
|
||||||
|
|
||||||
|
// 步骤 4:拼接 Authorization
|
||||||
|
authorization := provider.WriteString(algorithm, " Credential=", secretId, "/", credentialScope, ", SignedHeaders=", signedHeaders, ", Signature=", signature)
|
||||||
|
|
||||||
|
r.Header.Add("Authorization", authorization)
|
||||||
|
r.Header.Set("Host", host)
|
||||||
|
r.Header.Set("X-TC-Action", action)
|
||||||
|
r.Header.Add("X-TC-Timestamp", timestampStr)
|
||||||
|
}
|
3
resource/l10n/en-US.toml
vendored
3
resource/l10n/en-US.toml
vendored
@ -628,6 +628,9 @@ other = "Enable DDNS IPv6"
|
|||||||
[DDNSDomain]
|
[DDNSDomain]
|
||||||
other = "DDNS Domain"
|
other = "DDNS Domain"
|
||||||
|
|
||||||
|
[DDNSProfile]
|
||||||
|
other = "DDNS Profile Name"
|
||||||
|
|
||||||
[Feature]
|
[Feature]
|
||||||
other = "Feature"
|
other = "Feature"
|
||||||
|
|
||||||
|
3
resource/l10n/es-ES.toml
vendored
3
resource/l10n/es-ES.toml
vendored
@ -628,6 +628,9 @@ other = "Habilitar DDNS IPv6"
|
|||||||
[DDNSDomain]
|
[DDNSDomain]
|
||||||
other = "Dominio DDNS"
|
other = "Dominio DDNS"
|
||||||
|
|
||||||
|
[DDNSProfile]
|
||||||
|
other = "Nombre del perfil de DDNS"
|
||||||
|
|
||||||
[Feature]
|
[Feature]
|
||||||
other = "Característica"
|
other = "Característica"
|
||||||
|
|
||||||
|
3
resource/l10n/zh-CN.toml
vendored
3
resource/l10n/zh-CN.toml
vendored
@ -628,6 +628,9 @@ other = "启用DDNS IPv6"
|
|||||||
[DDNSDomain]
|
[DDNSDomain]
|
||||||
other = "DDNS域名"
|
other = "DDNS域名"
|
||||||
|
|
||||||
|
[DDNSProfile]
|
||||||
|
other = "DDNS配置名"
|
||||||
|
|
||||||
[Feature]
|
[Feature]
|
||||||
other = "功能"
|
other = "功能"
|
||||||
|
|
||||||
|
3
resource/l10n/zh-TW.toml
vendored
3
resource/l10n/zh-TW.toml
vendored
@ -628,6 +628,9 @@ other = "啟用DDNS IPv6"
|
|||||||
[DDNSDomain]
|
[DDNSDomain]
|
||||||
other = "DDNS網域"
|
other = "DDNS網域"
|
||||||
|
|
||||||
|
[DDNSProfile]
|
||||||
|
other = "DDNS設定名"
|
||||||
|
|
||||||
[Feature]
|
[Feature]
|
||||||
other = "功能"
|
other = "功能"
|
||||||
|
|
||||||
|
@ -303,6 +303,7 @@ function addOrEditServer(server, conf) {
|
|||||||
modal.find("input[name=name]").val(server ? server.Name : null);
|
modal.find("input[name=name]").val(server ? server.Name : null);
|
||||||
modal.find("input[name=Tag]").val(server ? server.Tag : null);
|
modal.find("input[name=Tag]").val(server ? server.Tag : null);
|
||||||
modal.find("input[name=DDNSDomain]").val(server ? server.DDNSDomain : null);
|
modal.find("input[name=DDNSDomain]").val(server ? server.DDNSDomain : null);
|
||||||
|
modal.find("input[name=DDNSProfile]").val(server ? server.DDNSProfile : null);
|
||||||
modal
|
modal
|
||||||
.find("input[name=DisplayIndex]")
|
.find("input[name=DisplayIndex]")
|
||||||
.val(server ? server.DisplayIndex : null);
|
.val(server ? server.DisplayIndex : null);
|
||||||
|
4
resource/template/component/server.html
vendored
4
resource/template/component/server.html
vendored
@ -48,6 +48,10 @@
|
|||||||
<label>{{tr "DDNSDomain"}}</label>
|
<label>{{tr "DDNSDomain"}}</label>
|
||||||
<input type="text" name="DDNSDomain" placeholder="{{tr "DDNSDomain"}}">
|
<input type="text" name="DDNSDomain" placeholder="{{tr "DDNSDomain"}}">
|
||||||
</div>
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label>{{tr "DDNSProfile"}}</label>
|
||||||
|
<input type="text" name="DDNSProfile" placeholder="{{tr "DDNSProfile"}}">
|
||||||
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label>{{tr "Note"}}</label>
|
<label>{{tr "Note"}}</label>
|
||||||
<textarea name="Note"></textarea>
|
<textarea name="Note"></textarea>
|
||||||
|
@ -14,7 +14,7 @@ site:
|
|||||||
theme: "default"
|
theme: "default"
|
||||||
ddns:
|
ddns:
|
||||||
enable: false
|
enable: false
|
||||||
provider: "webhook"
|
provider: "webhook" # 如需使用多配置功能,请把此项留空
|
||||||
accessid: ""
|
accessid: ""
|
||||||
accesssecret: ""
|
accesssecret: ""
|
||||||
webhookmethod: ""
|
webhookmethod: ""
|
||||||
@ -22,3 +22,12 @@ ddns:
|
|||||||
webhookrequestbody: ""
|
webhookrequestbody: ""
|
||||||
webhookheaders: ""
|
webhookheaders: ""
|
||||||
maxretries: 3
|
maxretries: 3
|
||||||
|
profiles:
|
||||||
|
example:
|
||||||
|
provider: ""
|
||||||
|
accessid: ""
|
||||||
|
accesssecret: ""
|
||||||
|
webhookmethod: ""
|
||||||
|
webhookurl: ""
|
||||||
|
webhookrequestbody: ""
|
||||||
|
webhookheaders: ""
|
@ -106,6 +106,7 @@ func (s *NezhaHandler) ReportSystemState(c context.Context, r *pb.State) (*pb.Re
|
|||||||
|
|
||||||
func (s *NezhaHandler) ReportSystemInfo(c context.Context, r *pb.Host) (*pb.Receipt, error) {
|
func (s *NezhaHandler) ReportSystemInfo(c context.Context, r *pb.Host) (*pb.Receipt, error) {
|
||||||
var clientID uint64
|
var clientID uint64
|
||||||
|
var provider ddns.Provider
|
||||||
var err error
|
var err error
|
||||||
if clientID, err = s.Auth.Check(c); err != nil {
|
if clientID, err = s.Auth.Check(c); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -122,7 +123,11 @@ func (s *NezhaHandler) ReportSystemInfo(c context.Context, r *pb.Host) (*pb.Rece
|
|||||||
singleton.ServerList[clientID].Host.IP != host.IP {
|
singleton.ServerList[clientID].Host.IP != host.IP {
|
||||||
|
|
||||||
serverDomain := singleton.ServerList[clientID].DDNSDomain
|
serverDomain := singleton.ServerList[clientID].DDNSDomain
|
||||||
provider, err := singleton.GetDDNSProviderFromString(singleton.Conf.DDNS.Provider)
|
if singleton.Conf.DDNS.Provider == "" {
|
||||||
|
provider, err = singleton.GetDDNSProviderFromProfile(singleton.ServerList[clientID].DDNSProfile)
|
||||||
|
} else {
|
||||||
|
provider, err = singleton.GetDDNSProviderFromString(singleton.Conf.DDNS.Provider)
|
||||||
|
}
|
||||||
if err == nil && serverDomain != "" {
|
if err == nil && serverDomain != "" {
|
||||||
ipv4, ipv6, _ := utils.SplitIPAddr(host.IP)
|
ipv4, ipv6, _ := utils.SplitIPAddr(host.IP)
|
||||||
maxRetries := int(singleton.Conf.DDNS.MaxRetries)
|
maxRetries := int(singleton.Conf.DDNS.MaxRetries)
|
||||||
@ -137,7 +142,7 @@ func (s *NezhaHandler) ReportSystemInfo(c context.Context, r *pb.Host) (*pb.Rece
|
|||||||
|
|
||||||
} else {
|
} else {
|
||||||
// 虽然会在启动时panic, 可以断言不会走这个分支, 但是考虑到动态加载配置或者其它情况, 这里输出一下方便检查奇奇怪怪的BUG
|
// 虽然会在启动时panic, 可以断言不会走这个分支, 但是考虑到动态加载配置或者其它情况, 这里输出一下方便检查奇奇怪怪的BUG
|
||||||
log.Printf("NEZHA>> 未找到对应的DDNS提供者(%s), 请前往config.yml检查你的设置\n", singleton.Conf.DDNS.Provider)
|
log.Printf("NEZHA>> 未找到对应的DDNS配置(%s), 或者是provider填写不正确, 请前往config.yml检查你的设置\n", singleton.ServerList[clientID].DDNSProfile)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -37,6 +37,52 @@ func GetDDNSProviderFromString(provider string) (ddns2.Provider, error) {
|
|||||||
return ddns2.ProviderCloudflare{
|
return ddns2.ProviderCloudflare{
|
||||||
Secret: Conf.DDNS.AccessSecret,
|
Secret: Conf.DDNS.AccessSecret,
|
||||||
}, nil
|
}, nil
|
||||||
|
case "tencentcloud":
|
||||||
|
return ddns2.ProviderTencentCloud{
|
||||||
|
SecretID: Conf.DDNS.AccessID,
|
||||||
|
SecretKey: Conf.DDNS.AccessSecret,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
return ddns2.ProviderDummy{}, errors.New(fmt.Sprintf("无法找到配置的DDNS提供者%s", Conf.DDNS.Provider))
|
return ddns2.ProviderDummy{}, errors.New(fmt.Sprintf("无法找到配置的DDNS提供者%s", Conf.DDNS.Provider))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetDDNSProviderFromProfile(profileName string) (ddns2.Provider, error) {
|
||||||
|
profile, ok := Conf.DDNS.Profiles[profileName]
|
||||||
|
if !ok {
|
||||||
|
return ddns2.ProviderDummy{}, errors.New(fmt.Sprintf("未找到配置项 %s", profileName))
|
||||||
|
}
|
||||||
|
|
||||||
|
switch profile.Provider {
|
||||||
|
case "webhook":
|
||||||
|
return ddns2.ProviderWebHook{
|
||||||
|
URL: profile.WebhookURL,
|
||||||
|
RequestMethod: profile.WebhookMethod,
|
||||||
|
RequestBody: profile.WebhookRequestBody,
|
||||||
|
RequestHeader: profile.WebhookHeaders,
|
||||||
|
}, nil
|
||||||
|
case "dummy":
|
||||||
|
return ddns2.ProviderDummy{}, nil
|
||||||
|
case "cloudflare":
|
||||||
|
return ddns2.ProviderCloudflare{
|
||||||
|
Secret: profile.AccessSecret,
|
||||||
|
}, nil
|
||||||
|
case "tencentcloud":
|
||||||
|
return ddns2.ProviderTencentCloud{
|
||||||
|
SecretID: profile.AccessID,
|
||||||
|
SecretKey: profile.AccessSecret,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
return ddns2.ProviderDummy{}, errors.New(fmt.Sprintf("无法找到配置的DDNS提供者%s", profile.Provider))
|
||||||
|
}
|
||||||
|
|
||||||
|
func ValidateDDNSProvidersFromProfiles() error {
|
||||||
|
validProviders := map[string]bool{"webhook": true, "dummy": true, "cloudflare": true, "tencentcloud": true}
|
||||||
|
providers := make(map[string]string)
|
||||||
|
for profileName, profile := range Conf.DDNS.Profiles {
|
||||||
|
if _, ok := validProviders[profile.Provider]; !ok {
|
||||||
|
return errors.New(fmt.Sprintf("无法找到配置的DDNS提供者%s", profile.Provider))
|
||||||
|
}
|
||||||
|
providers[profileName] = profile.Provider
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -52,12 +52,16 @@ func InitConfigFromPath(path string) {
|
|||||||
|
|
||||||
// ValidateConfig 验证配置文件有效性
|
// ValidateConfig 验证配置文件有效性
|
||||||
func ValidateConfig() {
|
func ValidateConfig() {
|
||||||
// 如果DDNS启用则检查Provider是否存在, 不存在直接退出
|
var err error
|
||||||
if Conf.DDNS.Enable {
|
if Conf.DDNS.Provider == "" {
|
||||||
_, err := GetDDNSProviderFromString(Conf.DDNS.Provider)
|
err = ValidateDDNSProvidersFromProfiles()
|
||||||
|
} else {
|
||||||
|
_, err = GetDDNSProviderFromString(Conf.DDNS.Provider)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
if Conf.DDNS.Enable {
|
||||||
if Conf.DDNS.MaxRetries < 1 || Conf.DDNS.MaxRetries > 10 {
|
if Conf.DDNS.MaxRetries < 1 || Conf.DDNS.MaxRetries > 10 {
|
||||||
panic(fmt.Errorf("DDNS.MaxRetries值域为[1, 10]的整数, 当前为 %d", Conf.DDNS.MaxRetries))
|
panic(fmt.Errorf("DDNS.MaxRetries值域为[1, 10]的整数, 当前为 %d", Conf.DDNS.MaxRetries))
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user