diff --git a/cmd/dashboard/controller/member_api.go b/cmd/dashboard/controller/member_api.go index ca318a0c2c..7e27ed5ab7 100644 --- a/cmd/dashboard/controller/member_api.go +++ b/cmd/dashboard/controller/member_api.go @@ -304,6 +304,7 @@ type serverForm struct { EnableIPv4 string EnableIpv6 string DDNSDomain string + DDNSProfile string } func (ma *memberAPI) addOrEditServer(c *gin.Context) { @@ -323,6 +324,7 @@ func (ma *memberAPI) addOrEditServer(c *gin.Context) { s.EnableIPv4 = sf.EnableIPv4 == "on" s.EnableIpv6 = sf.EnableIpv6 == "on" s.DDNSDomain = sf.DDNSDomain + s.DDNSProfile = sf.DDNSProfile if s.ID == 0 { s.Secret, err = utils.GenerateRandomString(18) if err == nil { diff --git a/go.mod b/go.mod index f3af79d15f..299f01ad7a 100644 --- a/go.mod +++ b/go.mod @@ -3,8 +3,8 @@ module github.com/naiba/nezha go 1.20 require ( - code.cloudfoundry.org/bytefmt v0.0.0-20240405144452-ebb2996022ca - code.gitea.io/sdk/gitea v0.17.1 + code.cloudfoundry.org/bytefmt v0.0.0-20240425163905-bcdc1ad063ea + code.gitea.io/sdk/gitea v0.18.0 github.com/BurntSushi/toml v1.3.2 github.com/gin-contrib/pprof v1.4.0 github.com/gin-gonic/gin v1.9.1 @@ -20,15 +20,16 @@ require ( github.com/samber/lo v1.39.0 github.com/spf13/pflag v1.0.5 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/net v0.24.0 golang.org/x/oauth2 v0.19.0 golang.org/x/sync v0.7.0 golang.org/x/text v0.14.0 google.golang.org/grpc v1.63.0 google.golang.org/protobuf v1.33.0 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 ) @@ -73,7 +74,6 @@ require ( go.uber.org/multierr v1.9.0 // indirect golang.org/x/arch v0.3.0 // 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/time v0.5.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect diff --git a/go.sum b/go.sum index a3afe98143..9d2afd5eb4 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ -code.cloudfoundry.org/bytefmt v0.0.0-20240405144452-ebb2996022ca h1:pyybVFhefCroOyqu71Rrnm0eORYFJMvTDsAsyPLjvEI= -code.cloudfoundry.org/bytefmt v0.0.0-20240405144452-ebb2996022ca/go.mod h1:21qd3QlOUZT1oQ2RccnLvWRu7CZWSN+3ZlUh2mzoE10= -code.gitea.io/sdk/gitea v0.17.1 h1:3jCPOG2ojbl8AcfaUCRYLT5MUcBMFwS0OSK2mA5Zok8= -code.gitea.io/sdk/gitea v0.17.1/go.mod h1:aCnBqhHpoEWA180gMbaCtdX9Pl6BWBAuuP2miadoTNM= +code.cloudfoundry.org/bytefmt v0.0.0-20240425163905-bcdc1ad063ea h1:1tgMNDgo8PjpsHhlaxdibj28C0WyLeOW2SPJ7GGdc9A= +code.cloudfoundry.org/bytefmt v0.0.0-20240425163905-bcdc1ad063ea/go.mod h1:3+xXJBOD8PsGHDqHedtCLalbaVJ+yi1OW+mXx9IcNxI= +code.gitea.io/sdk/gitea v0.18.0 h1:+zZrwVmujIrgobt6wVBWCqITz6bn1aBjnCUHmpZrerI= +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/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= 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/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= 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/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= 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/go.mod h1:nxYSZE9M0bf3Y70gPQjN9ha7XNHX7gMc814+6wVyEI4= 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/go.mod h1:4zFz687IAF7oNHHiB586U4iL+/4aV09o/PYLE34t2bA= 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.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= 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.102.0/go.mod h1:ETg8tcj4OhrB84UEgeE8dSuV/0h4BBL1uOV/qK0vlyI= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/xanzy/go-gitlab v0.103.0 h1:J9pTQoq0GsEFqzd6srCM1QfdfKAxSNz6mT6ntrpNF2w= +github.com/xanzy/go-gitlab v0.103.0/go.mod h1:ETg8tcj4OhrB84UEgeE8dSuV/0h4BBL1uOV/qK0vlyI= 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/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-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-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/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/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-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-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/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/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/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 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-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-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-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.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/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-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/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.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/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 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/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/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= 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= @@ -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= gorm.io/driver/sqlite v1.5.5 h1:7MDMtUZhV065SilG62E0MquljeArQZNfJnjd9i9gx3E= 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.9/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= +gorm.io/gorm v1.25.10 h1:dQpO+33KalOA+aFYGlK+EfxcI5MbO7EP2yYygwh9h+s= +gorm.io/gorm v1.25.10/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= 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/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/model/config.go b/model/config.go index 12953f5be1..90d65f50de 100644 --- a/model/config.go +++ b/model/config.go @@ -125,9 +125,20 @@ type Config struct { WebhookRequestBody string WebhookHeaders string MaxRetries uint32 + Profiles map[string]DDNSProfile } } +type DDNSProfile struct { + Provider string + AccessID string + AccessSecret string + WebhookURL string + WebhookMethod string + WebhookRequestBody string + WebhookHeaders string +} + // Read 读取配置文件并应用 func (c *Config) Read(path string) error { c.v = viper.New() @@ -166,12 +177,6 @@ func (c *Config) Read(path string) error { if c.AvgPingCount == 0 { c.AvgPingCount = 2 } - if c.DDNS.Provider == "" { - c.DDNS.Provider = "webhook" - } - if c.DDNS.WebhookMethod == "" { - c.DDNS.WebhookMethod = "POST" - } if c.DDNS.MaxRetries == 0 { c.DDNS.MaxRetries = 3 } diff --git a/model/monitor_history.go b/model/monitor_history.go index bbf75353f8..5d2d1bbf50 100644 --- a/model/monitor_history.go +++ b/model/monitor_history.go @@ -19,4 +19,3 @@ type MonitorHistory struct { Down uint64 // 检查状态异常计数 Data string } - diff --git a/model/server.go b/model/server.go index 106509ba4b..586ebd1cef 100644 --- a/model/server.go +++ b/model/server.go @@ -21,6 +21,7 @@ type Server struct { EnableIPv4 bool // 是否启用DDNS IPv4 EnableIpv6 bool // 是否启用DDNS IPv6 DDNSDomain string // DDNS中的前缀 如基础域名为abc.oracle DDNSName为mjj 就会把mjj.abc.oracle解析服务器IP 为空则停用 + DDNSProfile string // DDNS配置 Host *Host `gorm:"-"` State *HostState `gorm:"-"` @@ -56,5 +57,6 @@ func (s Server) Marshal() template.JS { note, _ := utils.Json.Marshal(s.Note) secret, _ := utils.Json.Marshal(s.Secret) 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 } diff --git a/pkg/ddns/helper.go b/pkg/ddns/helper.go index e16314f624..a9e909e122 100644 --- a/pkg/ddns/helper.go +++ b/pkg/ddns/helper.go @@ -1,7 +1,7 @@ package ddns import ( - "fmt" + "golang.org/x/net/publicsuffix" "net/http" "strings" ) @@ -33,29 +33,8 @@ func SetStringHeadersToRequest(req *http.Request, headers []string) { } // SplitDomain 分割域名为前缀和一级域名 -func SplitDomain(domain string) (prefix string, topLevelDomain string) { - // 带有二级TLD的一些常见例子,需要特别处理 - secondLevelTLDs := map[string]bool{ - ".co.uk": true, ".com.cn": true, ".gov.cn": true, ".net.cn": true, ".org.cn": true, - } - - // 分割域名为"."的各部分 - 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 // 当域名不包含子域名时,无前缀 +func SplitDomain(domain string) (prefix string, realDomain string) { + realDomain, _ = publicsuffix.EffectiveTLDPlusOne(domain) + prefix = domain[:len(domain)-len(realDomain)-1] + return prefix, realDomain } diff --git a/pkg/ddns/tencentcloud.go b/pkg/ddns/tencentcloud.go new file mode 100644 index 0000000000..7371ccd7f0 --- /dev/null +++ b/pkg/ddns/tencentcloud.go @@ -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) +} diff --git a/resource/l10n/en-US.toml b/resource/l10n/en-US.toml index d1f6710213..690500fede 100644 --- a/resource/l10n/en-US.toml +++ b/resource/l10n/en-US.toml @@ -628,6 +628,9 @@ other = "Enable DDNS IPv6" [DDNSDomain] other = "DDNS Domain" +[DDNSProfile] +other = "DDNS Profile Name" + [Feature] other = "Feature" diff --git a/resource/l10n/es-ES.toml b/resource/l10n/es-ES.toml index de7ead04b9..d30e0b3f33 100644 --- a/resource/l10n/es-ES.toml +++ b/resource/l10n/es-ES.toml @@ -628,6 +628,9 @@ other = "Habilitar DDNS IPv6" [DDNSDomain] other = "Dominio DDNS" +[DDNSProfile] +other = "Nombre del perfil de DDNS" + [Feature] other = "Característica" diff --git a/resource/l10n/zh-CN.toml b/resource/l10n/zh-CN.toml index 89ab1a9439..7638e78b0f 100644 --- a/resource/l10n/zh-CN.toml +++ b/resource/l10n/zh-CN.toml @@ -628,6 +628,9 @@ other = "启用DDNS IPv6" [DDNSDomain] other = "DDNS域名" +[DDNSProfile] +other = "DDNS配置名" + [Feature] other = "功能" diff --git a/resource/l10n/zh-TW.toml b/resource/l10n/zh-TW.toml index 7c94ed3a59..b05caf27c6 100644 --- a/resource/l10n/zh-TW.toml +++ b/resource/l10n/zh-TW.toml @@ -628,6 +628,9 @@ other = "啟用DDNS IPv6" [DDNSDomain] other = "DDNS網域" +[DDNSProfile] +other = "DDNS設定名" + [Feature] other = "功能" diff --git a/resource/static/main.js b/resource/static/main.js index b4503cc37c..9b8f500a25 100644 --- a/resource/static/main.js +++ b/resource/static/main.js @@ -303,6 +303,7 @@ function addOrEditServer(server, conf) { modal.find("input[name=name]").val(server ? server.Name : 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=DDNSProfile]").val(server ? server.DDNSProfile : null); modal .find("input[name=DisplayIndex]") .val(server ? server.DisplayIndex : null); diff --git a/resource/template/component/server.html b/resource/template/component/server.html index b84f7ab4f5..be72c23501 100644 --- a/resource/template/component/server.html +++ b/resource/template/component/server.html @@ -48,6 +48,10 @@ +