2
0
mirror of https://github.com/acepanel/panel.git synced 2026-02-04 06:47:20 +08:00

feat(#419): 面板自动更新

This commit is contained in:
耗子
2025-01-02 19:00:19 +08:00
parent 813a8f42cf
commit 5dc14ef1bb
7 changed files with 78 additions and 23 deletions

View File

@@ -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 {

View File

@@ -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
}

View File

@@ -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"`

View File

@@ -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))
}
}
})
}

View File

@@ -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)

View File

@@ -17,6 +17,7 @@ const model = ref<Setting>({
port: 8888,
entrance: '',
offline_mode: false,
auto_update: false,
website_path: '',
backup_path: '',
https: false,
@@ -103,6 +104,9 @@ onMounted(() => {
<n-form-item :label="$t('settingIndex.edit.fields.offline.label')">
<n-switch v-model:value="model.offline_mode" />
</n-form-item>
<n-form-item label="自动更新">
<n-switch v-model:value="model.auto_update" />
</n-form-item>
<n-form-item :label="$t('settingIndex.edit.fields.path.label')">
<n-input
v-model:value="model.website_path"

View File

@@ -7,6 +7,7 @@ export interface Setting {
port: number
entrance: string
offline_mode: boolean
auto_update: boolean
website_path: string
backup_path: string
https: boolean