mirror of
https://github.com/Buriburizaem0n/nezha_domains.git
synced 2026-02-05 21:20:06 +00:00
ddns: store configuation in database (#435)
* ddns: store configuation in database Co-authored-by: nap0o <144927971+nap0o@users.noreply.github.com> * feat: split domain with soa lookup * switch to libdns interface * ddns: add unit test * ddns: skip TestSplitDomainSOA on ci network is not steady * fix error handling * fix error handling --------- Co-authored-by: nap0o <144927971+nap0o@users.noreply.github.com>
This commit is contained in:
@@ -125,7 +125,6 @@ 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) {
|
||||
var clientID uint64
|
||||
var provider ddns.Provider
|
||||
var err error
|
||||
if clientID, err = s.Auth.Check(c); err != nil {
|
||||
return nil, err
|
||||
@@ -135,33 +134,19 @@ func (s *NezhaHandler) ReportSystemInfo(c context.Context, r *pb.Host) (*pb.Rece
|
||||
defer singleton.ServerLock.RUnlock()
|
||||
|
||||
// 检查并更新DDNS
|
||||
if singleton.Conf.DDNS.Enable &&
|
||||
singleton.ServerList[clientID].EnableDDNS &&
|
||||
host.IP != "" &&
|
||||
if singleton.ServerList[clientID].EnableDDNS && host.IP != "" &&
|
||||
(singleton.ServerList[clientID].Host == nil || singleton.ServerList[clientID].Host.IP != host.IP) {
|
||||
serverDomain := singleton.ServerList[clientID].DDNSDomain
|
||||
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 != "" {
|
||||
ipv4, ipv6, _ := utils.SplitIPAddr(host.IP)
|
||||
maxRetries := int(singleton.Conf.DDNS.MaxRetries)
|
||||
config := &ddns.DomainConfig{
|
||||
EnableIPv4: singleton.ServerList[clientID].EnableIPv4,
|
||||
EnableIpv6: singleton.ServerList[clientID].EnableIpv6,
|
||||
FullDomain: serverDomain,
|
||||
Ipv4Addr: ipv4,
|
||||
Ipv6Addr: ipv6,
|
||||
ipv4, ipv6, _ := utils.SplitIPAddr(host.IP)
|
||||
providers, err := singleton.GetDDNSProvidersFromProfiles(singleton.ServerList[clientID].DDNSProfiles, &ddns.IP{Ipv4Addr: ipv4, Ipv6Addr: ipv6})
|
||||
if err == nil {
|
||||
for _, provider := range providers {
|
||||
go func(provider *ddns.Provider) {
|
||||
provider.UpdateDomain(context.Background())
|
||||
}(provider)
|
||||
}
|
||||
|
||||
go singleton.RetryableUpdateDomain(provider, config, maxRetries)
|
||||
} else {
|
||||
// 虽然会在启动时panic, 可以断言不会走这个分支, 但是考虑到动态加载配置或者其它情况, 这里输出一下方便检查奇奇怪怪的BUG
|
||||
log.Printf("NEZHA>> 未找到对应的DDNS配置(%s), 或者是provider填写不正确, 请前往config.yml检查你的设置", singleton.ServerList[clientID].DDNSProfile)
|
||||
log.Printf("NEZHA>> 获取DDNS配置时发生错误: %v", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 发送IP变动通知
|
||||
|
||||
@@ -2,73 +2,68 @@ package singleton
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"slices"
|
||||
"sync"
|
||||
|
||||
"github.com/libdns/cloudflare"
|
||||
"github.com/libdns/tencentcloud"
|
||||
|
||||
"github.com/naiba/nezha/model"
|
||||
ddns2 "github.com/naiba/nezha/pkg/ddns"
|
||||
"github.com/naiba/nezha/pkg/ddns/dummy"
|
||||
"github.com/naiba/nezha/pkg/ddns/webhook"
|
||||
)
|
||||
|
||||
const (
|
||||
ProviderWebHook = "webhook"
|
||||
ProviderCloudflare = "cloudflare"
|
||||
ProviderTencentCloud = "tencentcloud"
|
||||
var (
|
||||
ddnsCache map[uint64]*model.DDNSProfile
|
||||
ddnsCacheLock sync.RWMutex
|
||||
)
|
||||
|
||||
type ProviderFunc func(*ddns2.DomainConfig) ddns2.Provider
|
||||
func initDDNS() {
|
||||
OnDDNSUpdate()
|
||||
}
|
||||
|
||||
func RetryableUpdateDomain(provider ddns2.Provider, domainConfig *ddns2.DomainConfig, maxRetries int) {
|
||||
if domainConfig == nil {
|
||||
return
|
||||
func OnDDNSUpdate() {
|
||||
var ddns []*model.DDNSProfile
|
||||
DB.Find(&ddns)
|
||||
ddnsCacheLock.Lock()
|
||||
defer ddnsCacheLock.Unlock()
|
||||
ddnsCache = make(map[uint64]*model.DDNSProfile)
|
||||
for i := 0; i < len(ddns); i++ {
|
||||
ddnsCache[ddns[i].ID] = ddns[i]
|
||||
}
|
||||
for retries := 0; retries < maxRetries; retries++ {
|
||||
log.Printf("NEZHA>> 正在尝试更新域名(%s)DDNS(%d/%d)", domainConfig.FullDomain, retries+1, maxRetries)
|
||||
if err := provider.UpdateDomain(domainConfig); err != nil {
|
||||
log.Printf("NEZHA>> 尝试更新域名(%s)DDNS失败: %v", domainConfig.FullDomain, err)
|
||||
}
|
||||
|
||||
func GetDDNSProvidersFromProfiles(profileId []uint64, ip *ddns2.IP) ([]*ddns2.Provider, error) {
|
||||
profiles := make([]*model.DDNSProfile, 0, len(profileId))
|
||||
ddnsCacheLock.RLock()
|
||||
for _, id := range profileId {
|
||||
if profile, ok := ddnsCache[id]; ok {
|
||||
profiles = append(profiles, profile)
|
||||
} else {
|
||||
log.Printf("NEZHA>> 尝试更新域名(%s)DDNS成功", domainConfig.FullDomain)
|
||||
break
|
||||
return nil, fmt.Errorf("无法找到DDNS配置 ID %d", id)
|
||||
}
|
||||
}
|
||||
}
|
||||
ddnsCacheLock.RUnlock()
|
||||
|
||||
// Deprecated
|
||||
func GetDDNSProviderFromString(provider string) (ddns2.Provider, error) {
|
||||
switch provider {
|
||||
case ProviderWebHook:
|
||||
return ddns2.NewProviderWebHook(Conf.DDNS.WebhookURL, Conf.DDNS.WebhookMethod, Conf.DDNS.WebhookRequestBody, Conf.DDNS.WebhookHeaders), nil
|
||||
case ProviderCloudflare:
|
||||
return ddns2.NewProviderCloudflare(Conf.DDNS.AccessSecret), nil
|
||||
case ProviderTencentCloud:
|
||||
return ddns2.NewProviderTencentCloud(Conf.DDNS.AccessID, Conf.DDNS.AccessSecret), nil
|
||||
default:
|
||||
return new(ddns2.ProviderDummy), fmt.Errorf("无法找到配置的DDNS提供者 %s", provider)
|
||||
}
|
||||
}
|
||||
|
||||
func GetDDNSProviderFromProfile(profileName string) (ddns2.Provider, error) {
|
||||
profile, ok := Conf.DDNS.Profiles[profileName]
|
||||
if !ok {
|
||||
return new(ddns2.ProviderDummy), fmt.Errorf("未找到配置项 %s", profileName)
|
||||
}
|
||||
|
||||
switch profile.Provider {
|
||||
case ProviderWebHook:
|
||||
return ddns2.NewProviderWebHook(profile.WebhookURL, profile.WebhookMethod, profile.WebhookRequestBody, profile.WebhookHeaders), nil
|
||||
case ProviderCloudflare:
|
||||
return ddns2.NewProviderCloudflare(profile.AccessSecret), nil
|
||||
case ProviderTencentCloud:
|
||||
return ddns2.NewProviderTencentCloud(profile.AccessID, profile.AccessSecret), nil
|
||||
default:
|
||||
return new(ddns2.ProviderDummy), fmt.Errorf("无法找到配置的DDNS提供者 %s", profile.Provider)
|
||||
}
|
||||
}
|
||||
|
||||
func ValidateDDNSProvidersFromProfiles() error {
|
||||
validProviders := []string{ProviderWebHook, ProviderCloudflare, ProviderTencentCloud}
|
||||
for _, profile := range Conf.DDNS.Profiles {
|
||||
if ok := slices.Contains(validProviders, profile.Provider); !ok {
|
||||
return fmt.Errorf("无法找到配置的DDNS提供者%s", profile.Provider)
|
||||
providers := make([]*ddns2.Provider, 0, len(profiles))
|
||||
for _, profile := range profiles {
|
||||
provider := &ddns2.Provider{DDNSProfile: profile, IPAddrs: ip}
|
||||
switch profile.Provider {
|
||||
case model.ProviderDummy:
|
||||
provider.Setter = &dummy.Provider{}
|
||||
providers = append(providers, provider)
|
||||
case model.ProviderWebHook:
|
||||
provider.Setter = &webhook.Provider{DDNSProfile: profile}
|
||||
providers = append(providers, provider)
|
||||
case model.ProviderCloudflare:
|
||||
provider.Setter = &cloudflare.Provider{APIToken: profile.AccessSecret}
|
||||
providers = append(providers, provider)
|
||||
case model.ProviderTencentCloud:
|
||||
provider.Setter = &tencentcloud.Provider{SecretId: profile.AccessID, SecretKey: profile.AccessSecret}
|
||||
providers = append(providers, provider)
|
||||
default:
|
||||
return nil, fmt.Errorf("无法找到配置的DDNS提供者ID %d", profile.Provider)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return providers, nil
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package singleton
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
@@ -39,6 +38,7 @@ func LoadSingleton() {
|
||||
loadCronTasks() // 加载定时任务
|
||||
loadAPI()
|
||||
initNAT()
|
||||
initDDNS()
|
||||
}
|
||||
|
||||
// InitConfigFromPath 从给出的文件路径中加载配置
|
||||
@@ -48,25 +48,6 @@ func InitConfigFromPath(path string) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
validateConfig()
|
||||
}
|
||||
|
||||
// validateConfig 验证配置文件有效性
|
||||
func validateConfig() {
|
||||
var err error
|
||||
if Conf.DDNS.Provider == "" {
|
||||
err = ValidateDDNSProvidersFromProfiles()
|
||||
} else {
|
||||
_, err = GetDDNSProviderFromString(Conf.DDNS.Provider)
|
||||
}
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if Conf.DDNS.Enable {
|
||||
if Conf.DDNS.MaxRetries < 1 || Conf.DDNS.MaxRetries > 10 {
|
||||
panic(fmt.Errorf("DDNS.MaxRetries值域为[1, 10]的整数, 当前为 %d", Conf.DDNS.MaxRetries))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// InitDBFromPath 从给出的文件路径中加载数据库
|
||||
@@ -84,7 +65,7 @@ func InitDBFromPath(path string) {
|
||||
err = DB.AutoMigrate(model.Server{}, model.User{},
|
||||
model.Notification{}, model.AlertRule{}, model.Monitor{},
|
||||
model.MonitorHistory{}, model.Cron{}, model.Transfer{},
|
||||
model.ApiToken{}, model.NAT{})
|
||||
model.ApiToken{}, model.NAT{}, model.DDNSProfile{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user