mirror of
https://github.com/nezhahq/nezha.git
synced 2025-01-22 12:48:14 -05:00
parent
64da3c7438
commit
eb6dd2855e
@ -1,7 +1,6 @@
|
|||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
@ -277,7 +276,7 @@ func natGateway(c *gin.Context) {
|
|||||||
rpc.NezhaHandlerSingleton.CreateStream(streamId)
|
rpc.NezhaHandlerSingleton.CreateStream(streamId)
|
||||||
defer rpc.NezhaHandlerSingleton.CloseStream(streamId)
|
defer rpc.NezhaHandlerSingleton.CloseStream(streamId)
|
||||||
|
|
||||||
taskData, err := json.Marshal(model.TaskNAT{
|
taskData, err := utils.Json.Marshal(model.TaskNAT{
|
||||||
StreamID: streamId,
|
StreamID: streamId,
|
||||||
Host: natConfig.Host,
|
Host: natConfig.Host,
|
||||||
})
|
})
|
||||||
|
@ -2,7 +2,6 @@ package controller
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -199,7 +198,7 @@ func (oa *oauth2controller) callback(c *gin.Context) {
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
var cloudflareUserInfo *cloudflare.UserInfo
|
var cloudflareUserInfo *cloudflare.UserInfo
|
||||||
if err := json.NewDecoder(resp.Body).Decode(&cloudflareUserInfo); err == nil {
|
if err := utils.Json.NewDecoder(resp.Body).Decode(&cloudflareUserInfo); err == nil {
|
||||||
user = cloudflareUserInfo.MapToNezhaUser()
|
user = cloudflareUserInfo.MapToNezhaUser()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package model
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -44,19 +45,6 @@ func percentage(used, total uint64) float64 {
|
|||||||
return float64(used) * 100 / float64(total)
|
return float64(used) * 100 / float64(total)
|
||||||
}
|
}
|
||||||
|
|
||||||
func maxSliceValue(slice []float64) float64 {
|
|
||||||
if len(slice) != 0 {
|
|
||||||
max := slice[0]
|
|
||||||
for _, val := range slice {
|
|
||||||
if max < val {
|
|
||||||
max = val
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return max
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Snapshot 未通过规则返回 struct{}{}, 通过返回 nil
|
// Snapshot 未通过规则返回 struct{}{}, 通过返回 nil
|
||||||
func (u *Rule) Snapshot(cycleTransferStats *CycleTransferStats, server *Server, db *gorm.DB) interface{} {
|
func (u *Rule) Snapshot(cycleTransferStats *CycleTransferStats, server *Server, db *gorm.DB) interface{} {
|
||||||
// 监控全部但是排除了此服务器
|
// 监控全部但是排除了此服务器
|
||||||
@ -145,7 +133,7 @@ func (u *Rule) Snapshot(cycleTransferStats *CycleTransferStats, server *Server,
|
|||||||
temp = append(temp, tempStat.Temperature)
|
temp = append(temp, tempStat.Temperature)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
src = maxSliceValue(temp)
|
src = slices.Max(temp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,169 +2,217 @@ package ddns
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/naiba/nezha/pkg/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const baseEndpoint = "https://api.cloudflare.com/client/v4/zones"
|
||||||
|
|
||||||
type ProviderCloudflare struct {
|
type ProviderCloudflare struct {
|
||||||
Secret string
|
secret string
|
||||||
|
zoneId string
|
||||||
|
recordId string
|
||||||
|
domainConfig *DomainConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provider *ProviderCloudflare) UpdateDomain(domainConfig *DomainConfig) bool {
|
type cfReq struct {
|
||||||
if domainConfig == nil {
|
Name string `json:"name"`
|
||||||
return false
|
Type string `json:"type"`
|
||||||
}
|
Content string `json:"content"`
|
||||||
|
TTL uint32 `json:"ttl"`
|
||||||
|
Proxied bool `json:"proxied"`
|
||||||
|
}
|
||||||
|
|
||||||
zoneID, err := provider.getZoneID(domainConfig.FullDomain)
|
type cfResp struct {
|
||||||
|
Result []struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
} `json:"result"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewProviderCloudflare(s string) *ProviderCloudflare {
|
||||||
|
return &ProviderCloudflare{
|
||||||
|
secret: s,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *ProviderCloudflare) UpdateDomain(domainConfig *DomainConfig) error {
|
||||||
|
if domainConfig == nil {
|
||||||
|
return fmt.Errorf("获取 DDNS 配置失败")
|
||||||
|
}
|
||||||
|
provider.domainConfig = domainConfig
|
||||||
|
|
||||||
|
err := provider.getZoneID()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("无法获取 zone ID: %s\n", err)
|
return fmt.Errorf("无法获取 zone ID: %s", err)
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 当IPv4和IPv6同时成功才算作成功
|
// 当IPv4和IPv6同时成功才算作成功
|
||||||
var resultV4 = true
|
if provider.domainConfig.EnableIPv4 {
|
||||||
var resultV6 = true
|
if err = provider.addDomainRecord(true); err != nil {
|
||||||
if domainConfig.EnableIPv4 {
|
return err
|
||||||
if !provider.addDomainRecord(zoneID, domainConfig, true) {
|
|
||||||
resultV4 = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if domainConfig.EnableIpv6 {
|
if provider.domainConfig.EnableIpv6 {
|
||||||
if !provider.addDomainRecord(zoneID, domainConfig, false) {
|
if err = provider.addDomainRecord(false); err != nil {
|
||||||
resultV6 = false
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return resultV4 && resultV6
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provider *ProviderCloudflare) addDomainRecord(zoneID string, domainConfig *DomainConfig, isIpv4 bool) bool {
|
func (provider *ProviderCloudflare) addDomainRecord(isIpv4 bool) error {
|
||||||
record, err := provider.findDNSRecord(zoneID, domainConfig.FullDomain, isIpv4)
|
err := provider.findDNSRecord(isIpv4)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("查找 DNS 记录时出错: %s\n", err)
|
return fmt.Errorf("查找 DNS 记录时出错: %s", err)
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if record == nil {
|
if provider.recordId == "" {
|
||||||
// 添加 DNS 记录
|
// 添加 DNS 记录
|
||||||
return provider.createDNSRecord(zoneID, domainConfig, isIpv4)
|
return provider.createDNSRecord(isIpv4)
|
||||||
} else {
|
} else {
|
||||||
// 更新 DNS 记录
|
// 更新 DNS 记录
|
||||||
return provider.updateDNSRecord(zoneID, record["id"].(string), domainConfig, isIpv4)
|
return provider.updateDNSRecord(isIpv4)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provider *ProviderCloudflare) getZoneID(domain string) (string, error) {
|
func (provider *ProviderCloudflare) getZoneID() error {
|
||||||
_, realDomain := SplitDomain(domain)
|
_, realDomain := splitDomain(provider.domainConfig.FullDomain)
|
||||||
url := fmt.Sprintf("https://api.cloudflare.com/client/v4/zones?name=%s", realDomain)
|
zu, _ := url.Parse(baseEndpoint)
|
||||||
body, err := provider.sendRequest("GET", url, nil)
|
|
||||||
|
q := zu.Query()
|
||||||
|
q.Set("name", realDomain)
|
||||||
|
zu.RawQuery = q.Encode()
|
||||||
|
|
||||||
|
body, err := provider.sendRequest("GET", zu.String(), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var res map[string]interface{}
|
res := &cfResp{}
|
||||||
err = json.Unmarshal(body, &res)
|
err = utils.Json.Unmarshal(body, res)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
result := res["result"].([]interface{})
|
result := res.Result
|
||||||
if len(result) > 0 {
|
if len(result) > 0 {
|
||||||
zoneID := result[0].(map[string]interface{})["id"].(string)
|
provider.zoneId = result[0].ID
|
||||||
return zoneID, nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return "", fmt.Errorf("找不到 Zone ID")
|
return fmt.Errorf("找不到 Zone ID")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provider *ProviderCloudflare) findDNSRecord(zoneID string, domain string, isIPv4 bool) (map[string]interface{}, error) {
|
func (provider *ProviderCloudflare) findDNSRecord(isIPv4 bool) error {
|
||||||
var ipType = "A"
|
var ipType string
|
||||||
if !isIPv4 {
|
if isIPv4 {
|
||||||
|
ipType = "A"
|
||||||
|
} else {
|
||||||
ipType = "AAAA"
|
ipType = "AAAA"
|
||||||
}
|
}
|
||||||
url := fmt.Sprintf("https://api.cloudflare.com/client/v4/zones/%s/dns_records?type=%s&name=%s", zoneID, ipType, domain)
|
|
||||||
body, err := provider.sendRequest("GET", url, nil)
|
de, _ := url.JoinPath(baseEndpoint, provider.zoneId, "dns_records")
|
||||||
|
du, _ := url.Parse(de)
|
||||||
|
|
||||||
|
q := du.Query()
|
||||||
|
q.Set("name", provider.domainConfig.FullDomain)
|
||||||
|
q.Set("type", ipType)
|
||||||
|
du.RawQuery = q.Encode()
|
||||||
|
|
||||||
|
body, err := provider.sendRequest("GET", du.String(), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var res map[string]interface{}
|
res := &cfResp{}
|
||||||
err = json.Unmarshal(body, &res)
|
err = utils.Json.Unmarshal(body, res)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
result := res["result"].([]interface{})
|
result := res.Result
|
||||||
if len(result) > 0 {
|
if len(result) > 0 {
|
||||||
return result[0].(map[string]interface{}), nil
|
provider.recordId = result[0].ID
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, nil // 没有找到 DNS 记录
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provider *ProviderCloudflare) createDNSRecord(zoneID string, domainConfig *DomainConfig, isIPv4 bool) bool {
|
func (provider *ProviderCloudflare) createDNSRecord(isIPv4 bool) error {
|
||||||
var ipType = "A"
|
var ipType, ipAddr string
|
||||||
var ipAddr = domainConfig.Ipv4Addr
|
if isIPv4 {
|
||||||
if !isIPv4 {
|
ipType = "A"
|
||||||
|
ipAddr = provider.domainConfig.Ipv4Addr
|
||||||
|
} else {
|
||||||
ipType = "AAAA"
|
ipType = "AAAA"
|
||||||
ipAddr = domainConfig.Ipv6Addr
|
ipAddr = provider.domainConfig.Ipv6Addr
|
||||||
}
|
}
|
||||||
url := fmt.Sprintf("https://api.cloudflare.com/client/v4/zones/%s/dns_records", zoneID)
|
|
||||||
data := map[string]interface{}{
|
de, _ := url.JoinPath(baseEndpoint, provider.zoneId, "dns_records")
|
||||||
"type": ipType,
|
data := &cfReq{
|
||||||
"name": domainConfig.FullDomain,
|
Name: provider.domainConfig.FullDomain,
|
||||||
"content": ipAddr,
|
Type: ipType,
|
||||||
"ttl": 60,
|
Content: ipAddr,
|
||||||
"proxied": false,
|
TTL: 60,
|
||||||
|
Proxied: false,
|
||||||
}
|
}
|
||||||
jsonData, _ := json.Marshal(data)
|
|
||||||
_, err := provider.sendRequest("POST", url, jsonData)
|
jsonData, _ := utils.Json.Marshal(data)
|
||||||
return err == nil
|
_, err := provider.sendRequest("POST", de, jsonData)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provider *ProviderCloudflare) updateDNSRecord(zoneID string, recordID string, domainConfig *DomainConfig, isIPv4 bool) bool {
|
func (provider *ProviderCloudflare) updateDNSRecord(isIPv4 bool) error {
|
||||||
var ipType = "A"
|
var ipType, ipAddr string
|
||||||
var ipAddr = domainConfig.Ipv4Addr
|
if isIPv4 {
|
||||||
if !isIPv4 {
|
ipType = "A"
|
||||||
|
ipAddr = provider.domainConfig.Ipv4Addr
|
||||||
|
} else {
|
||||||
ipType = "AAAA"
|
ipType = "AAAA"
|
||||||
ipAddr = domainConfig.Ipv6Addr
|
ipAddr = provider.domainConfig.Ipv6Addr
|
||||||
}
|
}
|
||||||
url := fmt.Sprintf("https://api.cloudflare.com/client/v4/zones/%s/dns_records/%s", zoneID, recordID)
|
|
||||||
data := map[string]interface{}{
|
de, _ := url.JoinPath(baseEndpoint, provider.zoneId, "dns_records", provider.recordId)
|
||||||
"type": ipType,
|
data := &cfReq{
|
||||||
"name": domainConfig.FullDomain,
|
Name: provider.domainConfig.FullDomain,
|
||||||
"content": ipAddr,
|
Type: ipType,
|
||||||
"ttl": 60,
|
Content: ipAddr,
|
||||||
"proxied": false,
|
TTL: 60,
|
||||||
|
Proxied: false,
|
||||||
}
|
}
|
||||||
jsonData, _ := json.Marshal(data)
|
|
||||||
_, err := provider.sendRequest("PATCH", url, jsonData)
|
jsonData, _ := utils.Json.Marshal(data)
|
||||||
return err == nil
|
_, err := provider.sendRequest("PATCH", de, jsonData)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 以下为辅助方法,如发送 HTTP 请求等
|
// 以下为辅助方法,如发送 HTTP 请求等
|
||||||
func (provider *ProviderCloudflare) sendRequest(method string, url string, data []byte) ([]byte, error) {
|
func (provider *ProviderCloudflare) sendRequest(method string, url string, data []byte) ([]byte, error) {
|
||||||
client := &http.Client{}
|
|
||||||
req, err := http.NewRequest(method, url, bytes.NewBuffer(data))
|
req, err := http.NewRequest(method, url, bytes.NewBuffer(data))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", provider.Secret))
|
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", provider.secret))
|
||||||
req.Header.Add("Content-Type", "application/json")
|
req.Header.Add("Content-Type", "application/json")
|
||||||
|
|
||||||
resp, err := client.Do(req)
|
resp, err := utils.HttpClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer func(Body io.ReadCloser) {
|
defer func(Body io.ReadCloser) {
|
||||||
err := Body.Close()
|
err := Body.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("NEZHA>> 无法关闭HTTP响应体流: %s\n", err.Error())
|
log.Printf("NEZHA>> 无法关闭HTTP响应体流: %s", err.Error())
|
||||||
}
|
}
|
||||||
}(resp.Body)
|
}(resp.Body)
|
||||||
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package ddns
|
package ddns
|
||||||
|
|
||||||
|
import "golang.org/x/net/publicsuffix"
|
||||||
|
|
||||||
type DomainConfig struct {
|
type DomainConfig struct {
|
||||||
EnableIPv4 bool
|
EnableIPv4 bool
|
||||||
EnableIpv6 bool
|
EnableIpv6 bool
|
||||||
@ -10,5 +12,11 @@ type DomainConfig struct {
|
|||||||
|
|
||||||
type Provider interface {
|
type Provider interface {
|
||||||
// UpdateDomain Return is updated
|
// UpdateDomain Return is updated
|
||||||
UpdateDomain(domainConfig *DomainConfig) bool
|
UpdateDomain(*DomainConfig) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func splitDomain(domain string) (prefix string, realDomain string) {
|
||||||
|
realDomain, _ = publicsuffix.EffectiveTLDPlusOne(domain)
|
||||||
|
prefix = domain[:len(domain)-len(realDomain)-1]
|
||||||
|
return prefix, realDomain
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,6 @@ package ddns
|
|||||||
|
|
||||||
type ProviderDummy struct{}
|
type ProviderDummy struct{}
|
||||||
|
|
||||||
func (provider *ProviderDummy) UpdateDomain(domainConfig *DomainConfig) bool {
|
func (provider *ProviderDummy) UpdateDomain(domainConfig *DomainConfig) error {
|
||||||
return false
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -1,40 +0,0 @@
|
|||||||
package ddns
|
|
||||||
|
|
||||||
import (
|
|
||||||
"golang.org/x/net/publicsuffix"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (provider ProviderWebHook) FormatWebhookString(s string, config *DomainConfig, ipType string) string {
|
|
||||||
if config == nil {
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
result := strings.TrimSpace(s)
|
|
||||||
result = strings.Replace(s, "{ip}", config.Ipv4Addr, -1)
|
|
||||||
result = strings.Replace(result, "{domain}", config.FullDomain, -1)
|
|
||||||
result = strings.Replace(result, "{type}", ipType, -1)
|
|
||||||
// remove \r
|
|
||||||
result = strings.Replace(result, "\r", "", -1)
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func SetStringHeadersToRequest(req *http.Request, headers []string) {
|
|
||||||
if req == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for _, element := range headers {
|
|
||||||
kv := strings.SplitN(element, ":", 2)
|
|
||||||
if len(kv) == 2 {
|
|
||||||
req.Header.Add(kv[0], kv[1])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SplitDomain 分割域名为前缀和一级域名
|
|
||||||
func SplitDomain(domain string) (prefix string, realDomain string) {
|
|
||||||
realDomain, _ = publicsuffix.EffectiveTLDPlusOne(domain)
|
|
||||||
prefix = domain[:len(domain)-len(realDomain)-1]
|
|
||||||
return prefix, realDomain
|
|
||||||
}
|
|
@ -5,145 +5,180 @@ import (
|
|||||||
"crypto/hmac"
|
"crypto/hmac"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/naiba/nezha/pkg/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const te = "https://dnspod.tencentcloudapi.com"
|
||||||
url = "https://dnspod.tencentcloudapi.com"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ProviderTencentCloud struct {
|
type ProviderTencentCloud struct {
|
||||||
SecretID string
|
secretID string
|
||||||
SecretKey string
|
secretKey string
|
||||||
|
domainConfig *DomainConfig
|
||||||
|
resp *tcResp
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provider *ProviderTencentCloud) UpdateDomain(domainConfig *DomainConfig) bool {
|
type tcReq struct {
|
||||||
if domainConfig == nil {
|
RecordType string `json:"RecordType"`
|
||||||
return false
|
Domain string `json:"Domain"`
|
||||||
|
RecordLine string `json:"RecordLine"`
|
||||||
|
Subdomain string `json:"Subdomain,omitempty"`
|
||||||
|
SubDomain string `json:"SubDomain,omitempty"` // As is
|
||||||
|
Value string `json:"Value,omitempty"`
|
||||||
|
TTL uint32 `json:"TTL,omitempty"`
|
||||||
|
RecordId uint64 `json:"RecordId,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type tcResp struct {
|
||||||
|
Response struct {
|
||||||
|
RecordList []struct {
|
||||||
|
RecordId uint64
|
||||||
|
Value string
|
||||||
|
}
|
||||||
|
Error struct {
|
||||||
|
Code string
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewProviderTencentCloud(id, key string) *ProviderTencentCloud {
|
||||||
|
return &ProviderTencentCloud{
|
||||||
|
secretID: id,
|
||||||
|
secretKey: key,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *ProviderTencentCloud) UpdateDomain(domainConfig *DomainConfig) error {
|
||||||
|
if domainConfig == nil {
|
||||||
|
return fmt.Errorf("获取 DDNS 配置失败")
|
||||||
|
}
|
||||||
|
provider.domainConfig = domainConfig
|
||||||
|
|
||||||
// 当IPv4和IPv6同时成功才算作成功
|
// 当IPv4和IPv6同时成功才算作成功
|
||||||
var resultV4 = true
|
var err error
|
||||||
var resultV6 = true
|
if provider.domainConfig.EnableIPv4 {
|
||||||
if domainConfig.EnableIPv4 {
|
if err = provider.addDomainRecord(true); err != nil {
|
||||||
if !provider.addDomainRecord(domainConfig, true) {
|
return err
|
||||||
resultV4 = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if domainConfig.EnableIpv6 {
|
if provider.domainConfig.EnableIpv6 {
|
||||||
if !provider.addDomainRecord(domainConfig, false) {
|
if err = provider.addDomainRecord(false); err != nil {
|
||||||
resultV6 = false
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return resultV4 && resultV6
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provider *ProviderTencentCloud) addDomainRecord(domainConfig *DomainConfig, isIpv4 bool) bool {
|
func (provider *ProviderTencentCloud) addDomainRecord(isIpv4 bool) error {
|
||||||
record, err := provider.findDNSRecord(domainConfig.FullDomain, isIpv4)
|
err := provider.findDNSRecord(isIpv4)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("查找 DNS 记录时出错: %s\n", err)
|
return fmt.Errorf("查找 DNS 记录时出错: %s", err)
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if errResponse, ok := record["Error"].(map[string]interface{}); ok {
|
if provider.resp.Response.Error.Code == "ResourceNotFound.NoDataOfRecord" { // 没有找到 DNS 记录
|
||||||
if errCode, ok := errResponse["Code"].(string); ok && errCode == "ResourceNotFound.NoDataOfRecord" { // 没有找到 DNS 记录
|
return provider.createDNSRecord(isIpv4)
|
||||||
// 添加 DNS 记录
|
} else if provider.resp.Response.Error.Code != "" {
|
||||||
return provider.createDNSRecord(domainConfig.FullDomain, domainConfig, isIpv4)
|
return fmt.Errorf("查询 DNS 记录时出错,错误代码为: %s", provider.resp.Response.Error.Code)
|
||||||
} else {
|
|
||||||
log.Printf("查询 DNS 记录时出错,错误代码为: %s\n", errCode)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 默认情况下更新 DNS 记录
|
// 默认情况下更新 DNS 记录
|
||||||
return provider.updateDNSRecord(domainConfig.FullDomain, record["RecordList"].([]interface{})[0].(map[string]interface{})["RecordId"].(float64), domainConfig, isIpv4)
|
return provider.updateDNSRecord(isIpv4)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provider *ProviderTencentCloud) findDNSRecord(domain string, isIPv4 bool) (map[string]interface{}, error) {
|
func (provider *ProviderTencentCloud) findDNSRecord(isIPv4 bool) error {
|
||||||
var ipType = "A"
|
var ipType string
|
||||||
if !isIPv4 {
|
if isIPv4 {
|
||||||
|
ipType = "A"
|
||||||
|
} else {
|
||||||
ipType = "AAAA"
|
ipType = "AAAA"
|
||||||
}
|
}
|
||||||
_, realDomain := SplitDomain(domain)
|
|
||||||
prefix, _ := SplitDomain(domain)
|
prefix, realDomain := splitDomain(provider.domainConfig.FullDomain)
|
||||||
data := map[string]interface{}{
|
data := &tcReq{
|
||||||
"RecordType": ipType,
|
RecordType: ipType,
|
||||||
"Domain": realDomain,
|
Domain: realDomain,
|
||||||
"RecordLine": "默认",
|
RecordLine: "默认",
|
||||||
"Subdomain": prefix,
|
Subdomain: prefix,
|
||||||
}
|
}
|
||||||
jsonData, _ := json.Marshal(data)
|
|
||||||
|
jsonData, _ := utils.Json.Marshal(data)
|
||||||
body, err := provider.sendRequest("DescribeRecordList", jsonData)
|
body, err := provider.sendRequest("DescribeRecordList", jsonData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var res map[string]interface{}
|
provider.resp = &tcResp{}
|
||||||
err = json.Unmarshal(body, &res)
|
err = utils.Json.Unmarshal(body, provider.resp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
result := res["Response"].(map[string]interface{})
|
return nil
|
||||||
return result, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provider *ProviderTencentCloud) createDNSRecord(domain string, domainConfig *DomainConfig, isIPv4 bool) bool {
|
func (provider *ProviderTencentCloud) createDNSRecord(isIPv4 bool) error {
|
||||||
var ipType = "A"
|
var ipType, ipAddr string
|
||||||
var ipAddr = domainConfig.Ipv4Addr
|
if isIPv4 {
|
||||||
if !isIPv4 {
|
ipType = "A"
|
||||||
|
ipAddr = provider.domainConfig.Ipv4Addr
|
||||||
|
} else {
|
||||||
ipType = "AAAA"
|
ipType = "AAAA"
|
||||||
ipAddr = domainConfig.Ipv6Addr
|
ipAddr = provider.domainConfig.Ipv6Addr
|
||||||
}
|
}
|
||||||
_, realDomain := SplitDomain(domain)
|
|
||||||
prefix, _ := SplitDomain(domain)
|
prefix, realDomain := splitDomain(provider.domainConfig.FullDomain)
|
||||||
data := map[string]interface{}{
|
data := &tcReq{
|
||||||
"RecordType": ipType,
|
RecordType: ipType,
|
||||||
"RecordLine": "默认",
|
RecordLine: "默认",
|
||||||
"Domain": realDomain,
|
Domain: realDomain,
|
||||||
"SubDomain": prefix,
|
SubDomain: prefix,
|
||||||
"Value": ipAddr,
|
Value: ipAddr,
|
||||||
"TTL": 600,
|
TTL: 600,
|
||||||
}
|
}
|
||||||
jsonData, _ := json.Marshal(data)
|
|
||||||
|
jsonData, _ := utils.Json.Marshal(data)
|
||||||
_, err := provider.sendRequest("CreateRecord", jsonData)
|
_, err := provider.sendRequest("CreateRecord", jsonData)
|
||||||
return err == nil
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provider *ProviderTencentCloud) updateDNSRecord(domain string, recordID float64, domainConfig *DomainConfig, isIPv4 bool) bool {
|
func (provider *ProviderTencentCloud) updateDNSRecord(isIPv4 bool) error {
|
||||||
var ipType = "A"
|
var ipType, ipAddr string
|
||||||
var ipAddr = domainConfig.Ipv4Addr
|
if isIPv4 {
|
||||||
if !isIPv4 {
|
ipType = "A"
|
||||||
|
ipAddr = provider.domainConfig.Ipv4Addr
|
||||||
|
} else {
|
||||||
ipType = "AAAA"
|
ipType = "AAAA"
|
||||||
ipAddr = domainConfig.Ipv6Addr
|
ipAddr = provider.domainConfig.Ipv6Addr
|
||||||
}
|
}
|
||||||
_, realDomain := SplitDomain(domain)
|
|
||||||
prefix, _ := SplitDomain(domain)
|
prefix, realDomain := splitDomain(provider.domainConfig.FullDomain)
|
||||||
data := map[string]interface{}{
|
data := &tcReq{
|
||||||
"RecordType": ipType,
|
RecordType: ipType,
|
||||||
"RecordLine": "默认",
|
RecordLine: "默认",
|
||||||
"Domain": realDomain,
|
Domain: realDomain,
|
||||||
"SubDomain": prefix,
|
SubDomain: prefix,
|
||||||
"Value": ipAddr,
|
Value: ipAddr,
|
||||||
"TTL": 600,
|
TTL: 600,
|
||||||
"RecordId": recordID,
|
RecordId: provider.resp.Response.RecordList[0].RecordId,
|
||||||
}
|
}
|
||||||
jsonData, _ := json.Marshal(data)
|
|
||||||
|
jsonData, _ := utils.Json.Marshal(data)
|
||||||
_, err := provider.sendRequest("ModifyRecord", jsonData)
|
_, err := provider.sendRequest("ModifyRecord", jsonData)
|
||||||
return err == nil
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 以下为辅助方法,如发送 HTTP 请求等
|
// 以下为辅助方法,如发送 HTTP 请求等
|
||||||
func (provider *ProviderTencentCloud) sendRequest(action string, data []byte) ([]byte, error) {
|
func (provider *ProviderTencentCloud) sendRequest(action string, data []byte) ([]byte, error) {
|
||||||
client := &http.Client{}
|
req, err := http.NewRequest("POST", te, bytes.NewBuffer(data))
|
||||||
req, err := http.NewRequest("POST", url, bytes.NewBuffer(data))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -151,8 +186,8 @@ func (provider *ProviderTencentCloud) sendRequest(action string, data []byte) ([
|
|||||||
req.Header.Set("Content-Type", "application/json")
|
req.Header.Set("Content-Type", "application/json")
|
||||||
req.Header.Set("X-TC-Version", "2021-03-23")
|
req.Header.Set("X-TC-Version", "2021-03-23")
|
||||||
|
|
||||||
provider.signRequest(provider.SecretID, provider.SecretKey, req, action, string(data))
|
provider.signRequest(provider.secretID, provider.secretKey, req, action, string(data))
|
||||||
resp, err := client.Do(req)
|
resp, err := utils.HttpClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -2,58 +2,109 @@ package ddns
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"log"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/naiba/nezha/pkg/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ProviderWebHook struct {
|
type ProviderWebHook struct {
|
||||||
URL string
|
url string
|
||||||
RequestMethod string
|
requestMethod string
|
||||||
RequestBody string
|
requestBody string
|
||||||
RequestHeader string
|
requestHeader string
|
||||||
|
domainConfig *DomainConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provider *ProviderWebHook) UpdateDomain(domainConfig *DomainConfig) bool {
|
func NewProviderWebHook(s, rm, rb, rh string) *ProviderWebHook {
|
||||||
|
return &ProviderWebHook{
|
||||||
|
url: s,
|
||||||
|
requestMethod: rm,
|
||||||
|
requestBody: rb,
|
||||||
|
requestHeader: rh,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *ProviderWebHook) UpdateDomain(domainConfig *DomainConfig) error {
|
||||||
if domainConfig == nil {
|
if domainConfig == nil {
|
||||||
return false
|
return fmt.Errorf("获取 DDNS 配置失败")
|
||||||
|
}
|
||||||
|
provider.domainConfig = domainConfig
|
||||||
|
|
||||||
|
if provider.domainConfig.FullDomain == "" {
|
||||||
|
return fmt.Errorf("failed to update an empty domain")
|
||||||
}
|
}
|
||||||
|
|
||||||
if domainConfig.FullDomain == "" {
|
if provider.domainConfig.EnableIPv4 && provider.domainConfig.Ipv4Addr != "" {
|
||||||
log.Println("NEZHA>> Failed to update an empty domain")
|
req, err := provider.prepareRequest(true)
|
||||||
return false
|
if err != nil {
|
||||||
}
|
return fmt.Errorf("failed to update a domain: %s. Cause by: %v", provider.domainConfig.FullDomain, err)
|
||||||
updated := false
|
}
|
||||||
client := &http.Client{}
|
if _, err := utils.HttpClient.Do(req); err != nil {
|
||||||
if domainConfig.EnableIPv4 && domainConfig.Ipv4Addr != "" {
|
return fmt.Errorf("failed to update a domain: %s. Cause by: %v", provider.domainConfig.FullDomain, err)
|
||||||
url := provider.FormatWebhookString(provider.URL, domainConfig, "ipv4")
|
|
||||||
body := provider.FormatWebhookString(provider.RequestBody, domainConfig, "ipv4")
|
|
||||||
header := provider.FormatWebhookString(provider.RequestHeader, domainConfig, "ipv4")
|
|
||||||
headers := strings.Split(header, "\n")
|
|
||||||
req, err := http.NewRequest(provider.RequestMethod, url, bytes.NewBufferString(body))
|
|
||||||
if err == nil && req != nil {
|
|
||||||
SetStringHeadersToRequest(req, headers)
|
|
||||||
if _, err := client.Do(req); err != nil {
|
|
||||||
log.Printf("NEZHA>> Failed to update a domain: %s. Cause by: %s\n", domainConfig.FullDomain, err.Error())
|
|
||||||
} else {
|
|
||||||
updated = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if domainConfig.EnableIpv6 && domainConfig.Ipv6Addr != "" {
|
|
||||||
url := provider.FormatWebhookString(provider.URL, domainConfig, "ipv6")
|
if provider.domainConfig.EnableIpv6 && provider.domainConfig.Ipv6Addr != "" {
|
||||||
body := provider.FormatWebhookString(provider.RequestBody, domainConfig, "ipv6")
|
req, err := provider.prepareRequest(false)
|
||||||
header := provider.FormatWebhookString(provider.RequestHeader, domainConfig, "ipv6")
|
if err != nil {
|
||||||
headers := strings.Split(header, "\n")
|
return fmt.Errorf("failed to update a domain: %s. Cause by: %v", provider.domainConfig.FullDomain, err)
|
||||||
req, err := http.NewRequest(provider.RequestMethod, url, bytes.NewBufferString(body))
|
}
|
||||||
if err == nil && req != nil {
|
if _, err := utils.HttpClient.Do(req); err != nil {
|
||||||
SetStringHeadersToRequest(req, headers)
|
return fmt.Errorf("failed to update a domain: %s. Cause by: %v", provider.domainConfig.FullDomain, err)
|
||||||
if _, err := client.Do(req); err != nil {
|
|
||||||
log.Printf("NEZHA>> Failed to update a domain: %s. Cause by: %s\n", domainConfig.FullDomain, err.Error())
|
|
||||||
} else {
|
|
||||||
updated = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return updated
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *ProviderWebHook) prepareRequest(isIPv4 bool) (*http.Request, error) {
|
||||||
|
u, err := url.Parse(provider.url)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed parsing url: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only handle queries here
|
||||||
|
q := u.Query()
|
||||||
|
for p, vals := range q {
|
||||||
|
for n, v := range vals {
|
||||||
|
vals[n] = provider.formatWebhookString(v, isIPv4)
|
||||||
|
}
|
||||||
|
q[p] = vals
|
||||||
|
}
|
||||||
|
|
||||||
|
u.RawQuery = q.Encode()
|
||||||
|
body := provider.formatWebhookString(provider.requestBody, isIPv4)
|
||||||
|
header := provider.formatWebhookString(provider.requestHeader, isIPv4)
|
||||||
|
headers := strings.Split(header, "\n")
|
||||||
|
|
||||||
|
req, err := http.NewRequest(provider.requestMethod, u.String(), bytes.NewBufferString(body))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed creating new request: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.SetStringHeadersToRequest(req, headers)
|
||||||
|
return req, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *ProviderWebHook) formatWebhookString(s string, isIPv4 bool) string {
|
||||||
|
var ipAddr, ipType string
|
||||||
|
if isIPv4 {
|
||||||
|
ipAddr = provider.domainConfig.Ipv4Addr
|
||||||
|
ipType = "ipv4"
|
||||||
|
} else {
|
||||||
|
ipAddr = provider.domainConfig.Ipv6Addr
|
||||||
|
ipType = "ipv6"
|
||||||
|
}
|
||||||
|
|
||||||
|
r := strings.NewReplacer(
|
||||||
|
"{ip}", ipAddr,
|
||||||
|
"{domain}", provider.domainConfig.FullDomain,
|
||||||
|
"{type}", ipType,
|
||||||
|
"\r", "",
|
||||||
|
)
|
||||||
|
|
||||||
|
result := r.Replace(strings.TrimSpace(s))
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package utils
|
|||||||
import (
|
import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
@ -86,3 +87,15 @@ func Uint64SubInt64(a uint64, b int64) uint64 {
|
|||||||
}
|
}
|
||||||
return a - uint64(b)
|
return a - uint64(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SetStringHeadersToRequest(req *http.Request, headers []string) {
|
||||||
|
if req == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, element := range headers {
|
||||||
|
kv := strings.SplitN(element, ":", 2)
|
||||||
|
if len(kv) == 2 {
|
||||||
|
req.Header.Add(kv[0], kv[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
25
resource/l10n/en-US.toml
vendored
25
resource/l10n/en-US.toml
vendored
@ -653,7 +653,28 @@ other = "Disable Switch Template in Frontend"
|
|||||||
other = "Servers On World Map"
|
other = "Servers On World Map"
|
||||||
|
|
||||||
[NAT]
|
[NAT]
|
||||||
other = "NAT"
|
other = "NAT Traversal"
|
||||||
|
|
||||||
[NetworkSpiterList]
|
[NetworkSpiterList]
|
||||||
other = "Network Monitor"
|
other = "Network Monitor"
|
||||||
|
|
||||||
|
[Refresh]
|
||||||
|
other = "Refresh"
|
||||||
|
|
||||||
|
[CopyPath]
|
||||||
|
other = "Copy Path"
|
||||||
|
|
||||||
|
[Goto]
|
||||||
|
other = "Go to"
|
||||||
|
|
||||||
|
[GotoHeadline]
|
||||||
|
other = "Go to a Folder"
|
||||||
|
|
||||||
|
[GotoGo]
|
||||||
|
other = "Go"
|
||||||
|
|
||||||
|
[GotoClose]
|
||||||
|
other = "Cancel"
|
||||||
|
|
||||||
|
[FMError]
|
||||||
|
other = "Agent returned an error, please view the console for details. To open a new connection, reopen the FM again."
|
||||||
|
25
resource/l10n/es-ES.toml
vendored
25
resource/l10n/es-ES.toml
vendored
@ -653,7 +653,28 @@ other = "Deshabilitar Cambio de Plantilla en Frontend"
|
|||||||
other = "Servidores en el mapa mundial"
|
other = "Servidores en el mapa mundial"
|
||||||
|
|
||||||
[NAT]
|
[NAT]
|
||||||
other = "NAT"
|
other = "NAT traversal"
|
||||||
|
|
||||||
[NetworkSpiterList]
|
[NetworkSpiterList]
|
||||||
other = "Red Monitor"
|
other = "Monitor de red"
|
||||||
|
|
||||||
|
[Refresh]
|
||||||
|
other = "Actualizar"
|
||||||
|
|
||||||
|
[CopyPath]
|
||||||
|
other = "Copiar ruta"
|
||||||
|
|
||||||
|
[Goto]
|
||||||
|
other = "Ir a"
|
||||||
|
|
||||||
|
[GotoHeadline]
|
||||||
|
other = "Ir a una carpeta"
|
||||||
|
|
||||||
|
[GotoGo]
|
||||||
|
other = "Ir"
|
||||||
|
|
||||||
|
[GotoClose]
|
||||||
|
other = "Cancelar"
|
||||||
|
|
||||||
|
[FMError]
|
||||||
|
other = "Agent devolvió un error, consulte la consola para obtener más detalles. Para abrir una nueva conexión, vuelva a abrir el FM."
|
||||||
|
21
resource/l10n/zh-CN.toml
vendored
21
resource/l10n/zh-CN.toml
vendored
@ -657,3 +657,24 @@ other = "内网穿透"
|
|||||||
|
|
||||||
[NetworkSpiterList]
|
[NetworkSpiterList]
|
||||||
other = "网络监控"
|
other = "网络监控"
|
||||||
|
|
||||||
|
[Refresh]
|
||||||
|
other = "刷新"
|
||||||
|
|
||||||
|
[CopyPath]
|
||||||
|
other = "复制路径"
|
||||||
|
|
||||||
|
[Goto]
|
||||||
|
other = "跳往"
|
||||||
|
|
||||||
|
[GotoHeadline]
|
||||||
|
other = "跳往文件夹"
|
||||||
|
|
||||||
|
[GotoGo]
|
||||||
|
other = "确认"
|
||||||
|
|
||||||
|
[GotoClose]
|
||||||
|
other = "取消"
|
||||||
|
|
||||||
|
[FMError]
|
||||||
|
other = "Agent 返回了错误,请查看控制台获取详细信息。要建立新连接,请重新打开 FM。"
|
||||||
|
59
resource/l10n/zh-TW.toml
vendored
59
resource/l10n/zh-TW.toml
vendored
@ -50,7 +50,7 @@ other = "新增計劃任務"
|
|||||||
other = "名稱"
|
other = "名稱"
|
||||||
|
|
||||||
[Scheduler]
|
[Scheduler]
|
||||||
other = "計劃"
|
other = "排程"
|
||||||
|
|
||||||
[BackUp]
|
[BackUp]
|
||||||
other = "備份"
|
other = "備份"
|
||||||
@ -80,37 +80,37 @@ other = "特定伺服器"
|
|||||||
other = "輸入ID/名稱以搜尋"
|
other = "輸入ID/名稱以搜尋"
|
||||||
|
|
||||||
[NotificationMethodGroup]
|
[NotificationMethodGroup]
|
||||||
other = "通知方式組"
|
other = "通知群組"
|
||||||
|
|
||||||
[PushSuccessMessages]
|
[PushSuccessMessages]
|
||||||
other = "推送成功的消息"
|
other = "推送成功的訊息"
|
||||||
|
|
||||||
[TaskType]
|
[TaskType]
|
||||||
other = "任務類型"
|
other = "任務類型"
|
||||||
|
|
||||||
[CronTask]
|
[CronTask]
|
||||||
other = "計劃任務"
|
other = "排程任務"
|
||||||
|
|
||||||
[TriggerTask]
|
[TriggerTask]
|
||||||
other = "觸發任務"
|
other = "觸發任務"
|
||||||
|
|
||||||
[TheFormaOfTheScheduleIs]
|
[TheFormaOfTheScheduleIs]
|
||||||
other = "計劃的格式為:"
|
other = "排程的格式為:"
|
||||||
|
|
||||||
[SecondsMinutesHoursDaysMonthsWeeksSeeDetails]
|
[SecondsMinutesHoursDaysMonthsWeeksSeeDetails]
|
||||||
other = "秒 分 時 天 月 星期,詳情見"
|
other = "秒 分 時 天 月 星期,詳情見"
|
||||||
|
|
||||||
[ScheduleExpressionFormat]
|
[ScheduleExpressionFormat]
|
||||||
other = "計劃表達式格式"
|
other = "排程表達式格式"
|
||||||
|
|
||||||
[IntroductionOfCommands]
|
[IntroductionOfCommands]
|
||||||
other = "命令說明:編寫命令時類似於 shell/bat 腳本。建議不要換行,多個命令可用 <code>&&</code> 或 <code>&</code> 連接,若出現命令無法找到的情況,可能是由於 <code>PATH</code> 環境變量配置問題。在 <code>Linux</code> 伺服器上,可在命令開頭加入 <code>source ~/.bashrc</code>,或使用命令的絕對路徑執行。"
|
other = "命令說明:編寫命令時類似於 shell/bat 腳本。建議不要換行,多個命令可用 <code>&&</code> 或 <code>&</code> 連接,若出現命令無法找到的情況,可能是由於 <code>PATH</code> 環境變數配置問題。在 <code>Linux</code> 伺服器上,可在命令開頭加入 <code>source ~/.bashrc</code>,或使用命令的絕對路徑執行。"
|
||||||
|
|
||||||
[AddMonitor]
|
[AddMonitor]
|
||||||
other = "新增監控"
|
other = "新增監控"
|
||||||
|
|
||||||
[Blog]
|
[Blog]
|
||||||
other = "博客"
|
other = "部落格"
|
||||||
|
|
||||||
[Target]
|
[Target]
|
||||||
other = "目標"
|
other = "目標"
|
||||||
@ -158,7 +158,7 @@ other = "新增通知方式"
|
|||||||
other = "分組"
|
other = "分組"
|
||||||
|
|
||||||
[DoNotSendTestMessages]
|
[DoNotSendTestMessages]
|
||||||
other = "不發送測試信息"
|
other = "不發送測試訊息"
|
||||||
|
|
||||||
[RequestMethod]
|
[RequestMethod]
|
||||||
other = "請求方式"
|
other = "請求方式"
|
||||||
@ -221,7 +221,7 @@ other = "排序"
|
|||||||
other = "越大越靠前"
|
other = "越大越靠前"
|
||||||
|
|
||||||
[Secret]
|
[Secret]
|
||||||
other = "密鑰"
|
other = "金鑰"
|
||||||
|
|
||||||
[Note]
|
[Note]
|
||||||
other = "備註"
|
other = "備註"
|
||||||
@ -254,10 +254,10 @@ other = "忽略所有"
|
|||||||
other = "觸發執行"
|
other = "觸發執行"
|
||||||
|
|
||||||
[DeleteScheduledTask]
|
[DeleteScheduledTask]
|
||||||
other = "刪除計劃任務"
|
other = "刪除排程任務"
|
||||||
|
|
||||||
[ConfirmToDeleteThisScheduledTask]
|
[ConfirmToDeleteThisScheduledTask]
|
||||||
other = "確認刪除此計劃任務?"
|
other = "確認刪除此排程任務?"
|
||||||
|
|
||||||
[AccessDenied]
|
[AccessDenied]
|
||||||
other = "訪問被拒絕"
|
other = "訪問被拒絕"
|
||||||
@ -407,7 +407,7 @@ other = "流量"
|
|||||||
other = "負載"
|
other = "負載"
|
||||||
|
|
||||||
[ProcessCount]
|
[ProcessCount]
|
||||||
other = "進程數"
|
other = "行程數"
|
||||||
|
|
||||||
[ConnCount]
|
[ConnCount]
|
||||||
other = "連接數"
|
other = "連接數"
|
||||||
@ -422,7 +422,7 @@ other = "活動"
|
|||||||
other = "版本"
|
other = "版本"
|
||||||
|
|
||||||
[NetSpeed]
|
[NetSpeed]
|
||||||
other = "網絡"
|
other = "網路"
|
||||||
|
|
||||||
[Uptime]
|
[Uptime]
|
||||||
other = "在線"
|
other = "在線"
|
||||||
@ -458,7 +458,7 @@ other = "狀態"
|
|||||||
other = "可用性"
|
other = "可用性"
|
||||||
|
|
||||||
[AverageLatency]
|
[AverageLatency]
|
||||||
other = "平均響應時間"
|
other = "平均回應時間"
|
||||||
|
|
||||||
[CycleTransferStats]
|
[CycleTransferStats]
|
||||||
other = "周期性流量統計"
|
other = "周期性流量統計"
|
||||||
@ -524,7 +524,7 @@ other = "發生錯誤"
|
|||||||
other = "系統錯誤"
|
other = "系統錯誤"
|
||||||
|
|
||||||
[NetworkError]
|
[NetworkError]
|
||||||
other = "網絡錯誤"
|
other = "網路錯誤"
|
||||||
|
|
||||||
[ServicesStatus]
|
[ServicesStatus]
|
||||||
other = "服務狀態"
|
other = "服務狀態"
|
||||||
@ -536,7 +536,7 @@ other = "伺服器管理"
|
|||||||
other = "服務監控"
|
other = "服務監控"
|
||||||
|
|
||||||
[ScheduledTasks]
|
[ScheduledTasks]
|
||||||
other = "計劃任務"
|
other = "排程任務"
|
||||||
|
|
||||||
[ApiManagement]
|
[ApiManagement]
|
||||||
other = "API 管理"
|
other = "API 管理"
|
||||||
@ -614,7 +614,7 @@ other = "對遊客隱藏"
|
|||||||
other = "菜單"
|
other = "菜單"
|
||||||
|
|
||||||
[NetworkSpiter]
|
[NetworkSpiter]
|
||||||
other = "網絡"
|
other = "網路"
|
||||||
|
|
||||||
[EnableShowInService]
|
[EnableShowInService]
|
||||||
other = "在服務中顯示"
|
other = "在服務中顯示"
|
||||||
@ -656,4 +656,25 @@ other = "伺服器世界分布圖"
|
|||||||
other = "NAT"
|
other = "NAT"
|
||||||
|
|
||||||
[NetworkSpiterList]
|
[NetworkSpiterList]
|
||||||
other = "網絡監控"
|
other = "網路監控"
|
||||||
|
|
||||||
|
[Refresh]
|
||||||
|
other = "重新整理"
|
||||||
|
|
||||||
|
[CopyPath]
|
||||||
|
other = "複製路徑"
|
||||||
|
|
||||||
|
[Goto]
|
||||||
|
other = "跳至"
|
||||||
|
|
||||||
|
[GotoHeadline]
|
||||||
|
other = "跳至資料夾"
|
||||||
|
|
||||||
|
[GotoGo]
|
||||||
|
other = "確定"
|
||||||
|
|
||||||
|
[GotoClose]
|
||||||
|
other = "取消"
|
||||||
|
|
||||||
|
[FMError]
|
||||||
|
other = "Agent 回傳了錯誤,請查看主控台獲取詳細資訊。要建立新連線,請重新開啟 FM。"
|
||||||
|
14
resource/template/dashboard-default/file.html
vendored
14
resource/template/dashboard-default/file.html
vendored
@ -58,9 +58,9 @@
|
|||||||
<mdui-dropdown>
|
<mdui-dropdown>
|
||||||
<mdui-button-icon slot="trigger" icon="menu"></mdui-button-icon>
|
<mdui-button-icon slot="trigger" icon="menu"></mdui-button-icon>
|
||||||
<mdui-menu>
|
<mdui-menu>
|
||||||
<mdui-menu-item id="refresh">Refresh</mdui-menu-item>
|
<mdui-menu-item id="refresh">{{tr "Refresh"}}</mdui-menu-item>
|
||||||
<mdui-menu-item id="copy">Copy path</mdui-menu-item>
|
<mdui-menu-item id="copy">{{tr "CopyPath"}}</mdui-menu-item>
|
||||||
<mdui-menu-item id="goto">Go to</mdui-menu-item>
|
<mdui-menu-item id="goto">{{tr "Goto"}}</mdui-menu-item>
|
||||||
</mdui-menu>
|
</mdui-menu>
|
||||||
</mdui-dropdown>
|
</mdui-dropdown>
|
||||||
<span id="current-directory"></span>
|
<span id="current-directory"></span>
|
||||||
@ -70,16 +70,16 @@
|
|||||||
<mdui-list id="file-list" class="file-list"></mdui-list>
|
<mdui-list id="file-list" class="file-list"></mdui-list>
|
||||||
|
|
||||||
<mdui-dialog id="error-dialog" headline="Error"
|
<mdui-dialog id="error-dialog" headline="Error"
|
||||||
description="Agent returned an error, please view the console for details. To open a new connection, reopen the FM again."></mdui-dialog>
|
description="{{tr "FMError"}}"></mdui-dialog>
|
||||||
|
|
||||||
<mdui-dialog id="upd-modal" class="modal">
|
<mdui-dialog id="upd-modal" class="modal">
|
||||||
<mdui-linear-progress id="upd-progress"></mdui-linear-progress>
|
<mdui-linear-progress id="upd-progress"></mdui-linear-progress>
|
||||||
</mdui-dialog>
|
</mdui-dialog>
|
||||||
|
|
||||||
<mdui-dialog id="goto-dialog" headline="Go to a folder" close-on-overlay-click>
|
<mdui-dialog id="goto-dialog" headline="{{tr "GotoHeadline"}}" close-on-overlay-click>
|
||||||
<mdui-text-field id="goto-text" variant="outlined" value=""></mdui-text-field>
|
<mdui-text-field id="goto-text" variant="outlined" value=""></mdui-text-field>
|
||||||
<mdui-button id="goto-go" slot="action" variant="text">Go</mdui-button>
|
<mdui-button id="goto-go" slot="action" variant="text">{{tr "GotoGo"}}</mdui-button>
|
||||||
<mdui-button id="goto-close" slot="action" variant="tonal">Close</mdui-button>
|
<mdui-button id="goto-close" slot="action" variant="tonal">{{tr "GotoClose"}}</mdui-button>
|
||||||
</mdui-dialog>
|
</mdui-dialog>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
@ -159,7 +159,7 @@ func (s *NezhaHandler) ReportSystemInfo(c context.Context, r *pb.Host) (*pb.Rece
|
|||||||
|
|
||||||
} else {
|
} else {
|
||||||
// 虽然会在启动时panic, 可以断言不会走这个分支, 但是考虑到动态加载配置或者其它情况, 这里输出一下方便检查奇奇怪怪的BUG
|
// 虽然会在启动时panic, 可以断言不会走这个分支, 但是考虑到动态加载配置或者其它情况, 这里输出一下方便检查奇奇怪怪的BUG
|
||||||
log.Printf("NEZHA>> 未找到对应的DDNS配置(%s), 或者是provider填写不正确, 请前往config.yml检查你的设置\n", singleton.ServerList[clientID].DDNSProfile)
|
log.Printf("NEZHA>> 未找到对应的DDNS配置(%s), 或者是provider填写不正确, 请前往config.yml检查你的设置", singleton.ServerList[clientID].DDNSProfile)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -3,86 +3,72 @@ package singleton
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"slices"
|
||||||
|
|
||||||
ddns2 "github.com/naiba/nezha/pkg/ddns"
|
ddns2 "github.com/naiba/nezha/pkg/ddns"
|
||||||
)
|
)
|
||||||
|
|
||||||
func RetryableUpdateDomain(provider ddns2.Provider, config *ddns2.DomainConfig, maxRetries int) bool {
|
const (
|
||||||
if nil == config {
|
ProviderWebHook = "webhook"
|
||||||
return false
|
ProviderCloudflare = "cloudflare"
|
||||||
|
ProviderTencentCloud = "tencentcloud"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ProviderFunc func(*ddns2.DomainConfig) ddns2.Provider
|
||||||
|
|
||||||
|
func RetryableUpdateDomain(provider ddns2.Provider, domainConfig *ddns2.DomainConfig, maxRetries int) {
|
||||||
|
if domainConfig == nil {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
for retries := 0; retries < maxRetries; retries++ {
|
for retries := 0; retries < maxRetries; retries++ {
|
||||||
log.Printf("NEZHA>> 正在尝试更新域名(%s)DDNS(%d/%d)\n", config.FullDomain, retries+1, maxRetries)
|
log.Printf("NEZHA>> 正在尝试更新域名(%s)DDNS(%d/%d)", domainConfig.FullDomain, retries+1, maxRetries)
|
||||||
if provider.UpdateDomain(config) {
|
if err := provider.UpdateDomain(domainConfig); err != nil {
|
||||||
log.Printf("NEZHA>> 尝试更新域名(%s)DDNS成功\n", config.FullDomain)
|
log.Printf("NEZHA>> 尝试更新域名(%s)DDNS失败: %v", domainConfig.FullDomain, err)
|
||||||
return true
|
} else {
|
||||||
|
log.Printf("NEZHA>> 尝试更新域名(%s)DDNS成功", domainConfig.FullDomain)
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.Printf("NEZHA>> 尝试更新域名(%s)DDNS失败\n", config.FullDomain)
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deprecated
|
||||||
func GetDDNSProviderFromString(provider string) (ddns2.Provider, error) {
|
func GetDDNSProviderFromString(provider string) (ddns2.Provider, error) {
|
||||||
switch provider {
|
switch provider {
|
||||||
case "webhook":
|
case ProviderWebHook:
|
||||||
return &ddns2.ProviderWebHook{
|
return ddns2.NewProviderWebHook(Conf.DDNS.WebhookURL, Conf.DDNS.WebhookMethod, Conf.DDNS.WebhookRequestBody, Conf.DDNS.WebhookHeaders), nil
|
||||||
URL: Conf.DDNS.WebhookURL,
|
case ProviderCloudflare:
|
||||||
RequestMethod: Conf.DDNS.WebhookMethod,
|
return ddns2.NewProviderCloudflare(Conf.DDNS.AccessSecret), nil
|
||||||
RequestBody: Conf.DDNS.WebhookRequestBody,
|
case ProviderTencentCloud:
|
||||||
RequestHeader: Conf.DDNS.WebhookHeaders,
|
return ddns2.NewProviderTencentCloud(Conf.DDNS.AccessID, Conf.DDNS.AccessSecret), nil
|
||||||
}, nil
|
default:
|
||||||
case "dummy":
|
return new(ddns2.ProviderDummy), fmt.Errorf("无法找到配置的DDNS提供者 %s", provider)
|
||||||
return &ddns2.ProviderDummy{}, nil
|
|
||||||
case "cloudflare":
|
|
||||||
return &ddns2.ProviderCloudflare{
|
|
||||||
Secret: Conf.DDNS.AccessSecret,
|
|
||||||
}, nil
|
|
||||||
case "tencentcloud":
|
|
||||||
return &ddns2.ProviderTencentCloud{
|
|
||||||
SecretID: Conf.DDNS.AccessID,
|
|
||||||
SecretKey: Conf.DDNS.AccessSecret,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
return &ddns2.ProviderDummy{}, fmt.Errorf("无法找到配置的DDNS提供者%s", Conf.DDNS.Provider)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetDDNSProviderFromProfile(profileName string) (ddns2.Provider, error) {
|
func GetDDNSProviderFromProfile(profileName string) (ddns2.Provider, error) {
|
||||||
profile, ok := Conf.DDNS.Profiles[profileName]
|
profile, ok := Conf.DDNS.Profiles[profileName]
|
||||||
if !ok {
|
if !ok {
|
||||||
return &ddns2.ProviderDummy{}, fmt.Errorf("未找到配置项 %s", profileName)
|
return new(ddns2.ProviderDummy), fmt.Errorf("未找到配置项 %s", profileName)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch profile.Provider {
|
switch profile.Provider {
|
||||||
case "webhook":
|
case ProviderWebHook:
|
||||||
return &ddns2.ProviderWebHook{
|
return ddns2.NewProviderWebHook(profile.WebhookURL, profile.WebhookMethod, profile.WebhookRequestBody, profile.WebhookHeaders), nil
|
||||||
URL: profile.WebhookURL,
|
case ProviderCloudflare:
|
||||||
RequestMethod: profile.WebhookMethod,
|
return ddns2.NewProviderCloudflare(profile.AccessSecret), nil
|
||||||
RequestBody: profile.WebhookRequestBody,
|
case ProviderTencentCloud:
|
||||||
RequestHeader: profile.WebhookHeaders,
|
return ddns2.NewProviderTencentCloud(profile.AccessID, profile.AccessSecret), nil
|
||||||
}, nil
|
default:
|
||||||
case "dummy":
|
return new(ddns2.ProviderDummy), fmt.Errorf("无法找到配置的DDNS提供者 %s", profile.Provider)
|
||||||
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{}, fmt.Errorf("无法找到配置的DDNS提供者%s", profile.Provider)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func ValidateDDNSProvidersFromProfiles() error {
|
func ValidateDDNSProvidersFromProfiles() error {
|
||||||
validProviders := map[string]bool{"webhook": true, "dummy": true, "cloudflare": true, "tencentcloud": true}
|
validProviders := []string{ProviderWebHook, ProviderCloudflare, ProviderTencentCloud}
|
||||||
providers := make(map[string]string)
|
for _, profile := range Conf.DDNS.Profiles {
|
||||||
for profileName, profile := range Conf.DDNS.Profiles {
|
if ok := slices.Contains(validProviders, profile.Provider); !ok {
|
||||||
if _, ok := validProviders[profile.Provider]; !ok {
|
|
||||||
return fmt.Errorf("无法找到配置的DDNS提供者%s", profile.Provider)
|
return fmt.Errorf("无法找到配置的DDNS提供者%s", profile.Provider)
|
||||||
}
|
}
|
||||||
providers[profileName] = profile.Provider
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user