迁移配置表

This commit is contained in:
ryan
2026-06-22 19:49:37 +08:00
parent d9b8dc81ee
commit 92ceecc6ce
53 changed files with 1928 additions and 1152 deletions
+86 -25
View File
@@ -12,15 +12,70 @@ import (
"github.com/Rain-kl/Wavelet/internal/apps/openflare/routeidentity"
"github.com/Rain-kl/Wavelet/internal/model"
"github.com/Rain-kl/Wavelet/internal/repository"
)
const uptimeKumaTagOpenFlare = "OpenFlare"
var isSyncing atomic.Bool
// kumaConfig 封装 UptimeKuma 配置
type kumaConfig struct {
URL string
Username string
Password string
MonitorScope string
SelectedSites string
Interval int
Retry int
RetryInterval int
Timeout int
}
// loadKumaConfig 从 SystemConfig 加载 UptimeKuma 配置
func loadKumaConfig(ctx context.Context) (*kumaConfig, error) {
url, _ := repository.GetSystemConfigByKey(ctx, model.ConfigKeyUptimeKumaURL)
username, _ := repository.GetSystemConfigByKey(ctx, model.ConfigKeyUptimeKumaUsername)
password, _ := repository.GetSystemConfigByKey(ctx, model.ConfigKeyUptimeKumaPassword)
scope, _ := repository.GetSystemConfigByKey(ctx, model.ConfigKeyUptimeKumaMonitorScope)
selected, _ := repository.GetSystemConfigByKey(ctx, model.ConfigKeyUptimeKumaSelectedSites)
interval, _ := repository.GetIntByKey(ctx, model.ConfigKeyUptimeKumaInterval)
if interval <= 0 {
interval = 60
}
retry, _ := repository.GetIntByKey(ctx, model.ConfigKeyUptimeKumaRetry)
retryInterval, _ := repository.GetIntByKey(ctx, model.ConfigKeyUptimeKumaRetryInterval)
if retryInterval <= 0 {
retryInterval = 60
}
timeout, _ := repository.GetIntByKey(ctx, model.ConfigKeyUptimeKumaTimeout)
if timeout <= 0 {
timeout = 48
}
if scope.Value == "" {
scope.Value = "all"
}
return &kumaConfig{
URL: strings.TrimSpace(url.Value),
Username: strings.TrimSpace(username.Value),
Password: strings.TrimSpace(password.Value),
MonitorScope: scope.Value,
SelectedSites: selected.Value,
Interval: interval,
Retry: retry,
RetryInterval: retryInterval,
Timeout: timeout,
}, nil
}
// SyncToUptimeKuma synchronizes enabled proxy routes to Uptime Kuma monitors.
func SyncToUptimeKuma(ctx context.Context) error {
if !model.UptimeKumaEnabled {
// 检查是否启用
enabled, _ := repository.GetBoolByKey(ctx, model.ConfigKeyUptimeKumaEnabled)
if !enabled {
return fmt.Errorf("uptime Kuma integration is disabled")
}
@@ -29,15 +84,21 @@ func SyncToUptimeKuma(ctx context.Context) error {
}
defer isSyncing.Store(false)
kumaURL, kumaUsername, kumaPassword, err := validateUptimeKumaConfig()
// 加载配置
config, err := loadKumaConfig(ctx)
if err != nil {
return err
}
// 验证配置
if err := validateKumaConfig(config); err != nil {
return err
}
slog.Info("Starting Uptime Kuma sync process",
"url", kumaURL,
"username", kumaUsername,
"scope", model.UptimeKumaMonitorScope,
"url", config.URL,
"username", config.Username,
"scope", config.MonitorScope,
)
allRoutes, err := model.ListProxyRoutes(ctx)
@@ -45,12 +106,12 @@ func SyncToUptimeKuma(ctx context.Context) error {
return fmt.Errorf("failed to list local proxy routes: %w", err)
}
expectedRoutes, err := filterExpectedRoutes(allRoutes)
expectedRoutes, err := filterExpectedRoutes(allRoutes, config)
if err != nil {
return err
}
client, err := connectAndLoginUptimeKuma(kumaURL, kumaUsername, kumaPassword)
client, err := connectAndLoginUptimeKuma(config.URL, config.Username, config.Password)
if err != nil {
return err
}
@@ -62,16 +123,16 @@ func SyncToUptimeKuma(ctx context.Context) error {
}
existingOpenFlareMonitors := filterOpenFlareMonitors(client.GetMonitorList(), openFlareTagID)
expectedSitesMap := syncRouteMonitors(client, expectedRoutes, existingOpenFlareMonitors, openFlareTagID)
expectedSitesMap := syncRouteMonitors(client, expectedRoutes, existingOpenFlareMonitors, openFlareTagID, config)
removeStaleMonitors(client, existingOpenFlareMonitors, expectedSitesMap)
return nil
}
func filterExpectedRoutes(allRoutes []*model.ProxyRoute) ([]*model.ProxyRoute, error) {
scope := model.UptimeKumaMonitorScope
func filterExpectedRoutes(allRoutes []*model.ProxyRoute, config *kumaConfig) ([]*model.ProxyRoute, error) {
scope := config.MonitorScope
if scope == "selected" {
selectedList := strings.Split(model.UptimeKumaSelectedSites, ",")
selectedList := strings.Split(config.SelectedSites, ",")
selectedMap := make(map[string]bool)
for _, name := range selectedList {
trimmedName := strings.TrimSpace(name)
@@ -175,15 +236,15 @@ func routeMonitorURL(route *model.ProxyRoute) (string, error) {
return "http://" + domain, nil
}
func monitorPayload(id int, name, targetURL string) map[string]any {
func monitorPayload(id int, name, targetURL string, config *kumaConfig) map[string]any {
payload := map[string]any{
"type": "http",
"name": name,
"url": targetURL,
"interval": model.UptimeKumaInterval,
"maxretries": model.UptimeKumaRetry,
"retryInterval": model.UptimeKumaRetryInterval,
"timeout": model.UptimeKumaTimeout,
"interval": config.Interval,
"maxretries": config.Retry,
"retryInterval": config.RetryInterval,
"timeout": config.Timeout,
"active": true,
"resendInterval": 0,
"expiryNotification": false,
@@ -198,17 +259,17 @@ func monitorPayload(id int, name, targetURL string) map[string]any {
return payload
}
func monitorNeedsUpdate(existing Monitor, targetURL string) bool {
func monitorNeedsUpdate(existing Monitor, targetURL string, config *kumaConfig) bool {
return existing.URL != targetURL ||
existing.Interval != model.UptimeKumaInterval ||
existing.MaxRetries != model.UptimeKumaRetry ||
existing.RetryInterval != model.UptimeKumaRetryInterval ||
existing.Timeout != model.UptimeKumaTimeout
existing.Interval != config.Interval ||
existing.MaxRetries != config.Retry ||
existing.RetryInterval != config.RetryInterval ||
existing.Timeout != config.Timeout
}
func createMonitor(client *SocketIOClient, siteName, targetURL string, openFlareTagID int) error {
func createMonitor(client *SocketIOClient, siteName, targetURL string, openFlareTagID int, config *kumaConfig) error {
slog.Info("Creating monitor in Uptime Kuma", "name", siteName, "url", targetURL)
addAck, err := client.Emit("add", monitorPayload(0, siteName, targetURL))
addAck, err := client.Emit("add", monitorPayload(0, siteName, targetURL, config))
if err != nil {
return err
}
@@ -238,9 +299,9 @@ func createMonitor(client *SocketIOClient, siteName, targetURL string, openFlare
return nil
}
func updateMonitor(client *SocketIOClient, monitorID int, siteName, targetURL string) error {
func updateMonitor(client *SocketIOClient, monitorID int, siteName, targetURL string, config *kumaConfig) error {
slog.Info("Updating monitor in Uptime Kuma due to settings mismatch", "name", siteName)
editAck, err := client.Emit("editMonitor", monitorPayload(monitorID, siteName, targetURL))
editAck, err := client.Emit("editMonitor", monitorPayload(monitorID, siteName, targetURL, config))
if err != nil {
return err
}
@@ -14,17 +14,18 @@ import (
const monitorListWaitTimeout = 5 * time.Second
func validateUptimeKumaConfig() (string, string, string, error) {
kumaURL := strings.TrimSpace(model.UptimeKumaURL)
kumaUsername := strings.TrimSpace(model.UptimeKumaUsername)
kumaPassword := strings.TrimSpace(model.UptimeKumaPassword)
if kumaURL == "" || kumaUsername == "" || kumaPassword == "" {
return kumaURL, kumaUsername, kumaPassword, fmt.Errorf(
"uptime Kuma URL, username, or password is not configured (URL: %q, Username: %q, PasswordLength: %d)",
kumaURL, kumaUsername, len(kumaPassword),
)
// validateKumaConfig 验证 kumaConfig 配置完整性
func validateKumaConfig(config *kumaConfig) error {
if strings.TrimSpace(config.URL) == "" {
return fmt.Errorf("uptime Kuma URL is not configured")
}
return kumaURL, kumaUsername, kumaPassword, nil
if strings.TrimSpace(config.Username) == "" {
return fmt.Errorf("uptime Kuma username is not configured")
}
if strings.TrimSpace(config.Password) == "" {
return fmt.Errorf("uptime Kuma password is not configured")
}
return nil
}
func connectAndLoginUptimeKuma(kumaURL, kumaUsername, kumaPassword string) (*SocketIOClient, error) {
@@ -68,7 +69,7 @@ func connectAndLoginUptimeKuma(kumaURL, kumaUsername, kumaPassword string) (*Soc
return client, nil
}
func syncRouteMonitors(client *SocketIOClient, expectedRoutes []*model.ProxyRoute, existingMonitors map[string]Monitor, openFlareTagID int) map[string]bool {
func syncRouteMonitors(client *SocketIOClient, expectedRoutes []*model.ProxyRoute, existingMonitors map[string]Monitor, openFlareTagID int, config *kumaConfig) map[string]bool {
expectedSitesMap := make(map[string]bool, len(expectedRoutes))
for _, route := range expectedRoutes {
expectedSitesMap[route.SiteName] = true
@@ -80,13 +81,13 @@ func syncRouteMonitors(client *SocketIOClient, expectedRoutes []*model.ProxyRout
existing, exists := existingMonitors[route.SiteName]
if !exists {
if err := createMonitor(client, route.SiteName, targetURL, openFlareTagID); err != nil {
if err := createMonitor(client, route.SiteName, targetURL, openFlareTagID, config); err != nil {
slog.Error("Failed to add monitor to Uptime Kuma", "name", route.SiteName, "error", err)
}
continue
}
if monitorNeedsUpdate(existing, targetURL) {
if err := updateMonitor(client, existing.ID, route.SiteName, targetURL); err != nil {
if monitorNeedsUpdate(existing, targetURL, config) {
if err := updateMonitor(client, existing.ID, route.SiteName, targetURL, config); err != nil {
slog.Error("Failed to edit monitor in Uptime Kuma", "name", route.SiteName, "error", err)
}
}
+56 -44
View File
@@ -17,6 +17,7 @@ import (
"github.com/Rain-kl/Wavelet/internal/db"
"github.com/Rain-kl/Wavelet/internal/model"
"github.com/Rain-kl/Wavelet/internal/repository"
"github.com/glebarez/sqlite"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -121,7 +122,7 @@ func setupSyncTestDB(t *testing.T) func() {
DisableForeignKeyConstraintWhenMigrating: true,
})
require.NoError(t, err)
require.NoError(t, sqliteDB.AutoMigrate(&model.ProxyRoute{}))
require.NoError(t, sqliteDB.AutoMigrate(&model.ProxyRoute{}, &model.SystemConfig{}))
db.SetDB(sqliteDB)
return func() {
@@ -129,41 +130,50 @@ func setupSyncTestDB(t *testing.T) func() {
}
}
func backupUptimeKumaConfig() func() {
oldEnabled := model.UptimeKumaEnabled
oldURL := model.UptimeKumaURL
oldUsername := model.UptimeKumaUsername
oldPassword := model.UptimeKumaPassword
oldScope := model.UptimeKumaMonitorScope
oldSelected := model.UptimeKumaSelectedSites
oldInterval := model.UptimeKumaInterval
oldRetry := model.UptimeKumaRetry
oldRetryInterval := model.UptimeKumaRetryInterval
oldTimeout := model.UptimeKumaTimeout
func backupUptimeKumaConfig(ctx context.Context) func() {
// 备份所有 UptimeKuma 相关配置
configs := []string{
model.ConfigKeyUptimeKumaEnabled,
model.ConfigKeyUptimeKumaURL,
model.ConfigKeyUptimeKumaUsername,
model.ConfigKeyUptimeKumaPassword,
model.ConfigKeyUptimeKumaMonitorScope,
model.ConfigKeyUptimeKumaSelectedSites,
model.ConfigKeyUptimeKumaInterval,
model.ConfigKeyUptimeKumaRetry,
model.ConfigKeyUptimeKumaRetryInterval,
model.ConfigKeyUptimeKumaTimeout,
}
oldValues := make(map[string]string)
for _, key := range configs {
config, _ := repository.GetSystemConfigByKey(ctx, key)
oldValues[key] = config.Value
}
return func() {
model.UptimeKumaEnabled = oldEnabled
model.UptimeKumaURL = oldURL
model.UptimeKumaUsername = oldUsername
model.UptimeKumaPassword = oldPassword
model.UptimeKumaMonitorScope = oldScope
model.UptimeKumaSelectedSites = oldSelected
model.UptimeKumaInterval = oldInterval
model.UptimeKumaRetry = oldRetry
model.UptimeKumaRetryInterval = oldRetryInterval
model.UptimeKumaTimeout = oldTimeout
// 恢复所有配置
for key, value := range oldValues {
_ = db.DB(ctx).Model(&model.SystemConfig{}).Where("key = ?", key).Update("value", value).Error
}
}
}
// setTestConfig 设置测试配置的辅助函数(不存在则创建)
func setTestConfig(ctx context.Context, key, value string) {
_ = repository.SaveOrUpdateSystemConfig(ctx, key, value)
}
func TestSyncToUptimeKumaDisabled(t *testing.T) {
cleanup := setupSyncTestDB(t)
defer cleanup()
restore := backupUptimeKumaConfig()
ctx := context.Background()
restore := backupUptimeKumaConfig(ctx)
defer restore()
model.UptimeKumaEnabled = false
setTestConfig(ctx, model.ConfigKeyUptimeKumaEnabled, "false")
err := SyncToUptimeKuma(context.Background())
err := SyncToUptimeKuma(ctx)
require.Error(t, err)
assert.Contains(t, err.Error(), "disabled")
}
@@ -171,9 +181,9 @@ func TestSyncToUptimeKumaDisabled(t *testing.T) {
func TestSyncToUptimeKumaSuccess(t *testing.T) {
cleanup := setupSyncTestDB(t)
defer cleanup()
restore := backupUptimeKumaConfig()
defer restore()
ctx := context.Background()
restore := backupUptimeKumaConfig(ctx)
defer restore()
require.NoError(t, db.DB(ctx).Where("1 = 1").Delete(&model.ProxyRoute{}).Error)
@@ -227,15 +237,16 @@ func TestSyncToUptimeKumaSuccess(t *testing.T) {
server := httptest.NewServer(mockSrv)
defer server.Close()
model.UptimeKumaEnabled = true
model.UptimeKumaURL = server.URL
model.UptimeKumaUsername = "admin"
model.UptimeKumaPassword = "password"
model.UptimeKumaMonitorScope = "all"
model.UptimeKumaInterval = 60
model.UptimeKumaRetry = 0
model.UptimeKumaRetryInterval = 60
model.UptimeKumaTimeout = 48
// 设置测试配置
setTestConfig(ctx, model.ConfigKeyUptimeKumaEnabled, "true")
setTestConfig(ctx, model.ConfigKeyUptimeKumaURL, server.URL)
setTestConfig(ctx, model.ConfigKeyUptimeKumaUsername, "admin")
setTestConfig(ctx, model.ConfigKeyUptimeKumaPassword, "password")
setTestConfig(ctx, model.ConfigKeyUptimeKumaMonitorScope, "all")
setTestConfig(ctx, model.ConfigKeyUptimeKumaInterval, "60")
setTestConfig(ctx, model.ConfigKeyUptimeKumaRetry, "0")
setTestConfig(ctx, model.ConfigKeyUptimeKumaRetryInterval, "60")
setTestConfig(ctx, model.ConfigKeyUptimeKumaTimeout, "48")
require.NoError(t, SyncToUptimeKuma(ctx))
@@ -282,9 +293,9 @@ func TestSyncToUptimeKumaSuccess(t *testing.T) {
func TestSyncToUptimeKumaSelectedScope(t *testing.T) {
cleanup := setupSyncTestDB(t)
defer cleanup()
restore := backupUptimeKumaConfig()
defer restore()
ctx := context.Background()
restore := backupUptimeKumaConfig(ctx)
defer restore()
require.NoError(t, db.DB(ctx).Where("1 = 1").Delete(&model.ProxyRoute{}).Error)
@@ -312,12 +323,13 @@ func TestSyncToUptimeKumaSelectedScope(t *testing.T) {
server := httptest.NewServer(mockSrv)
defer server.Close()
model.UptimeKumaEnabled = true
model.UptimeKumaURL = server.URL
model.UptimeKumaUsername = "admin"
model.UptimeKumaPassword = "password"
model.UptimeKumaMonitorScope = "selected"
model.UptimeKumaSelectedSites = "site-a"
// 设置测试配置
setTestConfig(ctx, model.ConfigKeyUptimeKumaEnabled, "true")
setTestConfig(ctx, model.ConfigKeyUptimeKumaURL, server.URL)
setTestConfig(ctx, model.ConfigKeyUptimeKumaUsername, "admin")
setTestConfig(ctx, model.ConfigKeyUptimeKumaPassword, "password")
setTestConfig(ctx, model.ConfigKeyUptimeKumaMonitorScope, "selected")
setTestConfig(ctx, model.ConfigKeyUptimeKumaSelectedSites, "site-a")
require.NoError(t, SyncToUptimeKuma(ctx))