diff --git a/app/console/commands/cert_renew.go b/app/console/commands/cert_renew.go index 0cd95f7f84..d9566c9041 100644 --- a/app/console/commands/cert_renew.go +++ b/app/console/commands/cert_renew.go @@ -10,9 +10,11 @@ import ( "github.com/goravel/framework/support/carbon" "panel/app/models" + "panel/internal" "panel/internal/services" ) +// CertRenew 证书续签 type CertRenew struct { } @@ -35,6 +37,10 @@ func (receiver *CertRenew) Extend() command.Extend { // Handle Execute the console command. func (receiver *CertRenew) Handle(ctx console.Context) error { + if internal.Status != internal.StatusNormal { + return nil + } + var certs []models.Cert err := facades.Orm().Query().With("Website").With("User").With("DNS").Find(&certs) if err != nil { diff --git a/app/console/commands/monitoring.go b/app/console/commands/monitoring.go index 8b080037e4..cb10fcaff2 100644 --- a/app/console/commands/monitoring.go +++ b/app/console/commands/monitoring.go @@ -11,10 +11,12 @@ import ( "github.com/spf13/cast" "panel/app/models" + "panel/internal" "panel/internal/services" "panel/pkg/tools" ) +// Monitoring 系统监控 type Monitoring struct { } @@ -37,6 +39,10 @@ func (receiver *Monitoring) Extend() command.Extend { // Handle Execute the console command. func (receiver *Monitoring) Handle(ctx console.Context) error { + if internal.Status != internal.StatusNormal { + return nil + } + var setting models.Setting monitor := services.NewSettingImpl().Get(models.SettingKeyMonitor) if !cast.ToBool(monitor) { diff --git a/app/console/commands/panel.go b/app/console/commands/panel.go index 0252138d8a..dd0b52750c 100644 --- a/app/console/commands/panel.go +++ b/app/console/commands/panel.go @@ -13,12 +13,14 @@ import ( "github.com/goravel/framework/facades" "github.com/goravel/framework/support/carbon" "github.com/spf13/cast" + "panel/internal" "panel/app/models" "panel/internal/services" "panel/pkg/tools" ) +// Panel 面板命令行 type Panel struct { } @@ -92,12 +94,14 @@ func (receiver *Panel) Handle(ctx console.Context) error { return err } - err = tools.UpdatePanel(panel) - if err != nil { + internal.Status = internal.StatusUpgrade + if err = tools.UpdatePanel(panel); err != nil { + internal.Status = internal.StatusFailed color.Redln("更新失败: " + err.Error()) return nil } + internal.Status = internal.StatusNormal color.Greenln("更新成功") tools.RestartPanel() diff --git a/app/console/commands/panel_task.go b/app/console/commands/panel_task.go new file mode 100644 index 0000000000..2a3008cf40 --- /dev/null +++ b/app/console/commands/panel_task.go @@ -0,0 +1,67 @@ +package commands + +import ( + "github.com/goravel/framework/contracts/console" + "github.com/goravel/framework/contracts/console/command" + "github.com/goravel/framework/facades" + "github.com/goravel/framework/support/carbon" + + "panel/internal" + "panel/pkg/tools" +) + +// PanelTask 面板每日任务 +type PanelTask struct { +} + +// Signature The name and signature of the console command. +func (receiver *PanelTask) Signature() string { + return "panel:task" +} + +// Description The console command description. +func (receiver *PanelTask) Description() string { + return "[面板] 每日任务" +} + +// Extend The console command extend. +func (receiver *PanelTask) Extend() command.Extend { + return command.Extend{ + Category: "panel", + } +} + +// Handle Execute the console command. +func (receiver *PanelTask) Handle(ctx console.Context) error { + internal.Status = internal.StatusMaintain + + // 优化数据库 + if _, err := facades.Orm().Query().Exec("VACUUM"); err != nil { + facades.Log().Tags("面板", "每日任务"). + With(map[string]any{ + "error": err.Error(), + }).Error("优化面板数据库失败") + return err + } + + // 备份面板 + if err := tools.Archive([]string{"/www/panel"}, "/www/backup/panel/panel-"+carbon.Now().ToShortDateTimeString()+".zip"); err != nil { + facades.Log().Tags("面板", "每日任务"). + With(map[string]any{ + "error": err.Error(), + }).Error("备份面板失败") + return err + } + + // 清理 7 天前的备份 + if _, err := tools.Exec(`find /www/backup/panel -mtime +7 -name "*.zip" -exec rm -rf {} \;`); err != nil { + facades.Log().Tags("面板", "每日任务"). + With(map[string]any{ + "error": err.Error(), + }).Error("清理面板备份失败") + return err + } + + internal.Status = internal.StatusNormal + return nil +} diff --git a/app/console/kernel.go b/app/console/kernel.go index a1430717f4..d5ab41f0de 100644 --- a/app/console/kernel.go +++ b/app/console/kernel.go @@ -14,7 +14,8 @@ type Kernel struct { func (kernel *Kernel) Schedule() []schedule.Event { return []schedule.Event{ facades.Schedule().Command("panel:monitoring").EveryMinute().SkipIfStillRunning(), - facades.Schedule().Command("panel:cert-renew").Daily().SkipIfStillRunning(), + facades.Schedule().Command("panel:cert-renew").DailyAt("04:00").SkipIfStillRunning(), + facades.Schedule().Command("panel:task").Daily().SkipIfStillRunning(), } } @@ -23,5 +24,6 @@ func (kernel *Kernel) Commands() []console.Command { &commands.Panel{}, &commands.Monitoring{}, &commands.CertRenew{}, + &commands.PanelTask{}, } } diff --git a/app/http/controllers/info_controller.go b/app/http/controllers/info_controller.go index fd2037d6da..0a3513cd32 100644 --- a/app/http/controllers/info_controller.go +++ b/app/http/controllers/info_controller.go @@ -309,14 +309,16 @@ func (r *InfoController) Update(ctx http.Context) http.Response { return Error(ctx, http.StatusInternalServerError, "获取最新版本失败") } - err = tools.UpdatePanel(panel) - if err != nil { + internal.Status = internal.StatusUpgrade + if err = tools.UpdatePanel(panel); err != nil { + internal.Status = internal.StatusFailed facades.Log().Request(ctx.Request()).Tags("面板", "基础信息").With(map[string]any{ "error": err.Error(), }).Info("更新面板失败") return Error(ctx, http.StatusInternalServerError, err.Error()) } + internal.Status = internal.StatusNormal tools.RestartPanel() return Success(ctx, nil) } diff --git a/app/http/kernel.go b/app/http/kernel.go index 729703aac8..585290770d 100644 --- a/app/http/kernel.go +++ b/app/http/kernel.go @@ -2,6 +2,8 @@ package http import ( "github.com/goravel/framework/contracts/http" + + "panel/app/http/middleware" ) type Kernel struct { @@ -10,5 +12,7 @@ type Kernel struct { // The application's global HTTP middleware stack. // These middleware are run during every request to your application. func (kernel Kernel) Middleware() []http.Middleware { - return []http.Middleware{} + return []http.Middleware{ + middleware.Status(), + } } diff --git a/app/http/middleware/status.go b/app/http/middleware/status.go new file mode 100644 index 0000000000..ef67d4656b --- /dev/null +++ b/app/http/middleware/status.go @@ -0,0 +1,42 @@ +package middleware + +import ( + "github.com/goravel/framework/contracts/http" + + "panel/internal" +) + +// Status 检查程序状态 +func Status() http.Middleware { + return func(ctx http.Context) { + switch internal.Status { + case internal.StatusUpgrade: + ctx.Request().AbortWithStatusJson(http.StatusOK, http.Json{ + "code": 503, + "message": "面板升级中,请稍后", + }) + return + case internal.StatusMaintain: + ctx.Request().AbortWithStatusJson(http.StatusOK, http.Json{ + "code": 503, + "message": "面板正在运行维护,请稍后", + }) + return + case internal.StatusClosed: + ctx.Request().AbortWithStatusJson(http.StatusOK, http.Json{ + "code": 403, + "message": "面板已关闭", + }) + return + case internal.StatusFailed: + ctx.Request().AbortWithStatusJson(http.StatusOK, http.Json{ + "code": 500, + "message": "面板运行出错,请检查排除或联系支持", + }) + return + default: + ctx.Request().Next() + return + } + } +} diff --git a/internal/status.go b/internal/status.go new file mode 100644 index 0000000000..e28cdf0f57 --- /dev/null +++ b/internal/status.go @@ -0,0 +1,12 @@ +package internal + +// 定义面板状态常量 +const ( + StatusNormal = iota + StatusMaintain + StatusClosed + StatusUpgrade + StatusFailed +) + +var Status = StatusNormal diff --git a/pkg/tools/tools.go b/pkg/tools/tools.go index 1c77df33aa..2f0ed64f9c 100644 --- a/pkg/tools/tools.go +++ b/pkg/tools/tools.go @@ -10,6 +10,7 @@ import ( "time" "github.com/gookit/color" + "github.com/goravel/framework/support/carbon" "github.com/goravel/framework/support/env" "github.com/imroc/req/v3" "github.com/shirou/gopsutil/cpu" @@ -403,11 +404,16 @@ func UpdatePanel(panelInfo PanelInfo) error { color.Greenln("前置检查...") if Exists("/tmp/panel-storage.zip") || Exists("/tmp/panel.conf.bak") { - color.Redln("检测到/tmp存在临时文件,可能是上次更新失败导致的,请谨慎排除后重试") - return errors.New("检测到/tmp存在临时文件,可能是上次更新失败导致的,请谨慎排除后重试") + color.Redln("检测到 /tmp 存在临时文件,可能是上次更新失败导致的,请谨慎排除后重试") + return errors.New("检测到 /tmp 存在临时文件,可能是上次更新失败导致的,请谨慎排除后重试") } color.Greenln("备份面板数据...") + // 备份面板 + if err := Archive([]string{"/www/panel"}, "/www/backup/panel/panel-"+carbon.Now().ToShortDateTimeString()+".zip"); err != nil { + color.Redln("备份面板失败") + return err + } if _, err := Exec("cd /www/panel/storage && zip -r /tmp/panel-storage.zip *"); err != nil { color.Redln("备份面板数据失败") return err