diff --git a/internal/biz/setting.go b/internal/biz/setting.go index 1bc7cea9..10f75091 100644 --- a/internal/biz/setting.go +++ b/internal/biz/setting.go @@ -18,6 +18,7 @@ const ( SettingKeyWebsitePath SettingKey = "website_path" SettingKeyMySQLRootPassword SettingKey = "mysql_root_password" SettingKeyOfflineMode SettingKey = "offline_mode" + SettingKeyAutoUpdate SettingKey = "auto_update" ) type Setting struct { diff --git a/internal/data/setting.go b/internal/data/setting.go index cb6d8995..65db8c32 100644 --- a/internal/data/setting.go +++ b/internal/data/setting.go @@ -93,7 +93,11 @@ func (r *settingRepo) GetPanelSetting(ctx context.Context) (*request.PanelSettin if err != nil { return nil, err } - offlineMode, err := r.Get(biz.SettingKeyOfflineMode) + offlineMode, err := r.GetBool(biz.SettingKeyOfflineMode) + if err != nil { + return nil, err + } + autoUpdate, err := r.GetBool(biz.SettingKeyAutoUpdate) if err != nil { return nil, err } @@ -125,7 +129,8 @@ func (r *settingRepo) GetPanelSetting(ctx context.Context) (*request.PanelSettin Name: name, Locale: r.conf.String("app.locale"), Entrance: r.conf.String("http.entrance"), - OfflineMode: cast.ToBool(offlineMode), + OfflineMode: offlineMode, + AutoUpdate: autoUpdate, WebsitePath: websitePath, BackupPath: backupPath, Username: user.Username, @@ -144,6 +149,9 @@ func (r *settingRepo) UpdatePanelSetting(ctx context.Context, setting *request.P if err := r.Set(biz.SettingKeyOfflineMode, cast.ToString(setting.OfflineMode)); err != nil { return false, err } + if err := r.Set(biz.SettingKeyAutoUpdate, cast.ToString(setting.AutoUpdate)); err != nil { + return false, err + } if err := r.Set(biz.SettingKeyWebsitePath, setting.WebsitePath); err != nil { return false, err } diff --git a/internal/http/request/setting.go b/internal/http/request/setting.go index 9c303a51..9ca48756 100644 --- a/internal/http/request/setting.go +++ b/internal/http/request/setting.go @@ -5,6 +5,7 @@ type PanelSetting struct { Locale string `json:"locale" validate:"required"` Entrance string `json:"entrance" validate:"required"` OfflineMode bool `json:"offline_mode"` + AutoUpdate bool `json:"auto_update"` WebsitePath string `json:"website_path" validate:"required"` BackupPath string `json:"backup_path" validate:"required"` Username string `json:"username" validate:"required"` diff --git a/internal/job/panel_task.go b/internal/job/panel_task.go index 0bdbf7c2..fee6f497 100644 --- a/internal/job/panel_task.go +++ b/internal/job/panel_task.go @@ -7,14 +7,18 @@ import ( "runtime/debug" "time" + "github.com/go-rat/utils/collect" + "github.com/hashicorp/go-version" "gorm.io/gorm" "github.com/tnb-labs/panel/internal/app" "github.com/tnb-labs/panel/internal/biz" + "github.com/tnb-labs/panel/pkg/api" ) // PanelTask 面板每日任务 type PanelTask struct { + api *api.API db *gorm.DB log *slog.Logger backupRepo biz.BackupRepo @@ -24,6 +28,7 @@ type PanelTask struct { func NewPanelTask(db *gorm.DB, log *slog.Logger, backup biz.BackupRepo, cache biz.CacheRepo, setting biz.SettingRepo) *PanelTask { return &PanelTask{ + api: api.NewAPI(app.Version), db: db, log: log, backupRepo: backup, @@ -51,30 +56,19 @@ func (r *PanelTask) Run() { } // 清理备份 - path, err := r.backupRepo.GetPath("panel") - if err == nil { + if path, err := r.backupRepo.GetPath("panel"); err == nil { if err = r.backupRepo.ClearExpired(path, "panel_", 10); err != nil { r.log.Warn("[Panel Task] failed to clear backup", slog.Any("err", err)) } } - // 更新商店缓存 - time.AfterFunc(time.Duration(rand.IntN(300))*time.Second, func() { - if offline, err := r.settingRepo.GetBool(biz.SettingKeyOfflineMode); err == nil && !offline { - if err = r.cacheRepo.UpdateApps(); err != nil { - r.log.Warn("[Panel Task] failed to update apps cache", slog.Any("err", err)) - } + if offline, err := r.settingRepo.GetBool(biz.SettingKeyOfflineMode); err == nil && !offline { + r.updateApps() + r.updateRewrites() + if autoUpdate, err := r.settingRepo.GetBool(biz.SettingKeyAutoUpdate); err == nil && autoUpdate { + r.updatePanel() } - }) - - // 更新伪静态缓存 - time.AfterFunc(time.Duration(rand.IntN(300))*time.Second, func() { - if offline, err := r.settingRepo.GetBool(biz.SettingKeyOfflineMode); err == nil && !offline { - if err = r.cacheRepo.UpdateRewrites(); err != nil { - r.log.Warn("[Panel Task] failed to update rewrites cache", slog.Any("err", err)) - } - } - }) + } // 回收内存 runtime.GC() @@ -82,3 +76,48 @@ func (r *PanelTask) Run() { app.Status = app.StatusNormal } + +// 更新商店缓存 +func (r *PanelTask) updateApps() { + time.AfterFunc(time.Duration(rand.IntN(300))*time.Second, func() { + if err := r.cacheRepo.UpdateApps(); err != nil { + r.log.Warn("[Panel Task] failed to update apps cache", slog.Any("err", err)) + } + }) +} + +// 更新伪静态缓存 +func (r *PanelTask) updateRewrites() { + time.AfterFunc(time.Duration(rand.IntN(300))*time.Second, func() { + if err := r.cacheRepo.UpdateRewrites(); err != nil { + r.log.Warn("[Panel Task] failed to update rewrites cache", slog.Any("err", err)) + } + }) +} + +// 更新面板 +func (r *PanelTask) updatePanel() { + // 加 300 秒确保在缓存更新后才更新面板 + time.AfterFunc(time.Duration(rand.IntN(300))*time.Second+300*time.Second, func() { + panel, err := r.api.LatestVersion() + if err != nil { + return + } + old, err := version.NewVersion(app.Version) + if err != nil { + return + } + current, err := version.NewVersion(panel.Version) + if err != nil { + return + } + if !current.GreaterThan(old) { + return + } + if download := collect.First(panel.Downloads); download != nil { + if err = r.backupRepo.UpdatePanel(panel.Version, download.URL, download.Checksum); err != nil { + r.log.Warn("[Panel Task] failed to update panel", slog.Any("err", err)) + } + } + }) +} diff --git a/internal/service/cli.go b/internal/service/cli.go index 1274dca8..397f427d 100644 --- a/internal/service/cli.go +++ b/internal/service/cli.go @@ -99,9 +99,8 @@ func (s *CliService) Update(ctx context.Context, cmd *cli.Command) error { if download == nil { return fmt.Errorf("下载地址为空") } - ver, url, checksum := panel.Version, download.URL, download.Checksum - return s.backupRepo.UpdatePanel(ver, url, checksum) + return s.backupRepo.UpdatePanel(panel.Version, download.URL, download.Checksum) } func (s *CliService) Sync(ctx context.Context, cmd *cli.Command) error { @@ -820,11 +819,13 @@ func (s *CliService) Init(ctx context.Context, cmd *cli.Command) error { settings := []biz.Setting{ {Key: biz.SettingKeyName, Value: "耗子面板"}, - {Key: biz.SettingKeyMonitor, Value: "1"}, + {Key: biz.SettingKeyMonitor, Value: "true"}, {Key: biz.SettingKeyMonitorDays, Value: "30"}, {Key: biz.SettingKeyBackupPath, Value: filepath.Join(app.Root, "backup")}, {Key: biz.SettingKeyWebsitePath, Value: filepath.Join(app.Root, "wwwroot")}, {Key: biz.SettingKeyVersion, Value: app.Version}, + {Key: biz.SettingKeyOfflineMode, Value: "false"}, + {Key: biz.SettingKeyAutoUpdate, Value: "false"}, } if err := s.db.Create(&settings).Error; err != nil { return fmt.Errorf("初始化失败:%v", err) diff --git a/web/src/views/setting/SettingBase.vue b/web/src/views/setting/SettingBase.vue index bf42e350..780ff3fa 100644 --- a/web/src/views/setting/SettingBase.vue +++ b/web/src/views/setting/SettingBase.vue @@ -17,6 +17,7 @@ const model = ref({ port: 8888, entrance: '', offline_mode: false, + auto_update: false, website_path: '', backup_path: '', https: false, @@ -103,6 +104,9 @@ onMounted(() => { + + +