Skip to content

Commit eb6dd28

Browse files
authored
refactor: ddns (#414)
* refactor ddns * update webhook
1 parent 64da3c7 commit eb6dd28

File tree

17 files changed

+530
-359
lines changed

17 files changed

+530
-359
lines changed

cmd/dashboard/controller/controller.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package controller
22

33
import (
4-
"encoding/json"
54
"fmt"
65
"html/template"
76
"io/fs"
@@ -277,7 +276,7 @@ func natGateway(c *gin.Context) {
277276
rpc.NezhaHandlerSingleton.CreateStream(streamId)
278277
defer rpc.NezhaHandlerSingleton.CloseStream(streamId)
279278

280-
taskData, err := json.Marshal(model.TaskNAT{
279+
taskData, err := utils.Json.Marshal(model.TaskNAT{
281280
StreamID: streamId,
282281
Host: natConfig.Host,
283282
})

cmd/dashboard/controller/oauth2.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package controller
22

33
import (
44
"context"
5-
"encoding/json"
65
"errors"
76
"fmt"
87
"net/http"
@@ -199,7 +198,7 @@ func (oa *oauth2controller) callback(c *gin.Context) {
199198
if err == nil {
200199
defer resp.Body.Close()
201200
var cloudflareUserInfo *cloudflare.UserInfo
202-
if err := json.NewDecoder(resp.Body).Decode(&cloudflareUserInfo); err == nil {
201+
if err := utils.Json.NewDecoder(resp.Body).Decode(&cloudflareUserInfo); err == nil {
203202
user = cloudflareUserInfo.MapToNezhaUser()
204203
}
205204
}

model/rule.go

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package model
22

33
import (
4+
"slices"
45
"strings"
56
"time"
67

@@ -44,19 +45,6 @@ func percentage(used, total uint64) float64 {
4445
return float64(used) * 100 / float64(total)
4546
}
4647

47-
func maxSliceValue(slice []float64) float64 {
48-
if len(slice) != 0 {
49-
max := slice[0]
50-
for _, val := range slice {
51-
if max < val {
52-
max = val
53-
}
54-
}
55-
return max
56-
}
57-
return 0
58-
}
59-
6048
// Snapshot 未通过规则返回 struct{}{}, 通过返回 nil
6149
func (u *Rule) Snapshot(cycleTransferStats *CycleTransferStats, server *Server, db *gorm.DB) interface{} {
6250
// 监控全部但是排除了此服务器
@@ -145,7 +133,7 @@ func (u *Rule) Snapshot(cycleTransferStats *CycleTransferStats, server *Server,
145133
temp = append(temp, tempStat.Temperature)
146134
}
147135
}
148-
src = maxSliceValue(temp)
136+
src = slices.Max(temp)
149137
}
150138
}
151139

pkg/ddns/cloudflare.go

Lines changed: 133 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -2,169 +2,217 @@ package ddns
22

33
import (
44
"bytes"
5-
"encoding/json"
65
"fmt"
76
"io"
87
"log"
98
"net/http"
9+
"net/url"
10+
11+
"github.com/naiba/nezha/pkg/utils"
1012
)
1113

14+
const baseEndpoint = "https://api.cloudflare.com/client/v4/zones"
15+
1216
type ProviderCloudflare struct {
13-
Secret string
17+
secret string
18+
zoneId string
19+
recordId string
20+
domainConfig *DomainConfig
21+
}
22+
23+
type cfReq struct {
24+
Name string `json:"name"`
25+
Type string `json:"type"`
26+
Content string `json:"content"`
27+
TTL uint32 `json:"ttl"`
28+
Proxied bool `json:"proxied"`
29+
}
30+
31+
type cfResp struct {
32+
Result []struct {
33+
ID string `json:"id"`
34+
Name string `json:"name"`
35+
} `json:"result"`
36+
}
37+
38+
func NewProviderCloudflare(s string) *ProviderCloudflare {
39+
return &ProviderCloudflare{
40+
secret: s,
41+
}
1442
}
1543

16-
func (provider *ProviderCloudflare) UpdateDomain(domainConfig *DomainConfig) bool {
44+
func (provider *ProviderCloudflare) UpdateDomain(domainConfig *DomainConfig) error {
1745
if domainConfig == nil {
18-
return false
46+
return fmt.Errorf("获取 DDNS 配置失败")
1947
}
48+
provider.domainConfig = domainConfig
2049

21-
zoneID, err := provider.getZoneID(domainConfig.FullDomain)
50+
err := provider.getZoneID()
2251
if err != nil {
23-
log.Printf("无法获取 zone ID: %s\n", err)
24-
return false
52+
return fmt.Errorf("无法获取 zone ID: %s", err)
2553
}
2654

2755
// 当IPv4和IPv6同时成功才算作成功
28-
var resultV4 = true
29-
var resultV6 = true
30-
if domainConfig.EnableIPv4 {
31-
if !provider.addDomainRecord(zoneID, domainConfig, true) {
32-
resultV4 = false
56+
if provider.domainConfig.EnableIPv4 {
57+
if err = provider.addDomainRecord(true); err != nil {
58+
return err
3359
}
3460
}
3561

36-
if domainConfig.EnableIpv6 {
37-
if !provider.addDomainRecord(zoneID, domainConfig, false) {
38-
resultV6 = false
62+
if provider.domainConfig.EnableIpv6 {
63+
if err = provider.addDomainRecord(false); err != nil {
64+
return err
3965
}
4066
}
4167

42-
return resultV4 && resultV6
68+
return nil
4369
}
4470

45-
func (provider *ProviderCloudflare) addDomainRecord(zoneID string, domainConfig *DomainConfig, isIpv4 bool) bool {
46-
record, err := provider.findDNSRecord(zoneID, domainConfig.FullDomain, isIpv4)
71+
func (provider *ProviderCloudflare) addDomainRecord(isIpv4 bool) error {
72+
err := provider.findDNSRecord(isIpv4)
4773
if err != nil {
48-
log.Printf("查找 DNS 记录时出错: %s\n", err)
49-
return false
74+
return fmt.Errorf("查找 DNS 记录时出错: %s", err)
5075
}
5176

52-
if record == nil {
77+
if provider.recordId == "" {
5378
// 添加 DNS 记录
54-
return provider.createDNSRecord(zoneID, domainConfig, isIpv4)
79+
return provider.createDNSRecord(isIpv4)
5580
} else {
5681
// 更新 DNS 记录
57-
return provider.updateDNSRecord(zoneID, record["id"].(string), domainConfig, isIpv4)
82+
return provider.updateDNSRecord(isIpv4)
5883
}
5984
}
6085

61-
func (provider *ProviderCloudflare) getZoneID(domain string) (string, error) {
62-
_, realDomain := SplitDomain(domain)
63-
url := fmt.Sprintf("https://api.cloudflare.com/client/v4/zones?name=%s", realDomain)
64-
body, err := provider.sendRequest("GET", url, nil)
86+
func (provider *ProviderCloudflare) getZoneID() error {
87+
_, realDomain := splitDomain(provider.domainConfig.FullDomain)
88+
zu, _ := url.Parse(baseEndpoint)
89+
90+
q := zu.Query()
91+
q.Set("name", realDomain)
92+
zu.RawQuery = q.Encode()
93+
94+
body, err := provider.sendRequest("GET", zu.String(), nil)
6595
if err != nil {
66-
return "", err
96+
return err
6797
}
6898

69-
var res map[string]interface{}
70-
err = json.Unmarshal(body, &res)
99+
res := &cfResp{}
100+
err = utils.Json.Unmarshal(body, res)
71101
if err != nil {
72-
return "", err
102+
return err
73103
}
74104

75-
result := res["result"].([]interface{})
105+
result := res.Result
76106
if len(result) > 0 {
77-
zoneID := result[0].(map[string]interface{})["id"].(string)
78-
return zoneID, nil
107+
provider.zoneId = result[0].ID
108+
return nil
79109
}
80110

81-
return "", fmt.Errorf("找不到 Zone ID")
111+
return fmt.Errorf("找不到 Zone ID")
82112
}
83113

84-
func (provider *ProviderCloudflare) findDNSRecord(zoneID string, domain string, isIPv4 bool) (map[string]interface{}, error) {
85-
var ipType = "A"
86-
if !isIPv4 {
114+
func (provider *ProviderCloudflare) findDNSRecord(isIPv4 bool) error {
115+
var ipType string
116+
if isIPv4 {
117+
ipType = "A"
118+
} else {
87119
ipType = "AAAA"
88120
}
89-
url := fmt.Sprintf("https://api.cloudflare.com/client/v4/zones/%s/dns_records?type=%s&name=%s", zoneID, ipType, domain)
90-
body, err := provider.sendRequest("GET", url, nil)
121+
122+
de, _ := url.JoinPath(baseEndpoint, provider.zoneId, "dns_records")
123+
du, _ := url.Parse(de)
124+
125+
q := du.Query()
126+
q.Set("name", provider.domainConfig.FullDomain)
127+
q.Set("type", ipType)
128+
du.RawQuery = q.Encode()
129+
130+
body, err := provider.sendRequest("GET", du.String(), nil)
91131
if err != nil {
92-
return nil, err
132+
return err
93133
}
94134

95-
var res map[string]interface{}
96-
err = json.Unmarshal(body, &res)
135+
res := &cfResp{}
136+
err = utils.Json.Unmarshal(body, res)
97137
if err != nil {
98-
return nil, err
138+
return err
99139
}
100140

101-
result := res["result"].([]interface{})
141+
result := res.Result
102142
if len(result) > 0 {
103-
return result[0].(map[string]interface{}), nil
143+
provider.recordId = result[0].ID
144+
return nil
104145
}
105146

106-
return nil, nil // 没有找到 DNS 记录
147+
return nil
107148
}
108149

109-
func (provider *ProviderCloudflare) createDNSRecord(zoneID string, domainConfig *DomainConfig, isIPv4 bool) bool {
110-
var ipType = "A"
111-
var ipAddr = domainConfig.Ipv4Addr
112-
if !isIPv4 {
150+
func (provider *ProviderCloudflare) createDNSRecord(isIPv4 bool) error {
151+
var ipType, ipAddr string
152+
if isIPv4 {
153+
ipType = "A"
154+
ipAddr = provider.domainConfig.Ipv4Addr
155+
} else {
113156
ipType = "AAAA"
114-
ipAddr = domainConfig.Ipv6Addr
115-
}
116-
url := fmt.Sprintf("https://api.cloudflare.com/client/v4/zones/%s/dns_records", zoneID)
117-
data := map[string]interface{}{
118-
"type": ipType,
119-
"name": domainConfig.FullDomain,
120-
"content": ipAddr,
121-
"ttl": 60,
122-
"proxied": false,
123-
}
124-
jsonData, _ := json.Marshal(data)
125-
_, err := provider.sendRequest("POST", url, jsonData)
126-
return err == nil
157+
ipAddr = provider.domainConfig.Ipv6Addr
158+
}
159+
160+
de, _ := url.JoinPath(baseEndpoint, provider.zoneId, "dns_records")
161+
data := &cfReq{
162+
Name: provider.domainConfig.FullDomain,
163+
Type: ipType,
164+
Content: ipAddr,
165+
TTL: 60,
166+
Proxied: false,
167+
}
168+
169+
jsonData, _ := utils.Json.Marshal(data)
170+
_, err := provider.sendRequest("POST", de, jsonData)
171+
return err
127172
}
128173

129-
func (provider *ProviderCloudflare) updateDNSRecord(zoneID string, recordID string, domainConfig *DomainConfig, isIPv4 bool) bool {
130-
var ipType = "A"
131-
var ipAddr = domainConfig.Ipv4Addr
132-
if !isIPv4 {
174+
func (provider *ProviderCloudflare) updateDNSRecord(isIPv4 bool) error {
175+
var ipType, ipAddr string
176+
if isIPv4 {
177+
ipType = "A"
178+
ipAddr = provider.domainConfig.Ipv4Addr
179+
} else {
133180
ipType = "AAAA"
134-
ipAddr = domainConfig.Ipv6Addr
135-
}
136-
url := fmt.Sprintf("https://api.cloudflare.com/client/v4/zones/%s/dns_records/%s", zoneID, recordID)
137-
data := map[string]interface{}{
138-
"type": ipType,
139-
"name": domainConfig.FullDomain,
140-
"content": ipAddr,
141-
"ttl": 60,
142-
"proxied": false,
143-
}
144-
jsonData, _ := json.Marshal(data)
145-
_, err := provider.sendRequest("PATCH", url, jsonData)
146-
return err == nil
181+
ipAddr = provider.domainConfig.Ipv6Addr
182+
}
183+
184+
de, _ := url.JoinPath(baseEndpoint, provider.zoneId, "dns_records", provider.recordId)
185+
data := &cfReq{
186+
Name: provider.domainConfig.FullDomain,
187+
Type: ipType,
188+
Content: ipAddr,
189+
TTL: 60,
190+
Proxied: false,
191+
}
192+
193+
jsonData, _ := utils.Json.Marshal(data)
194+
_, err := provider.sendRequest("PATCH", de, jsonData)
195+
return err
147196
}
148197

149198
// 以下为辅助方法,如发送 HTTP 请求等
150199
func (provider *ProviderCloudflare) sendRequest(method string, url string, data []byte) ([]byte, error) {
151-
client := &http.Client{}
152200
req, err := http.NewRequest(method, url, bytes.NewBuffer(data))
153201
if err != nil {
154202
return nil, err
155203
}
156204

157-
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", provider.Secret))
205+
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", provider.secret))
158206
req.Header.Add("Content-Type", "application/json")
159207

160-
resp, err := client.Do(req)
208+
resp, err := utils.HttpClient.Do(req)
161209
if err != nil {
162210
return nil, err
163211
}
164212
defer func(Body io.ReadCloser) {
165213
err := Body.Close()
166214
if err != nil {
167-
log.Printf("NEZHA>> 无法关闭HTTP响应体流: %s\n", err.Error())
215+
log.Printf("NEZHA>> 无法关闭HTTP响应体流: %s", err.Error())
168216
}
169217
}(resp.Body)
170218

pkg/ddns/ddns.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package ddns
22

3+
import "golang.org/x/net/publicsuffix"
4+
35
type DomainConfig struct {
46
EnableIPv4 bool
57
EnableIpv6 bool
@@ -10,5 +12,11 @@ type DomainConfig struct {
1012

1113
type Provider interface {
1214
// UpdateDomain Return is updated
13-
UpdateDomain(domainConfig *DomainConfig) bool
15+
UpdateDomain(*DomainConfig) error
16+
}
17+
18+
func splitDomain(domain string) (prefix string, realDomain string) {
19+
realDomain, _ = publicsuffix.EffectiveTLDPlusOne(domain)
20+
prefix = domain[:len(domain)-len(realDomain)-1]
21+
return prefix, realDomain
1422
}

0 commit comments

Comments
 (0)