diff --git a/app/console/commands/monitoring.go b/app/console/commands/monitoring.go index 415743dd..26996c18 100644 --- a/app/console/commands/monitoring.go +++ b/app/console/commands/monitoring.go @@ -8,8 +8,10 @@ import ( "github.com/goravel/framework/contracts/console/command" "github.com/goravel/framework/facades" "github.com/goravel/framework/support/carbon" + "github.com/spf13/cast" "panel/app/models" + "panel/app/services" "panel/pkg/tools" ) @@ -36,10 +38,8 @@ func (receiver *Monitoring) Extend() command.Extend { // Handle Execute the console command. func (receiver *Monitoring) Handle(ctx console.Context) error { var setting models.Setting - if err := facades.Orm().Query().Where("key", "monitor").First(&setting); err != nil { - return nil - } - if setting.Value == "0" || len(setting.Value) == 0 { + monitor := services.NewSettingImpl().Get(models.SettingKeyMonitor) + if !cast.ToBool(monitor) { return nil } diff --git a/app/console/kernel.go b/app/console/kernel.go index 57f20621..a0ebc441 100644 --- a/app/console/kernel.go +++ b/app/console/kernel.go @@ -3,6 +3,7 @@ package console import ( "github.com/goravel/framework/contracts/console" "github.com/goravel/framework/contracts/schedule" + "github.com/goravel/framework/facades" "panel/app/console/commands" ) @@ -11,7 +12,9 @@ type Kernel struct { } func (kernel *Kernel) Schedule() []schedule.Event { - return []schedule.Event{} + return []schedule.Event{ + facades.Schedule().Command("panel:monitoring").EveryMinute().SkipIfStillRunning(), + } } func (kernel *Kernel) Commands() []console.Command { diff --git a/app/http/controllers/helpers.go b/app/http/controllers/helpers.go index d25806fa..9bdee8eb 100644 --- a/app/http/controllers/helpers.go +++ b/app/http/controllers/helpers.go @@ -1,6 +1,13 @@ package controllers -import "github.com/goravel/framework/contracts/http" +import ( + "sync" + + "github.com/goravel/framework/contracts/http" + "github.com/goravel/framework/facades" + + "panel/app/services" +) func Success(ctx http.Context, data any) { ctx.Response().Success().Json(http.Json{ @@ -16,3 +23,51 @@ func Error(ctx http.Context, code int, message any) { "message": message, }) } + +// Check 检查插件是否可用 +func Check(ctx http.Context, slug string) bool { + plugin := services.NewPluginImpl().GetBySlug(slug) + installedPlugin := services.NewPluginImpl().GetInstalledBySlug(slug) + installedPlugins, err := services.NewPluginImpl().AllInstalled() + if err != nil { + facades.Log().Error("[面板][插件] 获取已安装插件失败") + Error(ctx, http.StatusInternalServerError, "系统内部错误") + return false + } + + if installedPlugin.Version != plugin.Version || installedPlugin.Slug != plugin.Slug { + Error(ctx, http.StatusForbidden, "插件 "+slug+" 需要更新至 "+plugin.Version+" 版本") + return false + } + + var lock sync.RWMutex + pluginsMap := make(map[string]bool) + + for _, p := range installedPlugins { + lock.Lock() + pluginsMap[p.Slug] = true + lock.Unlock() + } + + for _, require := range plugin.Requires { + lock.RLock() + _, requireFound := pluginsMap[require] + lock.RUnlock() + if !requireFound { + Error(ctx, http.StatusForbidden, "插件 "+slug+" 需要依赖 "+require+" 插件") + return false + } + } + + for _, exclude := range plugin.Excludes { + lock.RLock() + _, excludeFound := pluginsMap[exclude] + lock.RUnlock() + if excludeFound { + Error(ctx, http.StatusForbidden, "插件 "+slug+" 不兼容 "+exclude+" 插件") + return false + } + } + + return true +} diff --git a/app/http/controllers/info_controller.go b/app/http/controllers/info_controller.go index 9f69b713..6b967fb2 100644 --- a/app/http/controllers/info_controller.go +++ b/app/http/controllers/info_controller.go @@ -2,6 +2,7 @@ package controllers import ( "fmt" + "strings" "github.com/goravel/framework/contracts/http" "github.com/goravel/framework/facades" @@ -19,16 +20,16 @@ type MenuItem struct { } type InfoController struct { - // Dependent services + plugin services.Plugin } func NewInfoController() *InfoController { return &InfoController{ - // Inject services + plugin: services.NewPluginImpl(), } } -func (r *InfoController) Name(ctx http.Context) { +func (c *InfoController) Name(ctx http.Context) { var setting models.Setting err := facades.Orm().Query().Where("key", "name").First(&setting) if err != nil { @@ -42,7 +43,7 @@ func (r *InfoController) Name(ctx http.Context) { }) } -func (r *InfoController) Menu(ctx http.Context) { +func (c *InfoController) Menu(ctx http.Context) { Success(ctx, []MenuItem{ {Name: "home", Title: "主页", Icon: "layui-icon-home", Jump: "/"}, {Name: "website", Title: "网站管理", Icon: "layui-icon-website", Jump: "website/list"}, @@ -55,7 +56,7 @@ func (r *InfoController) Menu(ctx http.Context) { }) } -func (r *InfoController) HomePlugins(ctx http.Context) { +func (c *InfoController) HomePlugins(ctx http.Context) { var plugins []models.Plugin err := facades.Orm().Query().Where("show", 1).Find(&plugins) if err != nil { @@ -80,11 +81,11 @@ func (r *InfoController) HomePlugins(ctx http.Context) { Success(ctx, pluginsJson) } -func (r *InfoController) NowMonitor(ctx http.Context) { +func (c *InfoController) NowMonitor(ctx http.Context) { Success(ctx, tools.GetMonitoringInfo()) } -func (r *InfoController) SystemInfo(ctx http.Context) { +func (c *InfoController) SystemInfo(ctx http.Context) { monitorInfo := tools.GetMonitoringInfo() Success(ctx, http.Json{ @@ -94,7 +95,7 @@ func (r *InfoController) SystemInfo(ctx http.Context) { }) } -func (r *InfoController) InstalledDbAndPhp(ctx http.Context) { +func (c *InfoController) InstalledDbAndPhp(ctx http.Context) { var php []models.Plugin err := facades.Orm().Query().Where("slug like ?", "php%").Find(&php) if err != nil { @@ -116,14 +117,24 @@ func (r *InfoController) InstalledDbAndPhp(ctx http.Context) { postgresqlInstalled = false } + type data struct { + Slug string `json:"slug"` + Name string `json:"name"` + } + var phpData []data + phpData = append(phpData, data{Slug: "0", Name: "不使用"}) + for _, p := range php { + phpData = append(phpData, data{Slug: strings.ReplaceAll(p.Slug, "php", ""), Name: c.plugin.GetBySlug(p.Slug).Name}) + } + Success(ctx, http.Json{ - "php": php, + "php": phpData, "mysql": mysqlInstalled, "postgresql": postgresqlInstalled, }) } -func (r *InfoController) CheckUpdate(ctx http.Context) { +func (c *InfoController) CheckUpdate(ctx http.Context) { version := facades.Config().GetString("panel.version") remote, err := tools.GetLatestPanelVersion() if err != nil { @@ -151,7 +162,7 @@ func (r *InfoController) CheckUpdate(ctx http.Context) { }) } -func (r *InfoController) Update(ctx http.Context) { +func (c *InfoController) Update(ctx http.Context) { proxy := ctx.Request().InputBool("proxy") err := tools.UpdatePanel(proxy) if err != nil { diff --git a/app/http/controllers/monitor_controller.go b/app/http/controllers/monitor_controller.go index 47b36d56..0382b72c 100644 --- a/app/http/controllers/monitor_controller.go +++ b/app/http/controllers/monitor_controller.go @@ -1,12 +1,13 @@ package controllers import ( + "fmt" + "github.com/goravel/framework/contracts/http" "github.com/goravel/framework/facades" "github.com/goravel/framework/support/carbon" "github.com/spf13/cast" "panel/app/models" - "panel/app/services" ) @@ -46,9 +47,20 @@ func (r *MonitorController) SaveDays(ctx http.Context) { Success(ctx, nil) } +// SwitchAndDays 监控开关和监控天数 +func (r *MonitorController) SwitchAndDays(ctx http.Context) { + monitor := r.setting.Get(models.SettingKeyMonitor) + monitorDays := r.setting.Get(models.SettingKeyMonitorDays) + + Success(ctx, http.Json{ + "switch": cast.ToBool(monitor), + "days": cast.ToInt(monitorDays), + }) +} + // Clear 清空监控数据 func (r *MonitorController) Clear(ctx http.Context) { - _, err := facades.Orm().Query().Delete(&models.Monitor{}) + _, err := facades.Orm().Query().Where("1 = 1").Delete(&models.Monitor{}) if err != nil { facades.Log().Error("[面板][MonitorController] 清空监控数据失败 ", err) Error(ctx, http.StatusInternalServerError, "系统内部错误") @@ -60,18 +72,97 @@ func (r *MonitorController) Clear(ctx http.Context) { // List 监控数据列表 func (r *MonitorController) List(ctx http.Context) { - start := ctx.Request().Input("start") - end := ctx.Request().Input("end") - startTime := carbon.Parse(start) - endTime := carbon.Parse(end) + start := ctx.Request().InputInt64("start") + end := ctx.Request().InputInt64("end") + startTime := carbon.FromTimestampMilli(start) + endTime := carbon.FromTimestampMilli(end) var monitors []models.Monitor - err := facades.Orm().Query().Where("created_at", ">=", startTime).Where("created_at", "<=", endTime).Get(&monitors) + err := facades.Orm().Query().Where("created_at >= ?", startTime.ToDateTimeString()).Where("created_at <= ?", endTime.ToDateTimeString()).Get(&monitors) if err != nil { facades.Log().Error("[面板][MonitorController] 查询监控数据失败 ", err) Error(ctx, http.StatusInternalServerError, "系统内部错误") return } - Success(ctx, monitors) + if len(monitors) == 0 { + Error(ctx, http.StatusNotFound, "监控数据为空") + return + } + + type load struct { + Load1 []float64 `json:"load1"` + Load5 []float64 `json:"load5"` + Load15 []float64 `json:"load15"` + } + type cpu struct { + Percent []string `json:"percent"` + } + type mem struct { + Total string `json:"total"` + Available []string `json:"available"` + Used []string `json:"used"` + } + type swap struct { + Total string `json:"total"` + Used []string `json:"used"` + Free []string `json:"free"` + } + type network struct { + Sent []string `json:"sent"` + Recv []string `json:"recv"` + Tx []string `json:"tx"` + Rx []string `json:"rx"` + } + type monitorData struct { + Times []string `json:"times"` + Load load `json:"load"` + Cpu cpu `json:"cpu"` + Mem mem `json:"mem"` + Swap swap `json:"swap"` + Net network `json:"net"` + } + + var data monitorData + var bytesSent uint64 + var bytesRecv uint64 + var bytesSent2 uint64 + var bytesRecv2 uint64 + for _, net := range monitors[0].Info.Net { + bytesSent += net.BytesSent + bytesRecv += net.BytesRecv + } + for i, monitor := range monitors { + // 跳过第一条数据,因为第一条数据的流量为 0 + if i == 0 { + // MB + data.Mem.Total = fmt.Sprintf("%.2f", float64(monitor.Info.Mem.Total)/1024/1024) + data.Swap.Total = fmt.Sprintf("%.2f", float64(monitor.Info.Swap.Total)/1024/1024) + continue + } + for _, net := range monitor.Info.Net { + bytesSent2 += net.BytesSent + bytesRecv2 += net.BytesRecv + } + data.Times = append(data.Times, monitor.CreatedAt.ToDateTimeString()) + data.Load.Load1 = append(data.Load.Load1, monitor.Info.Load.Load1) + data.Load.Load5 = append(data.Load.Load5, monitor.Info.Load.Load5) + data.Load.Load15 = append(data.Load.Load15, monitor.Info.Load.Load15) + data.Cpu.Percent = append(data.Cpu.Percent, fmt.Sprintf("%.2f", monitor.Info.Percent[0])) + data.Mem.Available = append(data.Mem.Available, fmt.Sprintf("%.2f", float64(monitor.Info.Mem.Available)/1024/1024)) + data.Mem.Used = append(data.Mem.Used, fmt.Sprintf("%.2f", float64(monitor.Info.Mem.Used)/1024/1024)) + data.Swap.Used = append(data.Swap.Used, fmt.Sprintf("%.2f", float64(monitor.Info.Swap.Used)/1024/1024)) + data.Swap.Free = append(data.Swap.Free, fmt.Sprintf("%.2f", float64(monitor.Info.Swap.Free)/1024/1024)) + data.Net.Sent = append(data.Net.Sent, fmt.Sprintf("%.2f", float64(bytesSent2/1024))) + data.Net.Recv = append(data.Net.Recv, fmt.Sprintf("%.2f", float64(bytesRecv2/1024))) + + // 监控频率为 1 分钟,所以这里除以 60 即可得到每秒的流量 + data.Net.Tx = append(data.Net.Tx, fmt.Sprintf("%.2f", float64(bytesSent2-bytesSent)/60/1024)) + data.Net.Rx = append(data.Net.Rx, fmt.Sprintf("%.2f", float64(bytesRecv2-bytesRecv)/60/1024)) + + bytesSent = bytesSent2 + bytesRecv = bytesRecv2 + } + + Success(ctx, data) } diff --git a/app/http/controllers/plugin_controller.go b/app/http/controllers/plugin_controller.go index 82ddfa3a..153401d4 100644 --- a/app/http/controllers/plugin_controller.go +++ b/app/http/controllers/plugin_controller.go @@ -80,35 +80,53 @@ func (r *PluginController) List(ctx http.Context) { // Install 安装插件 func (r *PluginController) Install(ctx http.Context) { slug := ctx.Request().Input("slug") - plugins := r.plugin.All() - - var plugin services.PanelPlugin - check := false - for _, item := range plugins { - if item.Slug == slug { - check = true - plugin = item - break - } - } - if !check { - Error(ctx, http.StatusBadRequest, "插件不存在") - return - } - - var installedPlugin models.Plugin - if err := facades.Orm().Query().Where("slug", slug).First(&installedPlugin); err != nil { + plugin := r.plugin.GetBySlug(slug) + installedPlugin := r.plugin.GetInstalledBySlug(slug) + installedPlugins, err := r.plugin.AllInstalled() + if err != nil { + facades.Log().Error("[面板][PluginController] 获取已安装插件失败") Error(ctx, http.StatusInternalServerError, "系统内部错误") return } + if installedPlugin.ID != 0 { Error(ctx, http.StatusBadRequest, "插件已安装") + return + } + + var lock sync.RWMutex + pluginsMap := make(map[string]bool) + + for _, p := range installedPlugins { + lock.Lock() + pluginsMap[p.Slug] = true + lock.Unlock() + } + + for _, require := range plugin.Requires { + lock.RLock() + _, requireFound := pluginsMap[require] + lock.RUnlock() + if !requireFound { + Error(ctx, http.StatusForbidden, "插件 "+slug+" 需要依赖 "+require+" 插件") + return + } + } + + for _, exclude := range plugin.Excludes { + lock.RLock() + _, excludeFound := pluginsMap[exclude] + lock.RUnlock() + if excludeFound { + Error(ctx, http.StatusForbidden, "插件 "+slug+" 不兼容 "+exclude+" 插件") + return + } } var task models.Task task.Name = "安装插件 " + plugin.Name task.Status = models.TaskStatusWaiting - task.Shell = "bash /www/panel/scripts/" + plugin.Slug + "/install.sh >> /tmp/" + plugin.Slug + ".log 2>&1" + task.Shell = plugin.Install + " >> /tmp/" + plugin.Slug + ".log 2>&1" task.Log = "/tmp/" + plugin.Slug + ".log" if err := facades.Orm().Query().Create(&task); err != nil { facades.Log().Error("[面板][PluginController] 创建任务失败: " + err.Error()) @@ -123,35 +141,53 @@ func (r *PluginController) Install(ctx http.Context) { // Uninstall 卸载插件 func (r *PluginController) Uninstall(ctx http.Context) { slug := ctx.Request().Input("slug") - plugins := r.plugin.All() - - var plugin services.PanelPlugin - check := false - for _, item := range plugins { - if item.Slug == slug { - check = true - plugin = item - break - } - } - if !check { - Error(ctx, http.StatusBadRequest, "插件不存在") - return - } - - var installedPlugin models.Plugin - if err := facades.Orm().Query().Where("slug", slug).First(&installedPlugin); err != nil { + plugin := r.plugin.GetBySlug(slug) + installedPlugin := r.plugin.GetInstalledBySlug(slug) + installedPlugins, err := r.plugin.AllInstalled() + if err != nil { + facades.Log().Error("[面板][PluginController] 获取已安装插件失败") Error(ctx, http.StatusInternalServerError, "系统内部错误") return } + if installedPlugin.ID == 0 { Error(ctx, http.StatusBadRequest, "插件未安装") + return + } + + var lock sync.RWMutex + pluginsMap := make(map[string]bool) + + for _, p := range installedPlugins { + lock.Lock() + pluginsMap[p.Slug] = true + lock.Unlock() + } + + for _, require := range plugin.Requires { + lock.RLock() + _, requireFound := pluginsMap[require] + lock.RUnlock() + if !requireFound { + Error(ctx, http.StatusForbidden, "插件 "+slug+" 需要依赖 "+require+" 插件") + return + } + } + + for _, exclude := range plugin.Excludes { + lock.RLock() + _, excludeFound := pluginsMap[exclude] + lock.RUnlock() + if excludeFound { + Error(ctx, http.StatusForbidden, "插件 "+slug+" 不兼容 "+exclude+" 插件") + return + } } var task models.Task task.Name = "卸载插件 " + plugin.Name task.Status = models.TaskStatusWaiting - task.Shell = "bash /www/panel/scripts/" + plugin.Slug + "/uninstall.sh >> /tmp/" + plugin.Slug + ".log 2>&1" + task.Shell = plugin.Uninstall + " >> /tmp/" + plugin.Slug + ".log 2>&1" task.Log = "/tmp/" + plugin.Slug + ".log" if err := facades.Orm().Query().Create(&task); err != nil { facades.Log().Error("[面板][PluginController] 创建任务失败: " + err.Error()) @@ -166,35 +202,53 @@ func (r *PluginController) Uninstall(ctx http.Context) { // Update 更新插件 func (r *PluginController) Update(ctx http.Context) { slug := ctx.Request().Input("slug") - plugins := r.plugin.All() - - var plugin services.PanelPlugin - check := false - for _, item := range plugins { - if item.Slug == slug { - check = true - plugin = item - break - } - } - if !check { - Error(ctx, http.StatusBadRequest, "插件不存在") - return - } - - var installedPlugin models.Plugin - if err := facades.Orm().Query().Where("slug", slug).First(&installedPlugin); err != nil { + plugin := r.plugin.GetBySlug(slug) + installedPlugin := r.plugin.GetInstalledBySlug(slug) + installedPlugins, err := r.plugin.AllInstalled() + if err != nil { + facades.Log().Error("[面板][PluginController] 获取已安装插件失败") Error(ctx, http.StatusInternalServerError, "系统内部错误") return } + if installedPlugin.ID == 0 { Error(ctx, http.StatusBadRequest, "插件未安装") + return + } + + var lock sync.RWMutex + pluginsMap := make(map[string]bool) + + for _, p := range installedPlugins { + lock.Lock() + pluginsMap[p.Slug] = true + lock.Unlock() + } + + for _, require := range plugin.Requires { + lock.RLock() + _, requireFound := pluginsMap[require] + lock.RUnlock() + if !requireFound { + Error(ctx, http.StatusForbidden, "插件 "+slug+" 需要依赖 "+require+" 插件") + return + } + } + + for _, exclude := range plugin.Excludes { + lock.RLock() + _, excludeFound := pluginsMap[exclude] + lock.RUnlock() + if excludeFound { + Error(ctx, http.StatusForbidden, "插件 "+slug+" 不兼容 "+exclude+" 插件") + return + } } var task models.Task task.Name = "更新插件 " + plugin.Name task.Status = models.TaskStatusWaiting - task.Shell = "bash /www/panel/scripts/" + plugin.Slug + "/update.sh >> /tmp/" + plugin.Slug + ".log 2>&1" + task.Shell = plugin.Update + " >> /tmp/" + plugin.Slug + ".log 2>&1" task.Log = "/tmp/" + plugin.Slug + ".log" if err := facades.Orm().Query().Create(&task); err != nil { facades.Log().Error("[面板][PluginController] 创建任务失败: " + err.Error()) diff --git a/app/http/controllers/plugins/mysql57/mysql57_controller.go b/app/http/controllers/plugins/mysql57/mysql57_controller.go new file mode 100644 index 00000000..a42ee2cc --- /dev/null +++ b/app/http/controllers/plugins/mysql57/mysql57_controller.go @@ -0,0 +1,748 @@ +package mysql57 + +import ( + "fmt" + "os" + "path/filepath" + "regexp" + "strings" + + "github.com/goravel/framework/contracts/http" + "github.com/goravel/framework/facades" + "github.com/goravel/framework/support/carbon" + "github.com/spf13/cast" + "golang.org/x/exp/slices" + + "panel/app/http/controllers" + "panel/app/models" + "panel/app/services" + "panel/pkg/tools" +) + +type Mysql57Controller struct { + setting services.Setting +} + +func NewMysql57Controller() *Mysql57Controller { + return &Mysql57Controller{ + setting: services.NewSettingImpl(), + } +} + +// Status 获取运行状态 +func (c *Mysql57Controller) Status(ctx http.Context) { + if !controllers.Check(ctx, "mysql57") { + return + } + + status := tools.ExecShell("systemctl status mysql | grep Active | grep -v grep | awk '{print $2}'") + if len(status) == 0 { + controllers.Error(ctx, http.StatusInternalServerError, "获取MySQL状态失败") + return + } + + if status == "active" { + controllers.Success(ctx, true) + } else { + controllers.Success(ctx, false) + } +} + +// Reload 重载配置 +func (c *Mysql57Controller) Reload(ctx http.Context) { + if !controllers.Check(ctx, "mysql57") { + return + } + + tools.ExecShell("systemctl reload mysql") + status := tools.ExecShell("systemctl status mysql | grep Active | grep -v grep | awk '{print $2}'") + if len(status) == 0 { + controllers.Error(ctx, http.StatusInternalServerError, "获取MySQL状态失败") + return + } + + if status == "active" { + controllers.Success(ctx, true) + } else { + controllers.Success(ctx, false) + } +} + +// Restart 重启服务 +func (c *Mysql57Controller) Restart(ctx http.Context) { + if !controllers.Check(ctx, "mysql57") { + return + } + + tools.ExecShell("systemctl restart mysql") + status := tools.ExecShell("systemctl status mysql | grep Active | grep -v grep | awk '{print $2}'") + if len(status) == 0 { + controllers.Error(ctx, http.StatusInternalServerError, "获取MySQL状态失败") + return + } + + if status == "active" { + controllers.Success(ctx, true) + } else { + controllers.Success(ctx, false) + } +} + +// Start 启动服务 +func (c *Mysql57Controller) Start(ctx http.Context) { + if !controllers.Check(ctx, "mysql57") { + return + } + + tools.ExecShell("systemctl start mysql") + status := tools.ExecShell("systemctl status mysql | grep Active | grep -v grep | awk '{print $2}'") + if len(status) == 0 { + controllers.Error(ctx, http.StatusInternalServerError, "获取MySQL状态失败") + return + } + + if status == "active" { + controllers.Success(ctx, true) + } else { + controllers.Success(ctx, false) + } +} + +// Stop 停止服务 +func (c *Mysql57Controller) Stop(ctx http.Context) { + if !controllers.Check(ctx, "mysql57") { + return + } + + tools.ExecShell("systemctl stop mysql") + status := tools.ExecShell("systemctl status mysql | grep Active | grep -v grep | awk '{print $2}'") + if len(status) == 0 { + controllers.Error(ctx, http.StatusInternalServerError, "获取MySQL状态失败") + return + } + + if status != "active" { + controllers.Success(ctx, true) + } else { + controllers.Success(ctx, false) + } +} + +// GetConfig 获取配置 +func (c *Mysql57Controller) GetConfig(ctx http.Context) { + if !controllers.Check(ctx, "mysql57") { + return + } + + // 获取配置 + config := tools.ReadFile("mysql57") + if len(config) == 0 { + controllers.Error(ctx, http.StatusInternalServerError, "获取MySQL配置失败") + return + } + + controllers.Success(ctx, config) +} + +// SaveConfig 保存配置 +func (c *Mysql57Controller) SaveConfig(ctx http.Context) { + if !controllers.Check(ctx, "mysql57") { + return + } + + config := ctx.Request().Input("config") + if len(config) == 0 { + controllers.Error(ctx, http.StatusBadRequest, "配置不能为空") + return + } + + if !tools.WriteFile("mysql57", config, 0644) { + controllers.Error(ctx, http.StatusInternalServerError, "写入MySQL配置失败") + return + } + + controllers.Success(ctx, "保存MySQL配置成功") +} + +// Load 获取负载 +func (c *Mysql57Controller) Load(ctx http.Context) { + if !controllers.Check(ctx, "mysql57") { + return + } + + rootPassword := c.setting.Get(models.SettingKeyMysqlRootPassword) + if len(rootPassword) == 0 { + controllers.Error(ctx, http.StatusBadRequest, "MySQL root密码为空") + return + } + + status := tools.ExecShell("systemctl status mysqld | grep Active | grep -v grep | awk '{print $2}'") + if status != "active" { + controllers.Error(ctx, http.StatusInternalServerError, "MySQL 已停止运行") + return + } + + raw := tools.ExecShell("mysqladmin -uroot -p" + rootPassword + " extended-status 2>&1") + if strings.Contains(raw, "Access denied for user") { + controllers.Error(ctx, http.StatusBadRequest, "MySQL root密码错误") + return + } + if !strings.Contains(raw, "Uptime") { + controllers.Error(ctx, http.StatusInternalServerError, "获取MySQL负载失败") + return + } + + data := make(map[int]map[string]string) + expressions := []struct { + regex string + name string + }{ + {`Uptime\s+\|\s+(\d+)\s+\|`, "总查询次数"}, + {`Queries\s+\|\s+(\d+)\s+\|`, "总连接次数"}, + {`Connections\s+\|\s+(\d+)\s+\|`, "每秒事务"}, + {`Com_commit\s+\|\s+(\d+)\s+\|`, "每秒回滚"}, + {`Com_rollback\s+\|\s+(\d+)\s+\|`, "发送"}, + {`Bytes_sent\s+\|\s+(\d+)\s+\|`, "接收"}, + {`Bytes_received\s+\|\s+(\d+)\s+\|`, "活动连接数"}, + {`Threads_connected\s+\|\s+(\d+)\s+\|`, "峰值连接数"}, + {`Max_used_connections\s+\|\s+(\d+)\s+\|`, "索引命中率"}, + {`Key_read_requests\s+\|\s+(\d+)\s+\|`, "Innodb索引命中率"}, + {`Innodb_buffer_pool_reads\s+\|\s+(\d+)\s+\|`, "创建临时表到磁盘"}, + {`Created_tmp_disk_tables\s+\|\s+(\d+)\s+\|`, "已打开的表"}, + {`Open_tables\s+\|\s+(\d+)\s+\|`, "没有使用索引的量"}, + {`Select_full_join\s+\|\s+(\d+)\s+\|`, "没有索引的JOIN量"}, + {`Select_full_range_join\s+\|\s+(\d+)\s+\|`, "没有索引的子查询量"}, + {`Select_range_check\s+\|\s+(\d+)\s+\|`, "排序后的合并次数"}, + {`Sort_merge_passes\s+\|\s+(\d+)\s+\|`, "锁表次数"}, + {`Table_locks_waited\s+\|\s+(\d+)\s+\|`, ""}, + } + + for i, expression := range expressions { + re := regexp.MustCompile(expression.regex) + matches := re.FindStringSubmatch(raw) + if len(matches) > 1 { + data[i] = make(map[string]string) + data[i] = map[string]string{"name": expression.name, "value": matches[1]} + + if expression.name == "发送" || expression.name == "接收" { + data[i]["value"] = tools.FormatBytes(cast.ToFloat64(matches[1])) + } + } + } + + // 索引命中率 + readRequests := cast.ToFloat64(data[9]["value"]) + reads := cast.ToFloat64(data[10]["value"]) + data[9]["value"] = fmt.Sprintf("%.2f%%", readRequests/(reads+readRequests)*100) + // Innodb索引命中率 + bufferPoolReads := cast.ToFloat64(data[11]["value"]) + bufferPoolReadRequests := cast.ToFloat64(data[12]["value"]) + data[10]["value"] = fmt.Sprintf("%.2f%%", bufferPoolReadRequests/(bufferPoolReads+bufferPoolReadRequests)*100) + + controllers.Success(ctx, data) +} + +// ErrorLog 获取错误日志 +func (c *Mysql57Controller) ErrorLog(ctx http.Context) { + if !controllers.Check(ctx, "mysql57") { + return + } + + log := tools.ExecShell("tail -n 100 /www/server/mysql/mysql-error.log") + controllers.Success(ctx, log) +} + +// ClearErrorLog 清空错误日志 +func (c *Mysql57Controller) ClearErrorLog(ctx http.Context) { + if !controllers.Check(ctx, "mysql57") { + return + } + + tools.ExecShell("echo '' > /www/server/mysql/mysql-error.log") + controllers.Success(ctx, "清空错误日志成功") +} + +// SlowLog 获取慢查询日志 +func (c *Mysql57Controller) SlowLog(ctx http.Context) { + if !controllers.Check(ctx, "mysql57") { + return + } + + log := tools.ExecShell("tail -n 100 /www/server/mysql/mysql-slow.log") + controllers.Success(ctx, log) +} + +// ClearSlowLog 清空慢查询日志 +func (c *Mysql57Controller) ClearSlowLog(ctx http.Context) { + if !controllers.Check(ctx, "mysql57") { + return + } + + tools.ExecShell("echo '' > /www/server/mysql/mysql-slow.log") + controllers.Success(ctx, "清空慢查询日志成功") +} + +// GetRootPassword 获取root密码 +func (c *Mysql57Controller) GetRootPassword(ctx http.Context) { + if !controllers.Check(ctx, "mysql57") { + return + } + + rootPassword := c.setting.Get(models.SettingKeyMysqlRootPassword) + if len(rootPassword) == 0 { + controllers.Error(ctx, http.StatusBadRequest, "MySQL root密码为空") + return + } + + controllers.Success(ctx, rootPassword) +} + +// SetRootPassword 设置root密码 +func (c *Mysql57Controller) SetRootPassword(ctx http.Context) { + if !controllers.Check(ctx, "mysql57") { + return + } + + status := tools.ExecShell("systemctl status mysql | grep Active | grep -v grep | awk '{print $2}'") + if len(status) == 0 { + controllers.Error(ctx, http.StatusInternalServerError, "获取MySQL状态失败") + return + } + if status != "active" { + controllers.Error(ctx, http.StatusInternalServerError, "MySQL 未运行") + return + } + + rootPassword := ctx.Request().Input(models.SettingKeyMysqlRootPassword) + if len(rootPassword) == 0 { + controllers.Error(ctx, http.StatusBadRequest, "MySQL root密码不能为空") + return + } + + oldRootPassword := c.setting.Get(models.SettingKeyMysqlRootPassword) + if oldRootPassword != rootPassword { + tools.ExecShell("mysql -uroot -p" + oldRootPassword + " -e \"ALTER USER 'root'@'localhost' IDENTIFIED BY '" + rootPassword + "';\"") + tools.ExecShell("mysql -uroot -p" + oldRootPassword + " -e \"FLUSH PRIVILEGES;\"") + err := c.setting.Set(models.SettingKeyMysqlRootPassword, rootPassword) + if err != nil { + tools.ExecShell("mysql -uroot -p" + rootPassword + " -e \"ALTER USER 'root'@'localhost' IDENTIFIED BY '" + oldRootPassword + "';\"") + tools.ExecShell("mysql -uroot -p" + rootPassword + " -e \"FLUSH PRIVILEGES;\"") + controllers.Error(ctx, http.StatusInternalServerError, "设置root密码失败") + return + } + } + + controllers.Success(ctx, "设置root密码成功") +} + +// DatabaseList 获取数据库列表 +func (c *Mysql57Controller) DatabaseList(ctx http.Context) { + if !controllers.Check(ctx, "mysql57") { + return + } + + out := tools.ExecShell("mysql -uroot -p" + c.setting.Get(models.SettingKeyMysqlRootPassword) + " -e \"show databases;\"") + databases := strings.Split(out, "\n") + + databases = databases[1 : len(databases)-1] + systemDatabases := []string{"information_schema", "mysql", "performance_schema", "sys"} + + var userDatabases []string + for _, db := range databases { + if !slices.Contains(systemDatabases, db) { + userDatabases = append(userDatabases, db) + } + } + + type Database struct { + Name string + } + + var dbStructs []Database + for _, db := range userDatabases { + dbStructs = append(dbStructs, Database{Name: db}) + } + + controllers.Success(ctx, dbStructs) +} + +// AddDatabase 添加数据库 +func (c *Mysql57Controller) AddDatabase(ctx http.Context) { + if !controllers.Check(ctx, "mysql57") { + return + } + + validator, err := ctx.Request().Validate(map[string]string{ + "database": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$", + "user": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$", + "password": "required|min_len:8|max_len:255|regex:^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*(_|[^\\w])).+$", + }) + if err != nil { + controllers.Error(ctx, http.StatusBadRequest, err.Error()) + return + } + if validator.Fails() { + controllers.Error(ctx, http.StatusBadRequest, validator.Errors().All()) + return + } + + rootPassword := c.setting.Get(models.SettingKeyMysqlRootPassword) + database := ctx.Request().Input("database") + user := ctx.Request().Input("user") + password := ctx.Request().Input("password") + + tools.ExecShell("mysql -uroot -p" + rootPassword + " -e \"CREATE DATABASE IF NOT EXISTS " + database + " DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_general_ci;\"") + tools.ExecShell("mysql -uroot -p" + rootPassword + " -e \"CREATE USER '" + user + "'@'localhost' IDENTIFIED BY '" + password + "';\"") + tools.ExecShell("mysql -uroot -p" + rootPassword + " -e \"GRANT ALL PRIVILEGES ON " + database + ".* TO '" + user + "'@'localhost';\"") + tools.ExecShell("mysql -uroot -p" + rootPassword + " -e \"FLUSH PRIVILEGES;\"") + + controllers.Success(ctx, "添加数据库成功") +} + +// DeleteDatabase 删除数据库 +func (c *Mysql57Controller) DeleteDatabase(ctx http.Context) { + if !controllers.Check(ctx, "mysql57") { + return + } + + validator, err := ctx.Request().Validate(map[string]string{ + "database": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$|not_in:information_schema,mysql,performance_schema,sys", + }) + if err != nil { + controllers.Error(ctx, http.StatusBadRequest, err.Error()) + return + } + if validator.Fails() { + controllers.Error(ctx, http.StatusBadRequest, validator.Errors().All()) + return + } + + rootPassword := c.setting.Get(models.SettingKeyMysqlRootPassword) + database := ctx.Request().Input("database") + tools.ExecShell("mysql -uroot -p" + rootPassword + " -e \"DROP DATABASE IF EXISTS " + database + ";\"") + + controllers.Success(ctx, "删除数据库成功") +} + +// BackupList 获取备份列表 +func (c *Mysql57Controller) BackupList(ctx http.Context) { + backupPath := c.setting.Get(models.SettingKeyBackupPath) + "/mysql" + + if !tools.Exists(backupPath) { + tools.Mkdir(backupPath, 0644) + } + + files, err := os.ReadDir(backupPath) + if err != nil { + controllers.Error(ctx, http.StatusInternalServerError, "获取备份列表失败") + return + } + + var backupFiles []map[string]string + for _, file := range files { + if file.IsDir() { + continue + } + + info, err := file.Info() + if err != nil { + continue + } + + backupFiles = append(backupFiles, map[string]string{ + "file": file.Name(), + "size": tools.FormatBytes(float64(info.Size())), + }) + } + + controllers.Success(ctx, backupFiles) +} + +// CreateBackup 创建备份 +func (c *Mysql57Controller) CreateBackup(ctx http.Context) { + if !controllers.Check(ctx, "mysql57") { + return + } + + validator, err := ctx.Request().Validate(map[string]string{ + "database": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$|not_in:information_schema,mysql,performance_schema,sys", + }) + if err != nil { + controllers.Error(ctx, http.StatusBadRequest, err.Error()) + return + } + if validator.Fails() { + controllers.Error(ctx, http.StatusBadRequest, validator.Errors().All()) + return + } + + backupPath := c.setting.Get(models.SettingKeyBackupPath) + "/mysql" + rootPassword := c.setting.Get(models.SettingKeyMysqlRootPassword) + database := ctx.Request().Input("database") + backupFile := backupPath + "/" + database + "_" + carbon.Now().ToShortDateTimeString() + ".sql" + if !tools.Exists(backupPath) { + tools.Mkdir(backupPath, 0644) + } + err = os.Setenv("MYSQL_PWD", rootPassword) + if err != nil { + facades.Log().Error("[MySQL57] 设置环境变量 MYSQL_PWD 失败:" + err.Error()) + controllers.Error(ctx, http.StatusInternalServerError, "备份失败") + return + } + + tools.ExecShell("mysqldump -uroot " + database + " > " + backupFile) + tools.ExecShell("zip -c " + backupFile + ".zip " + backupFile) + tools.RemoveFile(backupFile) + _ = os.Unsetenv("MYSQL_PWD") + + controllers.Success(ctx, "备份成功") +} + +// DeleteBackup 删除备份 +func (c *Mysql57Controller) DeleteBackup(ctx http.Context) { + if !controllers.Check(ctx, "mysql57") { + return + } + + validator, err := ctx.Request().Validate(map[string]string{ + "file": "required|min_len:1|max_len:255", + }) + if err != nil { + controllers.Error(ctx, http.StatusBadRequest, err.Error()) + return + } + if validator.Fails() { + controllers.Error(ctx, http.StatusBadRequest, validator.Errors().All()) + return + } + + backupPath := c.setting.Get(models.SettingKeyBackupPath) + "/mysql" + file := ctx.Request().Input("file") + tools.RemoveFile(backupPath + "/" + file) + + controllers.Success(ctx, "删除备份成功") +} + +// RestoreBackup 还原备份 +func (c *Mysql57Controller) RestoreBackup(ctx http.Context) { + if !controllers.Check(ctx, "mysql57") { + return + } + + validator, err := ctx.Request().Validate(map[string]string{ + "file": "required|min_len:1|max_len:255", + "database": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$|not_in:information_schema,mysql,performance_schema,sys", + }) + if err != nil { + controllers.Error(ctx, http.StatusBadRequest, err.Error()) + return + } + if validator.Fails() { + controllers.Error(ctx, http.StatusBadRequest, validator.Errors().All()) + return + } + + backupPath := c.setting.Get(models.SettingKeyBackupPath) + "/mysql" + rootPassword := c.setting.Get(models.SettingKeyMysqlRootPassword) + file := ctx.Request().Input("file") + backupFile := backupPath + "/" + file + if !tools.Exists(backupFile) { + controllers.Error(ctx, http.StatusBadRequest, "备份文件不存在") + return + } + + err = os.Setenv("MYSQL_PWD", rootPassword) + if err != nil { + facades.Log().Error("[MYSQL57] 设置环境变量 MYSQL_PWD 失败:" + err.Error()) + controllers.Error(ctx, http.StatusInternalServerError, "还原失败") + return + } + + // 获取文件拓展名 + ext := filepath.Ext(file) + switch ext { + case ".zip": + tools.ExecShell("unzip -o " + backupFile + " -d " + backupPath) + backupFile = strings.TrimSuffix(backupFile, ext) + case ".gz": + if strings.HasSuffix(file, ".tar.gz") { + // 解压.tar.gz文件 + tools.ExecShell("tar -zxvf " + backupFile + " -C " + backupPath) + backupFile = strings.TrimSuffix(backupFile, ".tar.gz") + } else { + // 解压.gz文件 + tools.ExecShell("gzip -d " + backupFile) + backupFile = strings.TrimSuffix(backupFile, ext) + } + case ".bz2": + tools.ExecShell("bzip2 -d " + backupFile) + backupFile = strings.TrimSuffix(backupFile, ext) + case ".tar": + tools.ExecShell("tar -xvf " + backupFile + " -C " + backupPath) + backupFile = strings.TrimSuffix(backupFile, ext) + case ".rar": + tools.ExecShell("unrar x " + backupFile + " " + backupPath) + backupFile = strings.TrimSuffix(backupFile, ext) + } + + if !tools.Exists(backupFile) { + controllers.Error(ctx, http.StatusBadRequest, "自动解压备份文件失败,请手动解压") + return + } + + tools.ExecShell("mysql -uroot " + ctx.Request().Input("database") + " < " + backupFile) + _ = os.Unsetenv("MYSQL_PWD") + + controllers.Success(ctx, "还原成功") +} + +// UserList 用户列表 +func (c *Mysql57Controller) UserList(ctx http.Context) { + if !controllers.Check(ctx, "mysql57") { + return + } + + type User struct { + Username string `json:"username"` + Host string `json:"host"` + Privileges string `json:"privileges"` + } + + rootPassword := c.setting.Get(models.SettingKeyMysqlRootPassword) + out := tools.ExecShell("mysql -uroot -p" + rootPassword + " -e 'select user,host from mysql.user'") + rawUsers := strings.Split(out, "\n") + users := make([]User, 0) + for _, rawUser := range rawUsers { + user := strings.Split(rawUser, "\t") + if user[0] == "root" || user[0] == "mysql.sys" || user[0] == "mysql.infoschema" || user[0] == "mysql.session" { + continue + } + + out := tools.ExecShell("mysql -uroot -p" + rootPassword + " -e 'show grants for " + user[0] + "@" + user[1] + "'") + rawPrivileges := strings.Split(out, "\n") + privileges := make([]string, 0) + for _, rawPrivilege := range rawPrivileges { + if rawPrivilege == "" { + continue + } + privilege := rawPrivilege[6:strings.Index(rawPrivilege, " TO")] + privileges = append(privileges, privilege) + } + users = append(users, User{Username: user[0], Host: user[1], Privileges: strings.Join(privileges, " | ")}) + } + + controllers.Success(ctx, users) +} + +// AddUser 添加用户 +func (c *Mysql57Controller) AddUser(ctx http.Context) { + if !controllers.Check(ctx, "mysql57") { + return + } + + validator, err := ctx.Request().Validate(map[string]string{ + "database": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$", + "user": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$", + "password": "required|min_len:8|max_len:255|regex:^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*(_|[^\\w])).+$", + }) + if err != nil { + controllers.Error(ctx, http.StatusBadRequest, err.Error()) + return + } + if validator.Fails() { + controllers.Error(ctx, http.StatusBadRequest, validator.Errors().All()) + return + } + + rootPassword := c.setting.Get(models.SettingKeyMysqlRootPassword) + user := ctx.Request().Input("user") + password := ctx.Request().Input("password") + database := ctx.Request().Input("database") + tools.ExecShell("mysql -uroot -p" + rootPassword + " -e \"CREATE USER '" + user + "'@'localhost' IDENTIFIED BY '" + password + ";'\"") + tools.ExecShell("mysql -uroot -p" + rootPassword + " -e \"GRANT ALL PRIVILEGES ON " + database + ".* TO '" + user + "'@'localhost';\"") + tools.ExecShell("mysql -uroot -p" + rootPassword + " -e \"FLUSH PRIVILEGES;\"") + + controllers.Success(ctx, "添加成功") +} + +// DeleteUser 删除用户 +func (c *Mysql57Controller) DeleteUser(ctx http.Context) { + if !controllers.Check(ctx, "mysql57") { + return + } + + validator, err := ctx.Request().Validate(map[string]string{ + "user": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$", + }) + if err != nil { + controllers.Error(ctx, http.StatusBadRequest, err.Error()) + return + } + if validator.Fails() { + controllers.Error(ctx, http.StatusBadRequest, validator.Errors().All()) + return + } + + rootPassword := c.setting.Get(models.SettingKeyMysqlRootPassword) + user := ctx.Request().Input("user") + tools.ExecShell("mysql -uroot -p" + rootPassword + " -e \"DROP USER '" + user + "'@'localhost';\"") + + controllers.Success(ctx, "删除成功") +} + +// SetUserPassword 设置用户密码 +func (c *Mysql57Controller) SetUserPassword(ctx http.Context) { + if !controllers.Check(ctx, "mysql57") { + return + } + + validator, err := ctx.Request().Validate(map[string]string{ + "user": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$", + "password": "required|min_len:8|max_len:255|regex:^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*(_|[^\\w])).+$", + }) + if err != nil { + controllers.Error(ctx, http.StatusBadRequest, err.Error()) + return + } + if validator.Fails() { + controllers.Error(ctx, http.StatusBadRequest, validator.Errors().All()) + return + } + + rootPassword := c.setting.Get(models.SettingKeyMysqlRootPassword) + user := ctx.Request().Input("user") + password := ctx.Request().Input("password") + tools.ExecShell("mysql -uroot -p" + rootPassword + " -e \"ALTER USER '" + user + "'@'localhost' IDENTIFIED BY '" + password + "';\"") + tools.ExecShell("mysql -uroot -p" + rootPassword + " -e \"FLUSH PRIVILEGES;\"") + + controllers.Success(ctx, "修改成功") +} + +// SetUserPrivileges 设置用户权限 +func (c *Mysql57Controller) SetUserPrivileges(ctx http.Context) { + if !controllers.Check(ctx, "mysql57") { + return + } + + validator, err := ctx.Request().Validate(map[string]string{ + "user": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$", + "database": "required|min_len:1|max_len:255", + }) + if err != nil { + controllers.Error(ctx, http.StatusBadRequest, err.Error()) + return + } + if validator.Fails() { + controllers.Error(ctx, http.StatusBadRequest, validator.Errors().All()) + return + } + + rootPassword := c.setting.Get(models.SettingKeyMysqlRootPassword) + user := ctx.Request().Input("user") + database := ctx.Request().Input("database") + tools.ExecShell("mysql -uroot -p" + rootPassword + " -e \"REVOKE ALL PRIVILEGES ON *.* FROM '" + user + "'@'localhost';\"") + tools.ExecShell("mysql -uroot -p" + rootPassword + " -e \"GRANT ALL PRIVILEGES ON " + database + ".* TO '" + user + "'@'localhost';\"") + tools.ExecShell("mysql -uroot -p" + rootPassword + " -e \"FLUSH PRIVILEGES;\"") + + controllers.Success(ctx, "修改成功") +} diff --git a/app/http/controllers/plugins/mysql80/mysql80_controller.go b/app/http/controllers/plugins/mysql80/mysql80_controller.go index 18f1e3b6..fd25a964 100644 --- a/app/http/controllers/plugins/mysql80/mysql80_controller.go +++ b/app/http/controllers/plugins/mysql80/mysql80_controller.go @@ -12,10 +12,9 @@ import ( "github.com/goravel/framework/support/carbon" "github.com/spf13/cast" "golang.org/x/exp/slices" - "panel/app/models" "panel/app/http/controllers" - "panel/app/http/controllers/plugins" + "panel/app/models" "panel/app/services" "panel/pkg/tools" ) @@ -32,12 +31,11 @@ func NewMysql80Controller() *Mysql80Controller { // Status 获取运行状态 func (c *Mysql80Controller) Status(ctx http.Context) { - if !plugins.Check(ctx, "mysql80") { + if !controllers.Check(ctx, "mysql80") { return } - out := tools.ExecShell("systemctl status mysql | grep Active | grep -v grep | awk '{print $2}'") - status := strings.TrimSpace(out) + status := tools.ExecShell("systemctl status mysql | grep Active | grep -v grep | awk '{print $2}'") if len(status) == 0 { controllers.Error(ctx, http.StatusInternalServerError, "获取MySQL状态失败") return @@ -52,13 +50,12 @@ func (c *Mysql80Controller) Status(ctx http.Context) { // Reload 重载配置 func (c *Mysql80Controller) Reload(ctx http.Context) { - if !plugins.Check(ctx, "mysql80") { + if !controllers.Check(ctx, "mysql80") { return } tools.ExecShell("systemctl reload mysql") - out := tools.ExecShell("systemctl status mysql | grep Active | grep -v grep | awk '{print $2}'") - status := strings.TrimSpace(out) + status := tools.ExecShell("systemctl status mysql | grep Active | grep -v grep | awk '{print $2}'") if len(status) == 0 { controllers.Error(ctx, http.StatusInternalServerError, "获取MySQL状态失败") return @@ -73,13 +70,12 @@ func (c *Mysql80Controller) Reload(ctx http.Context) { // Restart 重启服务 func (c *Mysql80Controller) Restart(ctx http.Context) { - if !plugins.Check(ctx, "mysql80") { + if !controllers.Check(ctx, "mysql80") { return } tools.ExecShell("systemctl restart mysql") - out := tools.ExecShell("systemctl status mysql | grep Active | grep -v grep | awk '{print $2}'") - status := strings.TrimSpace(out) + status := tools.ExecShell("systemctl status mysql | grep Active | grep -v grep | awk '{print $2}'") if len(status) == 0 { controllers.Error(ctx, http.StatusInternalServerError, "获取MySQL状态失败") return @@ -94,13 +90,12 @@ func (c *Mysql80Controller) Restart(ctx http.Context) { // Start 启动服务 func (c *Mysql80Controller) Start(ctx http.Context) { - if !plugins.Check(ctx, "mysql80") { + if !controllers.Check(ctx, "mysql80") { return } tools.ExecShell("systemctl start mysql") - out := tools.ExecShell("systemctl status mysql | grep Active | grep -v grep | awk '{print $2}'") - status := strings.TrimSpace(out) + status := tools.ExecShell("systemctl status mysql | grep Active | grep -v grep | awk '{print $2}'") if len(status) == 0 { controllers.Error(ctx, http.StatusInternalServerError, "获取MySQL状态失败") return @@ -115,13 +110,12 @@ func (c *Mysql80Controller) Start(ctx http.Context) { // Stop 停止服务 func (c *Mysql80Controller) Stop(ctx http.Context) { - if !plugins.Check(ctx, "mysql80") { + if !controllers.Check(ctx, "mysql80") { return } tools.ExecShell("systemctl stop mysql") - out := tools.ExecShell("systemctl status mysql | grep Active | grep -v grep | awk '{print $2}'") - status := strings.TrimSpace(out) + status := tools.ExecShell("systemctl status mysql | grep Active | grep -v grep | awk '{print $2}'") if len(status) == 0 { controllers.Error(ctx, http.StatusInternalServerError, "获取MySQL状态失败") return @@ -136,7 +130,7 @@ func (c *Mysql80Controller) Stop(ctx http.Context) { // GetConfig 获取配置 func (c *Mysql80Controller) GetConfig(ctx http.Context) { - if !plugins.Check(ctx, "mysql80") { + if !controllers.Check(ctx, "mysql80") { return } @@ -152,7 +146,7 @@ func (c *Mysql80Controller) GetConfig(ctx http.Context) { // SaveConfig 保存配置 func (c *Mysql80Controller) SaveConfig(ctx http.Context) { - if !plugins.Check(ctx, "mysql80") { + if !controllers.Check(ctx, "mysql80") { return } @@ -172,7 +166,7 @@ func (c *Mysql80Controller) SaveConfig(ctx http.Context) { // Load 获取负载 func (c *Mysql80Controller) Load(ctx http.Context) { - if !plugins.Check(ctx, "mysql80") { + if !controllers.Check(ctx, "mysql80") { return } @@ -183,7 +177,7 @@ func (c *Mysql80Controller) Load(ctx http.Context) { } status := tools.ExecShell("systemctl status mysqld | grep Active | grep -v grep | awk '{print $2}'") - if strings.TrimSpace(status) != "active" { + if status != "active" { controllers.Error(ctx, http.StatusInternalServerError, "MySQL 已停止运行") return } @@ -250,7 +244,7 @@ func (c *Mysql80Controller) Load(ctx http.Context) { // ErrorLog 获取错误日志 func (c *Mysql80Controller) ErrorLog(ctx http.Context) { - if !plugins.Check(ctx, "mysql80") { + if !controllers.Check(ctx, "mysql80") { return } @@ -260,7 +254,7 @@ func (c *Mysql80Controller) ErrorLog(ctx http.Context) { // ClearErrorLog 清空错误日志 func (c *Mysql80Controller) ClearErrorLog(ctx http.Context) { - if !plugins.Check(ctx, "mysql80") { + if !controllers.Check(ctx, "mysql80") { return } @@ -270,7 +264,7 @@ func (c *Mysql80Controller) ClearErrorLog(ctx http.Context) { // SlowLog 获取慢查询日志 func (c *Mysql80Controller) SlowLog(ctx http.Context) { - if !plugins.Check(ctx, "mysql80") { + if !controllers.Check(ctx, "mysql80") { return } @@ -280,7 +274,7 @@ func (c *Mysql80Controller) SlowLog(ctx http.Context) { // ClearSlowLog 清空慢查询日志 func (c *Mysql80Controller) ClearSlowLog(ctx http.Context) { - if !plugins.Check(ctx, "mysql80") { + if !controllers.Check(ctx, "mysql80") { return } @@ -290,7 +284,7 @@ func (c *Mysql80Controller) ClearSlowLog(ctx http.Context) { // GetRootPassword 获取root密码 func (c *Mysql80Controller) GetRootPassword(ctx http.Context) { - if !plugins.Check(ctx, "mysql80") { + if !controllers.Check(ctx, "mysql80") { return } @@ -305,12 +299,11 @@ func (c *Mysql80Controller) GetRootPassword(ctx http.Context) { // SetRootPassword 设置root密码 func (c *Mysql80Controller) SetRootPassword(ctx http.Context) { - if !plugins.Check(ctx, "mysql80") { + if !controllers.Check(ctx, "mysql80") { return } - out := tools.ExecShell("systemctl status mysql | grep Active | grep -v grep | awk '{print $2}'") - status := strings.TrimSpace(out) + status := tools.ExecShell("systemctl status mysql | grep Active | grep -v grep | awk '{print $2}'") if len(status) == 0 { controllers.Error(ctx, http.StatusInternalServerError, "获取MySQL状态失败") return @@ -344,7 +337,7 @@ func (c *Mysql80Controller) SetRootPassword(ctx http.Context) { // DatabaseList 获取数据库列表 func (c *Mysql80Controller) DatabaseList(ctx http.Context) { - if !plugins.Check(ctx, "mysql80") { + if !controllers.Check(ctx, "mysql80") { return } @@ -375,7 +368,7 @@ func (c *Mysql80Controller) DatabaseList(ctx http.Context) { // AddDatabase 添加数据库 func (c *Mysql80Controller) AddDatabase(ctx http.Context) { - if !plugins.Check(ctx, "mysql80") { + if !controllers.Check(ctx, "mysql80") { return } @@ -408,7 +401,7 @@ func (c *Mysql80Controller) AddDatabase(ctx http.Context) { // DeleteDatabase 删除数据库 func (c *Mysql80Controller) DeleteDatabase(ctx http.Context) { - if !plugins.Check(ctx, "mysql80") { + if !controllers.Check(ctx, "mysql80") { return } @@ -467,7 +460,7 @@ func (c *Mysql80Controller) BackupList(ctx http.Context) { // CreateBackup 创建备份 func (c *Mysql80Controller) CreateBackup(ctx http.Context) { - if !plugins.Check(ctx, "mysql80") { + if !controllers.Check(ctx, "mysql80") { return } @@ -507,7 +500,7 @@ func (c *Mysql80Controller) CreateBackup(ctx http.Context) { // DeleteBackup 删除备份 func (c *Mysql80Controller) DeleteBackup(ctx http.Context) { - if !plugins.Check(ctx, "mysql80") { + if !controllers.Check(ctx, "mysql80") { return } @@ -532,7 +525,7 @@ func (c *Mysql80Controller) DeleteBackup(ctx http.Context) { // RestoreBackup 还原备份 func (c *Mysql80Controller) RestoreBackup(ctx http.Context) { - if !plugins.Check(ctx, "mysql80") { + if !controllers.Check(ctx, "mysql80") { return } @@ -605,7 +598,7 @@ func (c *Mysql80Controller) RestoreBackup(ctx http.Context) { // UserList 用户列表 func (c *Mysql80Controller) UserList(ctx http.Context) { - if !plugins.Check(ctx, "mysql80") { + if !controllers.Check(ctx, "mysql80") { return } @@ -643,7 +636,7 @@ func (c *Mysql80Controller) UserList(ctx http.Context) { // AddUser 添加用户 func (c *Mysql80Controller) AddUser(ctx http.Context) { - if !plugins.Check(ctx, "mysql80") { + if !controllers.Check(ctx, "mysql80") { return } @@ -674,7 +667,7 @@ func (c *Mysql80Controller) AddUser(ctx http.Context) { // DeleteUser 删除用户 func (c *Mysql80Controller) DeleteUser(ctx http.Context) { - if !plugins.Check(ctx, "mysql80") { + if !controllers.Check(ctx, "mysql80") { return } @@ -699,7 +692,7 @@ func (c *Mysql80Controller) DeleteUser(ctx http.Context) { // SetUserPassword 设置用户密码 func (c *Mysql80Controller) SetUserPassword(ctx http.Context) { - if !plugins.Check(ctx, "mysql80") { + if !controllers.Check(ctx, "mysql80") { return } @@ -727,7 +720,7 @@ func (c *Mysql80Controller) SetUserPassword(ctx http.Context) { // SetUserPrivileges 设置用户权限 func (c *Mysql80Controller) SetUserPrivileges(ctx http.Context) { - if !plugins.Check(ctx, "mysql80") { + if !controllers.Check(ctx, "mysql80") { return } diff --git a/app/http/controllers/plugins/openresty/openresty_controller.go b/app/http/controllers/plugins/openresty/openresty_controller.go index aded1244..27b0f27b 100644 --- a/app/http/controllers/plugins/openresty/openresty_controller.go +++ b/app/http/controllers/plugins/openresty/openresty_controller.go @@ -2,7 +2,6 @@ package openresty import ( "regexp" - "strings" "time" "github.com/goravel/framework/contracts/http" @@ -11,7 +10,6 @@ import ( "github.com/spf13/cast" "panel/app/http/controllers" - "panel/app/http/controllers/plugins" "panel/pkg/tools" ) @@ -27,12 +25,11 @@ func NewOpenrestyController() *OpenRestyController { // Status 获取运行状态 func (r *OpenRestyController) Status(ctx http.Context) { - if !plugins.Check(ctx, "openresty") { + if !controllers.Check(ctx, "openresty") { return } - out := tools.ExecShell("systemctl status openresty | grep Active | grep -v grep | awk '{print $2}'") - status := strings.TrimSpace(out) + status := tools.ExecShell("systemctl status openresty | grep Active | grep -v grep | awk '{print $2}'") if len(status) == 0 { controllers.Error(ctx, http.StatusInternalServerError, "获取OpenResty状态失败") return @@ -47,13 +44,12 @@ func (r *OpenRestyController) Status(ctx http.Context) { // Reload 重载配置 func (r *OpenRestyController) Reload(ctx http.Context) { - if !plugins.Check(ctx, "openresty") { + if !controllers.Check(ctx, "openresty") { return } tools.ExecShell("systemctl reload openresty") - out := tools.ExecShell("systemctl status openresty | grep Active | grep -v grep | awk '{print $2}'") - status := strings.TrimSpace(out) + status := tools.ExecShell("systemctl status openresty | grep Active | grep -v grep | awk '{print $2}'") if len(status) == 0 { controllers.Error(ctx, http.StatusInternalServerError, "获取OpenResty状态失败") return @@ -62,19 +58,18 @@ func (r *OpenRestyController) Reload(ctx http.Context) { if status == "active" { controllers.Success(ctx, "重载OpenResty成功") } else { - controllers.Error(ctx, 1, "重载OpenResty失败: "+string(out)) + controllers.Error(ctx, 1, "重载OpenResty失败: "+status) } } // Start 启动OpenResty func (r *OpenRestyController) Start(ctx http.Context) { - if !plugins.Check(ctx, "openresty") { + if !controllers.Check(ctx, "openresty") { return } tools.ExecShell("systemctl start openresty") - out := tools.ExecShell("systemctl status openresty | grep Active | grep -v grep | awk '{print $2}'") - status := strings.TrimSpace(out) + status := tools.ExecShell("systemctl status openresty | grep Active | grep -v grep | awk '{print $2}'") if len(status) == 0 { controllers.Error(ctx, http.StatusInternalServerError, "获取OpenResty状态失败") return @@ -83,19 +78,18 @@ func (r *OpenRestyController) Start(ctx http.Context) { if status == "active" { controllers.Success(ctx, "启动OpenResty成功") } else { - controllers.Error(ctx, 1, "启动OpenResty失败: "+string(out)) + controllers.Error(ctx, 1, "启动OpenResty失败: "+status) } } // Stop 停止OpenResty func (r *OpenRestyController) Stop(ctx http.Context) { - if !plugins.Check(ctx, "openresty") { + if !controllers.Check(ctx, "openresty") { return } tools.ExecShell("systemctl stop openresty") - out := tools.ExecShell("systemctl status openresty | grep Active | grep -v grep | awk '{print $2}'") - status := strings.TrimSpace(out) + status := tools.ExecShell("systemctl status openresty | grep Active | grep -v grep | awk '{print $2}'") if len(status) == 0 { controllers.Error(ctx, http.StatusInternalServerError, "获取OpenResty状态失败") return @@ -104,19 +98,18 @@ func (r *OpenRestyController) Stop(ctx http.Context) { if status != "active" { controllers.Success(ctx, "停止OpenResty成功") } else { - controllers.Error(ctx, 1, "停止OpenResty失败: "+string(out)) + controllers.Error(ctx, 1, "停止OpenResty失败: "+status) } } // Restart 重启OpenResty func (r *OpenRestyController) Restart(ctx http.Context) { - if !plugins.Check(ctx, "openresty") { + if !controllers.Check(ctx, "openresty") { return } tools.ExecShell("systemctl restart openresty") - out := tools.ExecShell("systemctl status openresty | grep Active | grep -v grep | awk '{print $2}'") - status := strings.TrimSpace(out) + status := tools.ExecShell("systemctl status openresty | grep Active | grep -v grep | awk '{print $2}'") if len(status) == 0 { controllers.Error(ctx, http.StatusInternalServerError, "获取OpenResty状态失败") return @@ -125,13 +118,13 @@ func (r *OpenRestyController) Restart(ctx http.Context) { if status == "active" { controllers.Success(ctx, "重启OpenResty成功") } else { - controllers.Error(ctx, 1, "重启OpenResty失败: "+string(out)) + controllers.Error(ctx, 1, "重启OpenResty失败: "+status) } } // GetConfig 获取配置 func (r *OpenRestyController) GetConfig(ctx http.Context) { - if !plugins.Check(ctx, "openresty") { + if !controllers.Check(ctx, "openresty") { return } @@ -146,7 +139,7 @@ func (r *OpenRestyController) GetConfig(ctx http.Context) { // SaveConfig 保存配置 func (r *OpenRestyController) SaveConfig(ctx http.Context) { - if !plugins.Check(ctx, "openresty") { + if !controllers.Check(ctx, "openresty") { return } @@ -166,7 +159,7 @@ func (r *OpenRestyController) SaveConfig(ctx http.Context) { // ErrorLog 获取错误日志 func (r *OpenRestyController) ErrorLog(ctx http.Context) { - if !plugins.Check(ctx, "openresty") { + if !controllers.Check(ctx, "openresty") { return } @@ -181,7 +174,7 @@ func (r *OpenRestyController) ErrorLog(ctx http.Context) { // ClearErrorLog 清空错误日志 func (r *OpenRestyController) ClearErrorLog(ctx http.Context) { - if !plugins.Check(ctx, "openresty") { + if !controllers.Check(ctx, "openresty") { return } @@ -191,7 +184,7 @@ func (r *OpenRestyController) ClearErrorLog(ctx http.Context) { // Load 获取负载 func (r *OpenRestyController) Load(ctx http.Context) { - if !plugins.Check(ctx, "openresty") { + if !controllers.Check(ctx, "openresty") { return } @@ -210,15 +203,14 @@ func (r *OpenRestyController) Load(ctx http.Context) { } var data []nginxStatus - out := tools.ExecShell("ps aux | grep nginx | grep 'worker process' | wc -l") - workers := strings.TrimSpace(out) + workers := tools.ExecShell("ps aux | grep nginx | grep 'worker process' | wc -l") data = append(data, nginxStatus{ Name: "工作进程", Value: workers, }) - out = tools.ExecShell("ps aux | grep nginx | grep 'worker process' | awk '{memsum+=$6};END {print memsum}'") - mem := tools.FormatBytes(cast.ToFloat64(strings.TrimSpace(out))) + out := tools.ExecShell("ps aux | grep nginx | grep 'worker process' | awk '{memsum+=$6};END {print memsum}'") + mem := tools.FormatBytes(cast.ToFloat64(out)) data = append(data, nginxStatus{ Name: "内存占用", Value: mem, diff --git a/app/http/controllers/plugins/php74/php74_controller.go b/app/http/controllers/plugins/php74/php74_controller.go index c954da1a..c9aea7d5 100644 --- a/app/http/controllers/plugins/php74/php74_controller.go +++ b/app/http/controllers/plugins/php74/php74_controller.go @@ -11,7 +11,6 @@ import ( "github.com/imroc/req/v3" "panel/app/http/controllers" - "panel/app/http/controllers/plugins" "panel/app/models" "panel/app/services" "panel/pkg/tools" @@ -32,7 +31,7 @@ func NewPhp74Controller() *Php74Controller { } func (c *Php74Controller) Status(ctx http.Context) { - if !plugins.Check(ctx, "php"+c.version) { + if !controllers.Check(ctx, "php"+c.version) { return } @@ -51,7 +50,7 @@ func (c *Php74Controller) Status(ctx http.Context) { } func (c *Php74Controller) Reload(ctx http.Context) { - if !plugins.Check(ctx, "php"+c.version) { + if !controllers.Check(ctx, "php"+c.version) { return } @@ -71,7 +70,7 @@ func (c *Php74Controller) Reload(ctx http.Context) { } func (c *Php74Controller) Start(ctx http.Context) { - if !plugins.Check(ctx, "php"+c.version) { + if !controllers.Check(ctx, "php"+c.version) { return } @@ -91,7 +90,7 @@ func (c *Php74Controller) Start(ctx http.Context) { } func (c *Php74Controller) Stop(ctx http.Context) { - if !plugins.Check(ctx, "php"+c.version) { + if !controllers.Check(ctx, "php"+c.version) { return } @@ -111,7 +110,7 @@ func (c *Php74Controller) Stop(ctx http.Context) { } func (c *Php74Controller) Restart(ctx http.Context) { - if !plugins.Check(ctx, "php"+c.version) { + if !controllers.Check(ctx, "php"+c.version) { return } @@ -131,7 +130,7 @@ func (c *Php74Controller) Restart(ctx http.Context) { } func (c *Php74Controller) GetConfig(ctx http.Context) { - if !plugins.Check(ctx, "php"+c.version) { + if !controllers.Check(ctx, "php"+c.version) { return } @@ -140,7 +139,7 @@ func (c *Php74Controller) GetConfig(ctx http.Context) { } func (c *Php74Controller) SaveConfig(ctx http.Context) { - if !plugins.Check(ctx, "php"+c.version) { + if !controllers.Check(ctx, "php"+c.version) { return } @@ -150,7 +149,7 @@ func (c *Php74Controller) SaveConfig(ctx http.Context) { } func (c *Php74Controller) Load(ctx http.Context) { - if !plugins.Check(ctx, "php"+c.version) { + if !controllers.Check(ctx, "php"+c.version) { return } @@ -186,7 +185,7 @@ func (c *Php74Controller) Load(ctx http.Context) { } func (c *Php74Controller) ErrorLog(ctx http.Context) { - if !plugins.Check(ctx, "php"+c.version) { + if !controllers.Check(ctx, "php"+c.version) { return } @@ -195,7 +194,7 @@ func (c *Php74Controller) ErrorLog(ctx http.Context) { } func (c *Php74Controller) SlowLog(ctx http.Context) { - if !plugins.Check(ctx, "php"+c.version) { + if !controllers.Check(ctx, "php"+c.version) { return } @@ -204,7 +203,7 @@ func (c *Php74Controller) SlowLog(ctx http.Context) { } func (c *Php74Controller) ClearErrorLog(ctx http.Context) { - if !plugins.Check(ctx, "php"+c.version) { + if !controllers.Check(ctx, "php"+c.version) { return } @@ -213,7 +212,7 @@ func (c *Php74Controller) ClearErrorLog(ctx http.Context) { } func (c *Php74Controller) ClearSlowLog(ctx http.Context) { - if !plugins.Check(ctx, "php"+c.version) { + if !controllers.Check(ctx, "php"+c.version) { return } @@ -229,7 +228,7 @@ type Extension struct { } func (c *Php74Controller) GetExtensionList(ctx http.Context) { - if !plugins.Check(ctx, "php"+c.version) { + if !controllers.Check(ctx, "php"+c.version) { return } @@ -238,7 +237,7 @@ func (c *Php74Controller) GetExtensionList(ctx http.Context) { } func (c *Php74Controller) InstallExtension(ctx http.Context) { - if !plugins.Check(ctx, "php"+c.version) { + if !controllers.Check(ctx, "php"+c.version) { return } @@ -259,7 +258,7 @@ func (c *Php74Controller) InstallExtension(ctx http.Context) { var task models.Task task.Name = "安装PHP-" + c.version + "扩展-" + item.Name task.Status = models.TaskStatusWaiting - task.Shell = "bash scripts/php_extensions/" + item.Slug + ".sh install " + c.version + ">> /tmp/" + item.Slug + ".log 2>&1" + task.Shell = "bash /www/panel/scripts/php_extensions/" + item.Slug + ".sh install " + c.version + ">> /tmp/" + item.Slug + ".log 2>&1" task.Log = "/tmp/" + item.Slug + ".log" if err := facades.Orm().Query().Create(&task); err != nil { facades.Log().Error("[PHP-" + c.version + "] 创建安装拓展任务失败:" + err.Error()) @@ -278,7 +277,7 @@ func (c *Php74Controller) InstallExtension(ctx http.Context) { } func (c *Php74Controller) UninstallExtension(ctx http.Context) { - if !plugins.Check(ctx, "php"+c.version) { + if !controllers.Check(ctx, "php"+c.version) { return } @@ -299,7 +298,7 @@ func (c *Php74Controller) UninstallExtension(ctx http.Context) { var task models.Task task.Name = "卸载PHP-" + c.version + "扩展-" + item.Name task.Status = models.TaskStatusWaiting - task.Shell = "bash scripts/php_extensions/" + item.Slug + ".sh uninstall " + c.version + ">> /tmp/" + item.Slug + ".log 2>&1" + task.Shell = "bash /www/panel/scripts/php_extensions/" + item.Slug + ".sh uninstall " + c.version + ">> /tmp/" + item.Slug + ".log 2>&1" task.Log = "/tmp/" + item.Slug + ".log" if err := facades.Orm().Query().Create(&task); err != nil { facades.Log().Error("[PHP-" + c.version + "] 创建卸载拓展任务失败:" + err.Error()) diff --git a/app/http/controllers/plugins/php80/php80_controller.go b/app/http/controllers/plugins/php80/php80_controller.go new file mode 100644 index 00000000..eb270c8b --- /dev/null +++ b/app/http/controllers/plugins/php80/php80_controller.go @@ -0,0 +1,372 @@ +package php80 + +import ( + "fmt" + "regexp" + "strings" + "time" + + "github.com/goravel/framework/contracts/http" + "github.com/goravel/framework/facades" + "github.com/imroc/req/v3" + + "panel/app/http/controllers" + "panel/app/models" + "panel/app/services" + "panel/pkg/tools" +) + +type Php80Controller struct { + setting services.Setting + task services.Task + version string +} + +func NewPhp80Controller() *Php80Controller { + return &Php80Controller{ + setting: services.NewSettingImpl(), + task: services.NewTaskImpl(), + version: "80", + } +} + +func (c *Php80Controller) Status(ctx http.Context) { + if !controllers.Check(ctx, "php"+c.version) { + return + } + + out := tools.ExecShell("systemctl status php-fpm-" + c.version + " | grep Active | grep -v grep | awk '{print $2}'") + status := strings.TrimSpace(out) + if len(status) == 0 { + controllers.Error(ctx, http.StatusInternalServerError, "获取PHP-"+c.version+"运行状态失败") + return + } + + if status == "active" { + controllers.Success(ctx, true) + } else { + controllers.Success(ctx, false) + } +} + +func (c *Php80Controller) Reload(ctx http.Context) { + if !controllers.Check(ctx, "php"+c.version) { + return + } + + tools.ExecShell("systemctl reload php-fpm-" + c.version) + out := tools.ExecShell("systemctl status php-fpm-" + c.version + " | grep Active | grep -v grep | awk '{print $2}'") + status := strings.TrimSpace(out) + if len(status) == 0 { + controllers.Error(ctx, http.StatusInternalServerError, "获取PHP-"+c.version+"运行状态失败") + return + } + + if status == "active" { + controllers.Success(ctx, true) + } else { + controllers.Success(ctx, false) + } +} + +func (c *Php80Controller) Start(ctx http.Context) { + if !controllers.Check(ctx, "php"+c.version) { + return + } + + tools.ExecShell("systemctl start php-fpm-" + c.version) + out := tools.ExecShell("systemctl status php-fpm-" + c.version + " | grep Active | grep -v grep | awk '{print $2}'") + status := strings.TrimSpace(out) + if len(status) == 0 { + controllers.Error(ctx, http.StatusInternalServerError, "获取PHP-"+c.version+"运行状态失败") + return + } + + if status == "active" { + controllers.Success(ctx, true) + } else { + controllers.Success(ctx, false) + } +} + +func (c *Php80Controller) Stop(ctx http.Context) { + if !controllers.Check(ctx, "php"+c.version) { + return + } + + tools.ExecShell("systemctl stop php-fpm-" + c.version) + out := tools.ExecShell("systemctl status php-fpm-" + c.version + " | grep Active | grep -v grep | awk '{print $2}'") + status := strings.TrimSpace(out) + if len(status) == 0 { + controllers.Error(ctx, http.StatusInternalServerError, "获取PHP-"+c.version+"运行状态失败") + return + } + + if status != "active" { + controllers.Success(ctx, true) + } else { + controllers.Success(ctx, false) + } +} + +func (c *Php80Controller) Restart(ctx http.Context) { + if !controllers.Check(ctx, "php"+c.version) { + return + } + + tools.ExecShell("systemctl restart php-fpm-" + c.version) + out := tools.ExecShell("systemctl status php-fpm-" + c.version + " | grep Active | grep -v grep | awk '{print $2}'") + status := strings.TrimSpace(out) + if len(status) == 0 { + controllers.Error(ctx, http.StatusInternalServerError, "获取PHP-"+c.version+"运行状态失败") + return + } + + if status == "active" { + controllers.Success(ctx, true) + } else { + controllers.Success(ctx, false) + } +} + +func (c *Php80Controller) GetConfig(ctx http.Context) { + if !controllers.Check(ctx, "php"+c.version) { + return + } + + config := tools.ReadFile("/www/server/php/" + c.version + "/etc/php.ini") + controllers.Success(ctx, config) +} + +func (c *Php80Controller) SaveConfig(ctx http.Context) { + if !controllers.Check(ctx, "php"+c.version) { + return + } + + config := ctx.Request().Input("config") + tools.WriteFile("/www/server/php/"+c.version+"/etc/php.ini", config, 0644) + c.Reload(ctx) +} + +func (c *Php80Controller) Load(ctx http.Context) { + if !controllers.Check(ctx, "php"+c.version) { + return + } + + client := req.C().SetTimeout(10 * time.Second) + resp, err := client.R().Get("http://127.0.0.1/phpfpm_" + c.version + "_status") + if err != nil || !resp.IsSuccessState() { + facades.Log().Error("获取PHP-" + c.version + "运行状态失败") + controllers.Error(ctx, http.StatusInternalServerError, "[PHP-"+c.version+"] 获取运行状态失败") + return + } + + raw := resp.String() + dataKeys := []string{"应用池", "工作模式", "启动时间", "接受连接", "监听队列", "最大监听队列", "监听队列长度", "空闲进程数量", "活动进程数量", "总进程数量", "最大活跃进程数量", "达到进程上限次数", "慢请求"} + regexKeys := []string{"pool", "process manager", "start time", "accepted conn", "listen queue", "max listen queue", "listen queue len", "idle processes", "active processes", "total processes", "max active processes", "max children reached", "slow requests"} + + type Data struct { + Name string `json:"name"` + Value string `json:"value"` + } + data := make([]Data, len(dataKeys)) + for i := range dataKeys { + data[i].Name = dataKeys[i] + + r := regexp.MustCompile(fmt.Sprintf("%s:\\s+(.*)", regexKeys[i])) + match := r.FindStringSubmatch(raw) + + if len(match) > 1 { + data[i].Value = strings.TrimSpace(match[1]) + } + } + + controllers.Success(ctx, data) +} + +func (c *Php80Controller) ErrorLog(ctx http.Context) { + if !controllers.Check(ctx, "php"+c.version) { + return + } + + log := tools.ExecShell("tail -n 100 /www/server/php/" + c.version + "/var/log/php-fpm.log") + controllers.Success(ctx, log) +} + +func (c *Php80Controller) SlowLog(ctx http.Context) { + if !controllers.Check(ctx, "php"+c.version) { + return + } + + log := tools.ExecShell("tail -n 100 /www/server/php/" + c.version + "/var/log/slow.log") + controllers.Success(ctx, log) +} + +func (c *Php80Controller) ClearErrorLog(ctx http.Context) { + if !controllers.Check(ctx, "php"+c.version) { + return + } + + tools.ExecShell("echo '' > /www/server/php/" + c.version + "/var/log/php-fpm.log") + controllers.Success(ctx, true) +} + +func (c *Php80Controller) ClearSlowLog(ctx http.Context) { + if !controllers.Check(ctx, "php"+c.version) { + return + } + + tools.ExecShell("echo '' > /www/server/php/" + c.version + "/var/log/slow.log") + controllers.Success(ctx, true) +} + +type Extension struct { + Name string `json:"name"` + Slug string `json:"slug"` + Description string `json:"description"` + Installed bool `json:"installed"` +} + +func (c *Php80Controller) GetExtensionList(ctx http.Context) { + if !controllers.Check(ctx, "php"+c.version) { + return + } + + extensions := c.GetExtensions() + controllers.Success(ctx, extensions) +} + +func (c *Php80Controller) InstallExtension(ctx http.Context) { + if !controllers.Check(ctx, "php"+c.version) { + return + } + + slug := ctx.Request().Input("slug") + if len(slug) == 0 { + controllers.Error(ctx, http.StatusBadRequest, "参数错误") + return + } + + extensions := c.GetExtensions() + for _, item := range extensions { + if item.Slug == slug { + if item.Installed { + controllers.Error(ctx, http.StatusBadRequest, "扩展已安装") + return + } + + var task models.Task + task.Name = "安装PHP-" + c.version + "扩展-" + item.Name + task.Status = models.TaskStatusWaiting + task.Shell = "bash /www/panel/scripts/php_extensions/" + item.Slug + ".sh install " + c.version + ">> /tmp/" + item.Slug + ".log 2>&1" + task.Log = "/tmp/" + item.Slug + ".log" + if err := facades.Orm().Query().Create(&task); err != nil { + facades.Log().Error("[PHP-" + c.version + "] 创建安装拓展任务失败:" + err.Error()) + controllers.Error(ctx, http.StatusInternalServerError, "系统内部错误") + return + } + + c.task.Process(task.ID) + + controllers.Success(ctx, true) + return + } + } + + controllers.Error(ctx, http.StatusBadRequest, "扩展不存在") +} + +func (c *Php80Controller) UninstallExtension(ctx http.Context) { + if !controllers.Check(ctx, "php"+c.version) { + return + } + + slug := ctx.Request().Input("slug") + if len(slug) == 0 { + controllers.Error(ctx, http.StatusBadRequest, "参数错误") + return + } + + extensions := c.GetExtensions() + for _, item := range extensions { + if item.Slug == slug { + if !item.Installed { + controllers.Error(ctx, http.StatusBadRequest, "扩展未安装") + return + } + + var task models.Task + task.Name = "卸载PHP-" + c.version + "扩展-" + item.Name + task.Status = models.TaskStatusWaiting + task.Shell = "bash /www/panel/scripts/php_extensions/" + item.Slug + ".sh uninstall " + c.version + ">> /tmp/" + item.Slug + ".log 2>&1" + task.Log = "/tmp/" + item.Slug + ".log" + if err := facades.Orm().Query().Create(&task); err != nil { + facades.Log().Error("[PHP-" + c.version + "] 创建卸载拓展任务失败:" + err.Error()) + controllers.Error(ctx, http.StatusInternalServerError, "系统内部错误") + return + } + + c.task.Process(task.ID) + + controllers.Success(ctx, true) + return + } + } + + controllers.Error(ctx, http.StatusBadRequest, "扩展不存在") +} + +func (c *Php80Controller) GetExtensions() []Extension { + var extensions []Extension + extensions = append(extensions, Extension{ + Name: "OPcache", + Slug: "Zend OPcache", + Description: "OPcache 通过将 PHP 脚本预编译的字节码存储到共享内存中来提升 PHP 的性能,存储预编译字节码可以省去每次加载和解析 PHP 脚本的开销。", + Installed: false, + }) + extensions = append(extensions, Extension{ + Name: "PhpRedis", + Slug: "redis", + Description: "PhpRedis 是一个用C语言编写的PHP模块,用来连接并操作 Redis 数据库上的数据。", + Installed: false, + }) + extensions = append(extensions, Extension{ + Name: "ImageMagick", + Slug: "imagick", + Description: "ImageMagick 是一个免费的创建、编辑、合成图片的软件。", + Installed: false, + }) + extensions = append(extensions, Extension{ + Name: "Exif", + Slug: "exif", + Description: "通过 exif 扩展,你可以操作图像元数据。", + Installed: false, + }) + extensions = append(extensions, Extension{ + Name: "pdo_pgsql", + Slug: "pdo_pgsql", + Description: "(需先安装PostgreSQL)pdo_pgsql 是一个驱动程序,它实现了 PHP 数据对象(PDO)接口以启用从 PHP 到 PostgreSQL 数据库的访问。", + Installed: false, + }) + extensions = append(extensions, Extension{ + Name: "ionCube", + Slug: "ionCube Loader", + Description: "ionCube 是一个专业级的PHP加密解密工具。", + Installed: false, + }) + + raw := tools.ExecShell("/www/server/php/" + c.version + "/bin/php -m") + rawExtensionList := strings.Split(raw, "\n") + + for _, item := range rawExtensionList { + if !strings.Contains(item, "[") && item != "" { + for i := range extensions { + if extensions[i].Name == item { + extensions[i].Installed = true + } + } + } + } + + return extensions +} diff --git a/app/http/controllers/plugins/plugins.go b/app/http/controllers/plugins/plugins.go deleted file mode 100644 index 8797c709..00000000 --- a/app/http/controllers/plugins/plugins.go +++ /dev/null @@ -1,59 +0,0 @@ -package plugins - -import ( - "sync" - - "github.com/goravel/framework/contracts/http" - "github.com/goravel/framework/facades" - - "panel/app/http/controllers" - "panel/app/services" -) - -// Check 检查插件是否可用 -func Check(ctx http.Context, slug string) bool { - plugin := services.NewPluginImpl().GetBySlug(slug) - installedPlugin := services.NewPluginImpl().GetInstalledBySlug(slug) - installedPlugins, err := services.NewPluginImpl().AllInstalled() - if err != nil { - facades.Log().Error("[面板][插件] 获取已安装插件失败") - controllers.Error(ctx, http.StatusInternalServerError, "系统内部错误") - return false - } - - if installedPlugin.Version != plugin.Version || installedPlugin.Slug != plugin.Slug { - controllers.Error(ctx, http.StatusForbidden, "插件 "+slug+" 需要更新至 "+plugin.Version+" 版本") - return false - } - - var lock sync.RWMutex - pluginsMap := make(map[string]bool) - - for _, p := range installedPlugins { - lock.Lock() - pluginsMap[p.Slug] = true - lock.Unlock() - } - - for _, require := range plugin.Requires { - lock.RLock() - _, requireFound := pluginsMap[require] - lock.RUnlock() - if !requireFound { - controllers.Error(ctx, http.StatusForbidden, "插件 "+slug+" 需要依赖 "+require+" 插件") - return false - } - } - - for _, exclude := range plugin.Excludes { - lock.RLock() - _, excludeFound := pluginsMap[exclude] - lock.RUnlock() - if excludeFound { - controllers.Error(ctx, http.StatusForbidden, "插件 "+slug+" 不兼容 "+exclude+" 插件") - return false - } - } - - return true -} diff --git a/app/http/controllers/safe_controller.go b/app/http/controllers/safe_controller.go index 7e7709af..b11a6dd4 100644 --- a/app/http/controllers/safe_controller.go +++ b/app/http/controllers/safe_controller.go @@ -26,7 +26,7 @@ func (r *SafeController) GetFirewallStatus(ctx http.Context) { func (r *SafeController) SetFirewallStatus(ctx http.Context) { var out string - if ctx.Request().QueryBool("status") { + if ctx.Request().InputBool("status") { if tools.IsRHEL() { out = tools.ExecShell("systemctl start firewalld") } else { @@ -45,7 +45,7 @@ func (r *SafeController) SetFirewallStatus(ctx http.Context) { func (r *SafeController) GetFirewallRules(ctx http.Context) { if !r.firewallStatus() { - Error(ctx, http.StatusBadRequest, "防火墙未启动") + Success(ctx, nil) return } @@ -68,16 +68,13 @@ func (r *SafeController) GetFirewallRules(ctx http.Context) { Success(ctx, rules) } else { - out := tools.ExecShell("ufw status numbered | grep ALLOW | awk '{print $2}'") + out := tools.ExecShell("ufw status | grep -v '(v6)' | grep ALLOW | awk '{print $1}'") if len(out) == 0 { Success(ctx, nil) return } var rules []map[string]string for _, port := range strings.Split(out, "\n") { - if strings.Contains(port, "]") { - continue - } rule := strings.Split(port, "/") rules = append(rules, map[string]string{ "port": rule[0], @@ -162,7 +159,13 @@ func (r *SafeController) firewallStatus() bool { } func (r *SafeController) GetSshStatus(ctx http.Context) { - out := tools.ExecShell("systemctl status sshd | grep Active | awk '{print $3}'") + var out string + if tools.IsRHEL() { + out = tools.ExecShell("systemctl status sshd | grep Active | awk '{print $3}'") + } else { + out = tools.ExecShell("systemctl status ssh | grep Active | awk '{print $3}'") + } + running := false if out == "(running)" { running = true @@ -172,19 +175,29 @@ func (r *SafeController) GetSshStatus(ctx http.Context) { } func (r *SafeController) SetSshStatus(ctx http.Context) { - if ctx.Request().QueryBool("status") { - tools.ExecShell("systemctl enable sshd") - tools.ExecShell("systemctl start sshd") + if ctx.Request().InputBool("status") { + if tools.IsRHEL() { + tools.ExecShell("systemctl enable sshd") + tools.ExecShell("systemctl start sshd") + } else { + tools.ExecShell("systemctl enable ssh") + tools.ExecShell("systemctl start ssh") + } } else { - tools.ExecShell("systemctl stop sshd") - tools.ExecShell("systemctl disable sshd") + if tools.IsRHEL() { + tools.ExecShell("systemctl stop sshd") + tools.ExecShell("systemctl disable sshd") + } else { + tools.ExecShell("systemctl stop ssh") + tools.ExecShell("systemctl disable ssh") + } } Success(ctx, nil) } func (r *SafeController) GetSshPort(ctx http.Context) { - out := tools.ExecShell("cat /etc/ssh/sshd_config | grep Port | awk '{print $2}'") + out := tools.ExecShell("cat /etc/ssh/sshd_config | grep 'Port ' | awk '{print $2}'") Success(ctx, out) } @@ -195,7 +208,7 @@ func (r *SafeController) SetSshPort(ctx http.Context) { return } - oldPort := tools.ExecShell("cat /etc/ssh/sshd_config | grep Port | awk '{print $2}'") + oldPort := tools.ExecShell("cat /etc/ssh/sshd_config | grep 'Port ' | awk '{print $2}'") tools.ExecShell("sed -i 's/#Port " + oldPort + "/Port " + cast.ToString(port) + "/g' /etc/ssh/sshd_config") tools.ExecShell("sed -i 's/Port " + oldPort + "/Port " + cast.ToString(port) + "/g' /etc/ssh/sshd_config") @@ -226,17 +239,17 @@ func (r *SafeController) GetPingStatus(ctx http.Context) { func (r *SafeController) SetPingStatus(ctx http.Context) { if tools.IsRHEL() { - if ctx.Request().QueryBool("status") { - tools.ExecShell("firewall-cmd --permanent --add-rich-rule='rule protocol value=icmp drop'") - } else { + if ctx.Request().InputBool("status") { tools.ExecShell("firewall-cmd --permanent --remove-rich-rule='rule protocol value=icmp drop'") + } else { + tools.ExecShell("firewall-cmd --permanent --add-rich-rule='rule protocol value=icmp drop'") } tools.ExecShell("firewall-cmd --reload") } else { - if ctx.Request().QueryBool("status") { - tools.ExecShell("sed -i 's/-A ufw-before-input -p icmp --icmp-type echo-request -j ACCEPT/-A ufw-before-input -p icmp --icmp-type echo-request -j DROP/g' /etc/ufw/before.rules") - } else { + if ctx.Request().InputBool("status") { tools.ExecShell("sed -i 's/-A ufw-before-input -p icmp --icmp-type echo-request -j DROP/-A ufw-before-input -p icmp --icmp-type echo-request -j ACCEPT/g' /etc/ufw/before.rules") + } else { + tools.ExecShell("sed -i 's/-A ufw-before-input -p icmp --icmp-type echo-request -j ACCEPT/-A ufw-before-input -p icmp --icmp-type echo-request -j DROP/g' /etc/ufw/before.rules") } tools.ExecShell("ufw reload") } diff --git a/app/http/controllers/website_controller.go b/app/http/controllers/website_controller.go index fdfc2b82..b1c808db 100644 --- a/app/http/controllers/website_controller.go +++ b/app/http/controllers/website_controller.go @@ -8,10 +8,10 @@ import ( "github.com/goravel/framework/contracts/http" "github.com/goravel/framework/facades" - "panel/app/models" - "panel/pkg/tools" + "panel/app/models" "panel/app/services" + "panel/pkg/tools" ) type WebsiteController struct { @@ -46,15 +46,18 @@ func (c *WebsiteController) List(ctx http.Context) { } func (c *WebsiteController) Add(ctx http.Context) { + if !Check(ctx, "openresty") { + return + } validator, err := ctx.Request().Validate(map[string]string{ "name": "required|regex:^[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)*$", "domain": "required", "php": "required", "db": "required", - "db_type": "required_if:db,1", - "db_name": "required_if:db,1", - "db_user": "required_if:db,1", - "db_password": "required_if:db,1", + "db_type": "required_if:db,true", + "db_name": "required_if:db,true", + "db_user": "required_if:db,true", + "db_password": "required_if:db,true", }) if err != nil { Error(ctx, http.StatusBadRequest, err.Error()) @@ -66,11 +69,14 @@ func (c *WebsiteController) Add(ctx http.Context) { } var website services.PanelWebsite - err = ctx.Request().Bind(&website) - if err != nil { - Error(ctx, http.StatusBadRequest, err.Error()) - return - } + website.Name = ctx.Request().Input("name") + website.Domain = ctx.Request().Input("domain") + website.Php = ctx.Request().InputInt("php") + website.Db = ctx.Request().InputBool("db") + website.DbType = ctx.Request().Input("db_type") + website.DbName = ctx.Request().Input("db_name") + website.DbUser = ctx.Request().Input("db_user") + website.DbPassword = ctx.Request().Input("db_password") newSite, err := c.website.Add(website) if err != nil { @@ -83,23 +89,14 @@ func (c *WebsiteController) Add(ctx http.Context) { } func (c *WebsiteController) Delete(ctx http.Context) { - validator, err := ctx.Request().Validate(map[string]string{ - "id": "required|int", - }) - if err != nil { - Error(ctx, http.StatusBadRequest, err.Error()) + if !Check(ctx, "openresty") { return } - if validator.Fails() { - Error(ctx, http.StatusBadRequest, validator.Errors().All()) - return - } - id := ctx.Request().InputInt("id") - err = c.website.Delete(id) + err := c.website.Delete(id) if err != nil { facades.Log().Error("[面板][WebsiteController] 删除网站失败 ", err) - Error(ctx, http.StatusInternalServerError, "系统内部错误") + Error(ctx, http.StatusInternalServerError, "删除网站失败: "+err.Error()) return } @@ -107,6 +104,9 @@ func (c *WebsiteController) Delete(ctx http.Context) { } func (c *WebsiteController) GetDefaultConfig(ctx http.Context) { + if !Check(ctx, "openresty") { + return + } index := tools.ReadFile("/www/server/openresty/html/index.html") stop := tools.ReadFile("/www/server/openresty/html/stop.html") @@ -117,6 +117,9 @@ func (c *WebsiteController) GetDefaultConfig(ctx http.Context) { } func (c *WebsiteController) SaveDefaultConfig(ctx http.Context) { + if !Check(ctx, "openresty") { + return + } index := ctx.Request().Input("index") stop := ctx.Request().Input("stop") @@ -136,6 +139,9 @@ func (c *WebsiteController) SaveDefaultConfig(ctx http.Context) { } func (c *WebsiteController) GetConfig(ctx http.Context) { + if !Check(ctx, "openresty") { + return + } id := ctx.Request().InputInt("id") if id == 0 { Error(ctx, http.StatusBadRequest, "参数错误") @@ -153,21 +159,40 @@ func (c *WebsiteController) GetConfig(ctx http.Context) { } func (c *WebsiteController) SaveConfig(ctx http.Context) { + if !Check(ctx, "openresty") { + return + } validator, err := ctx.Request().Validate(map[string]string{ - "id": "required|int", + "id": "required", + "domains": "required", + "ports": "required", + "hsts": "bool", + "ssl": "bool", + "http_redirect": "bool", + "open_basedir": "bool", + "waf": "required", + "waf_cache": "required", + "waf_mode": "required", + "waf_cc_deny": "required", + "index": "required", + "path": "required", + "root": "required", + "raw": "required", + "php": "required", + "ssl_certificate": "required_if:ssl,true", + "ssl_certificate_key": "required_if:ssl,true", }) if err != nil { Error(ctx, http.StatusBadRequest, err.Error()) return } if validator.Fails() { - Error(ctx, http.StatusBadRequest, validator.Errors().All()) + Error(ctx, http.StatusBadRequest, validator.Errors().One()) return } - id := ctx.Request().InputInt("id") var website models.Website - if facades.Orm().Query().Where("id", id).FirstOrFail(&website) != nil { + if facades.Orm().Query().Where("id", ctx.Request().Input("id")).FirstOrFail(&website) != nil { Error(ctx, http.StatusBadRequest, "网站不存在") return } @@ -178,9 +203,9 @@ func (c *WebsiteController) SaveConfig(ctx http.Context) { } // 原文 - raw := tools.ReadFile("/www/server/panel/vhost/openresty/" + website.Name + ".conf") + raw := tools.ReadFile("/www/server/vhost/" + website.Name + ".conf") if strings.TrimSpace(raw) != strings.TrimSpace(ctx.Request().Input("raw")) { - tools.WriteFile("/www/server/panel/vhost/openresty/"+website.Name+".conf", ctx.Request().Input("raw"), 0644) + tools.WriteFile("/www/server/vhost/"+website.Name+".conf", ctx.Request().Input("raw"), 0644) Success(ctx, nil) return } @@ -195,7 +220,7 @@ func (c *WebsiteController) SaveConfig(ctx http.Context) { // 域名 domain := "server_name" - domains := strings.Split(ctx.Request().Input("domain"), "\n") + domains := strings.Split(ctx.Request().Input("domains"), "\n") if len(domains) == 0 { Error(ctx, http.StatusBadRequest, "域名不能为空") return @@ -216,7 +241,7 @@ func (c *WebsiteController) SaveConfig(ctx http.Context) { // 端口 var port strings.Builder - ports := strings.Split(ctx.Request().Input("port"), "\n") + ports := strings.Split(ctx.Request().Input("ports"), "\n") if len(ports) == 0 { Error(ctx, http.StatusBadRequest, "端口不能为空") return @@ -287,15 +312,13 @@ func (c *WebsiteController) SaveConfig(ctx http.Context) { wafMode := ctx.Request().Input("waf_mode", "DYNAMIC") wafCcDeny := ctx.Request().Input("waf_cc_deny", "rate=1000r/m duration=60m") wafCache := ctx.Request().Input("waf_cache", "capacity=50") - wafConfig := ` -# waf标记位开始 + wafConfig := `# waf标记位开始 waf ` + waf + `; waf_rule_path /www/server/openresty/ngx_waf/assets/rules/; waf_mode ` + wafMode + `; waf_cc_deny ` + wafCcDeny + `; waf_cache ` + wafCache + `; - -` + ` wafConfigOld := tools.Cut(raw, "# waf标记位开始", "# waf标记位结束") if len(strings.TrimSpace(wafConfigOld)) != 0 { raw = strings.Replace(raw, wafConfigOld, "", -1) @@ -308,8 +331,7 @@ func (c *WebsiteController) SaveConfig(ctx http.Context) { if ssl { tools.WriteFile("/www/server/vhost/ssl/"+website.Name+".pem", ctx.Request().Input("ssl_certificate"), 0644) tools.WriteFile("/www/server/vhost/ssl/"+website.Name+".key", ctx.Request().Input("ssl_certificate_key"), 0644) - sslConfig := ` -# ssl标记位开始 + sslConfig := `# ssl标记位开始 ssl_certificate /www/server/vhost/ssl/` + website.Name + `.pem; ssl_certificate_key /www/server/vhost/ssl/` + website.Name + `.key; ssl_session_timeout 1d; @@ -318,28 +340,21 @@ func (c *WebsiteController) SaveConfig(ctx http.Context) { ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384; ssl_prefer_server_ciphers off; - -` + ` if ctx.Request().InputBool("http_redirect") { - sslConfig += ` - - # http重定向标记位开始 + sslConfig += `# http重定向标记位开始 if (\$server_port !~ 443){ return 301 https://\$host\$request_uri; } error_page 497 https://\$host\$request_uri; # http重定向标记位结束 - -` + ` } if ctx.Request().InputBool("hsts") { - sslConfig += ` - - # hsts标记位开始 + sslConfig += `# hsts标记位开始 add_header Strict-Transport-Security "max-age=63072000" always; # hsts标记位结束 - -` + ` } sslConfigOld := tools.Cut(raw, "# ssl标记位开始", "# ssl标记位结束") if len(strings.TrimSpace(sslConfigOld)) != 0 { @@ -349,7 +364,7 @@ func (c *WebsiteController) SaveConfig(ctx http.Context) { } else { sslConfigOld := tools.Cut(raw, "# ssl标记位开始", "# ssl标记位结束") if len(strings.TrimSpace(sslConfigOld)) != 0 { - raw = strings.Replace(raw, sslConfigOld, "", -1) + raw = strings.Replace(raw, sslConfigOld, "\n ", -1) } } @@ -379,7 +394,10 @@ func (c *WebsiteController) SaveConfig(ctx http.Context) { Success(ctx, nil) } -func (c *WebsiteController) ClearSiteLpg(ctx http.Context) { +func (c *WebsiteController) ClearLog(ctx http.Context) { + if !Check(ctx, "openresty") { + return + } id := ctx.Request().InputInt("id") if id == 0 { Error(ctx, http.StatusBadRequest, "参数错误") @@ -426,7 +444,6 @@ func (c *WebsiteController) UpdateRemark(ctx http.Context) { } func (c *WebsiteController) BackupList(ctx http.Context) { - backupList, err := c.backup.WebsiteList() if err != nil { facades.Log().Error("[面板][WebsiteController] 获取网站备份列表失败 ", err) @@ -463,6 +480,9 @@ func (c *WebsiteController) CreateBackup(ctx http.Context) { } func (c *WebsiteController) ResetConfig(ctx http.Context) { + if !Check(ctx, "openresty") { + return + } id := ctx.Request().InputInt("id") if id == 0 { Error(ctx, http.StatusBadRequest, "参数错误") @@ -511,7 +531,7 @@ server # waf标记位开始 waf on; - waf_rule_path /www/server/nginx/ngx_waf/assets/rules/; + waf_rule_path /www/server/openresty/ngx_waf/assets/rules/; waf_mode DYNAMIC; waf_cc_deny rate=1000r/m duration=60m; waf_cache capacity=50; @@ -550,7 +570,10 @@ server Success(ctx, nil) } -func (c *WebsiteController) SetStatus(ctx http.Context) { +func (c *WebsiteController) Status(ctx http.Context) { + if !Check(ctx, "openresty") { + return + } id := ctx.Request().InputInt("id") if id == 0 { Error(ctx, http.StatusBadRequest, "参数错误") @@ -574,26 +597,26 @@ func (c *WebsiteController) SetStatus(ctx http.Context) { raw := tools.ReadFile("/www/server/vhost/" + website.Name + ".conf") // 运行目录 - rootConfig := tools.Cut(raw, "# root标记位开始", "# root标记位结束") + rootConfig := tools.Cut(raw, "# root标记位开始\n", "# root标记位结束") match := regexp.MustCompile(`root\s+(.+);`).FindStringSubmatch(rootConfig) if len(match) == 2 { if website.Status { root := regexp.MustCompile(`# root\s+(.+);`).FindStringSubmatch(rootConfig) - raw = strings.ReplaceAll(raw, rootConfig, "root "+root[1]+";") + raw = strings.ReplaceAll(raw, rootConfig, " root "+root[1]+";\n ") } else { - raw = strings.ReplaceAll(raw, rootConfig, "root /www/server/openresty/html;\n# root "+match[1]+";\n") + raw = strings.ReplaceAll(raw, rootConfig, " root /www/server/openresty/html;\n # root "+match[1]+";\n ") } } // 默认文件 - indexConfig := tools.Cut(raw, "# index标记位开始", "# index标记位结束") + indexConfig := tools.Cut(raw, "# index标记位开始\n", "# index标记位结束") match = regexp.MustCompile(`index\s+(.+);`).FindStringSubmatch(indexConfig) if len(match) == 2 { if website.Status { index := regexp.MustCompile(`# index\s+(.+);`).FindStringSubmatch(indexConfig) - raw = strings.ReplaceAll(raw, indexConfig, "index "+index[1]+";") + raw = strings.ReplaceAll(raw, indexConfig, " index "+index[1]+";\n ") } else { - raw = strings.ReplaceAll(raw, indexConfig, "index stop.html;\n# index "+match[1]+";\n") + raw = strings.ReplaceAll(raw, indexConfig, " index stop.html;\n # index "+match[1]+";\n ") } } diff --git a/app/plugins/mysql57/mysql57.go b/app/plugins/mysql57/mysql57.go index fb245f18..6136d3e5 100644 --- a/app/plugins/mysql57/mysql57.go +++ b/app/plugins/mysql57/mysql57.go @@ -8,4 +8,7 @@ var ( Version = "5.7.42" Requires = []string{} Excludes = []string{"mysql80"} + Install = `bash /www/panel/scripts/mysql/install.sh 57` + Uninstall = `bash /www/panel/scripts/mysql/uninstall.sh 57` + Update = `echo "not support now"` ) diff --git a/app/plugins/mysql80/mysql80.go b/app/plugins/mysql80/mysql80.go index 7273e854..8f97bf68 100644 --- a/app/plugins/mysql80/mysql80.go +++ b/app/plugins/mysql80/mysql80.go @@ -3,9 +3,12 @@ package mysql80 var ( Name = "MySQL-8.0" Author = "耗子" - Description = "MySQL 是最流行的关系型数据库管理系统之一,Oracle 旗下产品。" + Description = "MySQL 是最流行的关系型数据库管理系统之一,Oracle 旗下产品。(内存 < 4G 无法安装)" Slug = "mysql80" Version = "8.0.33" Requires = []string{} Excludes = []string{"mysql57"} + Install = `bash /www/panel/scripts/mysql/install.sh 80` + Uninstall = `bash /www/panel/scripts/mysql/uninstall.sh 80` + Update = `echo "not support now"` ) diff --git a/app/plugins/openresty/openresty.go b/app/plugins/openresty/openresty.go index 1b5af222..81806bb2 100644 --- a/app/plugins/openresty/openresty.go +++ b/app/plugins/openresty/openresty.go @@ -8,4 +8,7 @@ var ( Version = "1.21.4.1" Requires = []string{} Excludes = []string{} + Install = "bash /www/panel/scripts/openresty/install.sh" + Uninstall = "bash /www/panel/scripts/openresty/uninstall.sh" + Update = "bash /www/panel/scripts/openresty/install.sh" ) diff --git a/app/plugins/php74/php74.go b/app/plugins/php74/php74.go index 7e0263ca..729590c5 100644 --- a/app/plugins/php74/php74.go +++ b/app/plugins/php74/php74.go @@ -8,4 +8,7 @@ var ( Version = "7.4.33" Requires = []string{} Excludes = []string{} + Install = `bash /www/panel/scripts/php/install.sh 74` + Uninstall = `bash /www/panel/scripts/php/uninstall.sh 74` + Update = `bash /www/panel/scripts/php/install.sh 74` ) diff --git a/app/plugins/php80/php80.go b/app/plugins/php80/php80.go index e3d1e8ec..d7053ef0 100644 --- a/app/plugins/php80/php80.go +++ b/app/plugins/php80/php80.go @@ -8,4 +8,7 @@ var ( Version = "8.0.29" Requires = []string{} Excludes = []string{} + Install = `bash /www/panel/scripts/php/install.sh 80` + Uninstall = `bash /www/panel/scripts/php/uninstall.sh 80` + Update = `bash /www/panel/scripts/php/install.sh 80` ) diff --git a/app/services/plugin.go b/app/services/plugin.go index 373bdd51..d7eb2bab 100644 --- a/app/services/plugin.go +++ b/app/services/plugin.go @@ -5,9 +5,11 @@ import ( "github.com/goravel/framework/facades" "panel/app/models" + "panel/app/plugins/mysql57" "panel/app/plugins/mysql80" "panel/app/plugins/openresty" "panel/app/plugins/php74" + "panel/app/plugins/php80" ) // PanelPlugin 插件元数据结构 @@ -19,11 +21,16 @@ type PanelPlugin struct { Version string Requires []string Excludes []string + Install string + Uninstall string + Update string } type Plugin interface { AllInstalled() ([]models.Plugin, error) All() []PanelPlugin + GetBySlug(slug string) PanelPlugin + GetInstalledBySlug(slug string) models.Plugin } type PluginImpl struct { @@ -55,6 +62,21 @@ func (r *PluginImpl) All() []PanelPlugin { Version: openresty.Version, Requires: openresty.Requires, Excludes: openresty.Excludes, + Install: openresty.Install, + Uninstall: openresty.Uninstall, + Update: openresty.Update, + }) + p = append(p, PanelPlugin{ + Name: mysql57.Name, + Author: mysql57.Author, + Description: mysql57.Description, + Slug: mysql57.Slug, + Version: mysql57.Version, + Requires: mysql57.Requires, + Excludes: mysql57.Excludes, + Install: mysql57.Install, + Uninstall: mysql57.Uninstall, + Update: mysql57.Update, }) p = append(p, PanelPlugin{ Name: mysql80.Name, @@ -64,6 +86,9 @@ func (r *PluginImpl) All() []PanelPlugin { Version: mysql80.Version, Requires: mysql80.Requires, Excludes: mysql80.Excludes, + Install: mysql80.Install, + Uninstall: mysql80.Uninstall, + Update: mysql80.Update, }) p = append(p, PanelPlugin{ Name: php74.Name, @@ -73,6 +98,21 @@ func (r *PluginImpl) All() []PanelPlugin { Version: php74.Version, Requires: php74.Requires, Excludes: php74.Excludes, + Install: php74.Install, + Uninstall: php74.Uninstall, + Update: php74.Update, + }) + p = append(p, PanelPlugin{ + Name: php80.Name, + Author: php80.Author, + Description: php80.Description, + Slug: php80.Slug, + Version: php80.Version, + Requires: php80.Requires, + Excludes: php80.Excludes, + Install: php80.Install, + Uninstall: php80.Uninstall, + Update: php80.Update, }) return p diff --git a/app/services/website.go b/app/services/website.go index f87c67df..a4c70a83 100644 --- a/app/services/website.go +++ b/app/services/website.go @@ -116,8 +116,7 @@ func (r *WebsiteImpl) Add(website PanelWebsite) (models.Website, error) { tools.Mkdir(website.Path, 0755) - index := ` - + index := ` @@ -162,8 +161,7 @@ func (r *WebsiteImpl) Add(website PanelWebsite) (models.Website, error) { } } - nginxConf := fmt.Sprintf(` -# 配置文件中的标记位请勿随意修改,改错将导致面板无法识别! + nginxConf := fmt.Sprintf(`# 配置文件中的标记位请勿随意修改,改错将导致面板无法识别! # 有自定义配置需求的,请将自定义的配置写在各标记位下方。 server { @@ -189,7 +187,7 @@ server # waf标记位开始 waf on; - waf_rule_path /www/server/nginx/ngx_waf/assets/rules/; + waf_rule_path /www/server/openresty/ngx_waf/assets/rules/; waf_mode DYNAMIC; waf_cc_deny rate=1000r/m duration=60m; waf_cache capacity=50; @@ -221,12 +219,12 @@ server `, portList, domainList, website.Path, website.Php, website.Name, website.Name, website.Name) - tools.WriteFile("/www/server/panel/vhost/openresty/"+website.Name+".conf", nginxConf, 0644) - tools.WriteFile("/www/server/panel/vhost/openresty/rewrite/"+website.Name+".conf", "", 0644) - tools.WriteFile("/www/server/panel/vhost/openresty/ssl/"+website.Name+".pem", "", 0644) - tools.WriteFile("/www/server/panel/vhost/openresty/ssl/"+website.Name+".key", "", 0644) + tools.WriteFile("/www/server/vhost/"+website.Name+".conf", nginxConf, 0644) + tools.WriteFile("/www/server/vhost/rewrite/"+website.Name+".conf", "", 0644) + tools.WriteFile("/www/server/vhost/ssl/"+website.Name+".pem", "", 0644) + tools.WriteFile("/www/server/vhost/ssl/"+website.Name+".key", "", 0644) - tools.ExecShellAsync("systemctl reload openresty") + tools.ExecShell("systemctl reload openresty") // TODO 创建数据库 @@ -236,7 +234,7 @@ server // Delete 删除网站 func (r *WebsiteImpl) Delete(id int) error { var website models.Website - if err := facades.Orm().Query().Where("id", id).First(&website); err != nil { + if err := facades.Orm().Query().Where("id", id).FirstOrFail(&website); err != nil { return err } @@ -244,13 +242,13 @@ func (r *WebsiteImpl) Delete(id int) error { return err } - tools.RemoveFile("/www/server/panel/vhost/openresty/" + website.Name + ".conf") - tools.RemoveFile("/www/server/panel/vhost/openresty/rewrite/" + website.Name + ".conf") - tools.RemoveFile("/www/server/panel/vhost/openresty/ssl/" + website.Name + ".pem") - tools.RemoveFile("/www/server/panel/vhost/openresty/ssl/" + website.Name + ".key") + tools.RemoveFile("/www/server/vhost/" + website.Name + ".conf") + tools.RemoveFile("/www/server/vhost/rewrite/" + website.Name + ".conf") + tools.RemoveFile("/www/server/vhost/ssl/" + website.Name + ".pem") + tools.RemoveFile("/www/server/vhost/ssl/" + website.Name + ".key") tools.RemoveFile(website.Path) - tools.ExecShellAsync("systemctl reload openresty") + tools.ExecShell("systemctl reload openresty") // TODO 删除数据库 @@ -264,7 +262,7 @@ func (r *WebsiteImpl) GetConfig(id int) (WebsiteSetting, error) { return WebsiteSetting{}, err } - config := tools.ReadFile("/www/server/panel/vhost/openresty/" + website.Name + ".conf") + config := tools.ReadFile("/www/server/vhost/" + website.Name + ".conf") var setting WebsiteSetting setting.Name = website.Name @@ -342,7 +340,7 @@ func (r *WebsiteImpl) GetConfig(id int) (WebsiteSetting, error) { setting.WafCache = match[1] } - setting.Rewrite = tools.ReadFile("/www/server/panel/vhost/openresty/rewrite/" + website.Name + ".conf") + setting.Rewrite = tools.ReadFile("/www/server/vhost/rewrite/" + website.Name + ".conf") setting.Log = tools.ExecShell("tail -n 100 /www/wwwlogs/" + website.Name + ".log") return setting, nil diff --git a/bootstrap/app.go b/bootstrap/app.go index 8b3afaff..b55d9062 100644 --- a/bootstrap/app.go +++ b/bootstrap/app.go @@ -2,6 +2,7 @@ package bootstrap import ( "github.com/goravel/framework/foundation" + "github.com/goravel/framework/support/carbon" "panel/config" ) @@ -9,9 +10,12 @@ import ( func Boot() { app := foundation.NewApplication() - //Bootstrap the application + // Bootstrap the application app.Boot() - //Bootstrap the config. + // Bootstrap the config. config.Boot() + + // 设置 Carbon 时区 + carbon.SetTimezone(carbon.PRC) } diff --git a/config/panel.go b/config/panel.go index 74e305f0..95d2d0aa 100644 --- a/config/panel.go +++ b/config/panel.go @@ -8,6 +8,6 @@ func init() { config := facades.Config() config.Add("panel", map[string]any{ "name": "耗子面板", - "version": "v2.0.0", + "version": "v2.0.7", }) } diff --git a/main.go b/main.go index 245764fe..cf8ae9f6 100644 --- a/main.go +++ b/main.go @@ -32,5 +32,8 @@ func main() { } }() + // 启动计划任务 + go facades.Schedule().Run() + select {} } diff --git a/pkg/tools/system.go b/pkg/tools/system.go index cf7bf601..431e49e7 100644 --- a/pkg/tools/system.go +++ b/pkg/tools/system.go @@ -4,6 +4,7 @@ import ( "os" "os/exec" "path/filepath" + "strings" "github.com/goravel/framework/facades" ) @@ -55,7 +56,7 @@ func ExecShell(shell string) string { return "" } - return string(output) + return strings.TrimSpace(string(output)) } // ExecShellAsync 异步执行 shell 命令 diff --git a/public/panel/views/cron.html b/public/panel/views/cron.html index d043a727..17ac8d45 100644 --- a/public/panel/views/cron.html +++ b/public/panel/views/cron.html @@ -143,7 +143,7 @@ Date: 2023-07-21 if (res.code === 0) { $('#cron-log-view').html(res.data); } else { - layer.msg(res.msg, {icon: 2, time: 1000}); + layer.msg(res.message, {icon: 2, time: 1000}); } } }); @@ -195,7 +195,7 @@ Date: 2023-07-21 table.reload('panel-cron'); layer.close(index); } else { - layer.msg(res.msg, {icon: 2, time: 1000}); + layer.msg(res.message, {icon: 2, time: 1000}); } } }); @@ -225,7 +225,7 @@ Date: 2023-07-21 time: 1000 }); } else { - layer.msg(res.msg, {icon: 2, time: 1000}); + layer.msg(res.message, {icon: 2, time: 1000}); } } }); @@ -249,7 +249,7 @@ Date: 2023-07-21 if (res.code === 0) { layer.msg('设置成功', {icon: 1, time: 1000}); } else { - layer.msg(res.msg, {icon: 2, time: 1000}); + layer.msg(res.message, {icon: 2, time: 1000}); } } }); diff --git a/public/panel/views/login.html b/public/panel/views/login.html index 27e1ab36..fe10eab9 100644 --- a/public/panel/views/login.html +++ b/public/panel/views/login.html @@ -24,7 +24,8 @@ placeholder="密码" class="layui-input">
- 忘记密码?
@@ -41,55 +42,59 @@
diff --git a/public/panel/views/plugin.html b/public/panel/views/plugin.html index 72c9d7bc..ec2a069c 100644 --- a/public/panel/views/plugin.html +++ b/public/panel/views/plugin.html @@ -92,7 +92,7 @@ time: 1000 }) } else { - layer.msg(res.msg, {icon: 2, time: 1000}) + layer.msg(res.message, {icon: 2, time: 1000}) } } }) @@ -114,7 +114,7 @@ time: 1000 }) } else { - layer.msg(res.msg, {icon: 2, time: 1000}) + layer.msg(res.message, {icon: 2, time: 1000}) } } }) @@ -136,7 +136,7 @@ time: 1000 }) } else { - layer.msg(res.msg, {icon: 2, time: 1000}) + layer.msg(res.message, {icon: 2, time: 1000}) } } }) @@ -163,7 +163,7 @@ // 还原开关状态 obj.elem.checked = !obj.elem.checked form.render('checkbox') - layer.msg(res.msg, {icon: 2, time: 1000}) + layer.msg(res.message, {icon: 2, time: 1000}) } } , error: function (xhr, status, error) { diff --git a/public/panel/views/setting.html b/public/panel/views/setting.html index 3bdca849..c6e314f8 100644 --- a/public/panel/views/setting.html +++ b/public/panel/views/setting.html @@ -117,7 +117,6 @@ Date: 2023-07-21 form.render(); $('#setting-api-token').hide(); - // ajax获取设置项并赋值 admin.req({ url: "/api/panel/setting/list" , type: 'get' @@ -127,7 +126,6 @@ Date: 2023-07-21 layer.msg('系统信息获取失败,请刷新重试!') return false; } - console.log(result) form.val("panel_setting", result.data ); diff --git a/public/panel/views/task.html b/public/panel/views/task.html index 5f48d352..64fc49ab 100644 --- a/public/panel/views/task.html +++ b/public/panel/views/task.html @@ -1,7 +1,7 @@ 任务中心 @@ -73,7 +73,7 @@ Date: 2023-07-21 render_plugin_install_log(d); setInterval(function () { render_plugin_install_log(d); - }, 1000); + }, 2000); } }; @@ -151,7 +151,7 @@ Date: 2023-07-21 table.reload('panel-task-finished-table'); }); } else { - layer.msg(res.msg, {icon: 2, time: 1000}); + layer.msg(res.message, {icon: 2, time: 1000}); } } }); diff --git a/public/panel/views/website/add.html b/public/panel/views/website/add.html index fbf04a66..8594d86b 100644 --- a/public/panel/views/website/add.html +++ b/public/panel/views/website/add.html @@ -25,9 +25,8 @@ Date: 2023-06-24
@@ -57,7 +56,7 @@ Date: 2023-06-24
- +
@@ -122,11 +121,11 @@ Date: 2023-06-24 if (data.value === 'mysql') { $('#add-website-db-info').show() $('input[name="db_name"]').val($('input[name="name"]').val() + '_mysql') - $('input[name="db_username"]').val($('input[name="name"]').val() + '_mysql') + $('input[name="db_user"]').val($('input[name="name"]').val() + '_mysql') } else if (data.value === 'postgresql') { $('#add-website-db-info').show() $('input[name="db_name"]').val($('input[name="name"]').val() + '_postgresql') - $('input[name="db_username"]').val($('input[name="name"]').val() + '_postgresql') + $('input[name="db_user"]').val($('input[name="name"]').val() + '_postgresql') } }) // 提交 diff --git a/public/panel/views/website/default_settings.html b/public/panel/views/website/default_settings.html deleted file mode 100644 index 218d39a1..00000000 --- a/public/panel/views/website/default_settings.html +++ /dev/null @@ -1,82 +0,0 @@ - - - diff --git a/public/panel/views/website/edit.html b/public/panel/views/website/edit.html index e5315dac..10d2a60e 100644 --- a/public/panel/views/website/edit.html +++ b/public/panel/views/website/edit.html @@ -1,7 +1,7 @@ diff --git a/public/panel/views/website/list.html b/public/panel/views/website/list.html index dd53f808..e0dcd978 100644 --- a/public/panel/views/website/list.html +++ b/public/panel/views/website/list.html @@ -1,7 +1,7 @@ 网站
@@ -16,7 +16,7 @@ Date: 2023-06-24 @@ -30,12 +30,16 @@ Date: 2023-06-24 + +
@@ -44,198 +48,200 @@ Date: 2023-06-24 diff --git a/routes/plugin.go b/routes/plugin.go index 413a9717..81c45c86 100644 --- a/routes/plugin.go +++ b/routes/plugin.go @@ -3,6 +3,8 @@ package routes import ( "github.com/goravel/framework/contracts/route" "github.com/goravel/framework/facades" + "panel/app/http/controllers/plugins/mysql57" + "panel/app/http/controllers/plugins/php80" "panel/app/http/controllers/plugins/mysql80" "panel/app/http/controllers/plugins/openresty" @@ -25,6 +27,35 @@ func Plugin() { route.Get("errorLog", openRestyController.ErrorLog) route.Post("clearErrorLog", openRestyController.ClearErrorLog) }) + facades.Route().Prefix("api/plugins/mysql57").Middleware(middleware.Jwt()).Group(func(route route.Route) { + mysql57Controller := mysql57.NewMysql57Controller() + route.Get("status", mysql57Controller.Status) + route.Post("reload", mysql57Controller.Reload) + route.Post("start", mysql57Controller.Start) + route.Post("stop", mysql57Controller.Stop) + route.Post("restart", mysql57Controller.Restart) + route.Get("load", mysql57Controller.Load) + route.Get("config", mysql57Controller.GetConfig) + route.Post("config", mysql57Controller.SaveConfig) + route.Get("errorLog", mysql57Controller.ErrorLog) + route.Get("clearErrorLog", mysql57Controller.ClearErrorLog) + route.Get("slowLog", mysql57Controller.SlowLog) + route.Get("clearSlowLog", mysql57Controller.ClearSlowLog) + route.Get("rootPassword", mysql57Controller.GetRootPassword) + route.Post("rootPassword", mysql57Controller.SetRootPassword) + route.Get("database", mysql57Controller.DatabaseList) + route.Post("addDatabase", mysql57Controller.AddDatabase) + route.Post("deleteDatabase", mysql57Controller.DeleteDatabase) + route.Get("backup", mysql57Controller.BackupList) + route.Post("createBackup", mysql57Controller.CreateBackup) + route.Post("deleteBackup", mysql57Controller.DeleteBackup) + route.Post("restoreBackup", mysql57Controller.RestoreBackup) + route.Get("user", mysql57Controller.UserList) + route.Post("addUser", mysql57Controller.AddUser) + route.Post("deleteUser", mysql57Controller.DeleteUser) + route.Post("setUserPassword", mysql57Controller.SetUserPassword) + route.Post("setUserPrivileges", mysql57Controller.SetUserPrivileges) + }) facades.Route().Prefix("api/plugins/mysql80").Middleware(middleware.Jwt()).Group(func(route route.Route) { mysql80Controller := mysql80.NewMysql80Controller() route.Get("status", mysql80Controller.Status) @@ -72,4 +103,22 @@ func Plugin() { route.Post("installExtension", php74Controller.InstallExtension) route.Post("uninstallExtension", php74Controller.UninstallExtension) }) + facades.Route().Prefix("api/plugins/php80").Middleware(middleware.Jwt()).Group(func(route route.Route) { + php80Controller := php80.NewPhp80Controller() + route.Get("status", php80Controller.Status) + route.Post("reload", php80Controller.Reload) + route.Post("start", php80Controller.Start) + route.Post("stop", php80Controller.Stop) + route.Post("restart", php80Controller.Restart) + route.Get("load", php80Controller.Load) + route.Get("config", php80Controller.GetConfig) + route.Post("config", php80Controller.SaveConfig) + route.Get("errorLog", php80Controller.ErrorLog) + route.Get("slowLog", php80Controller.SlowLog) + route.Get("clearErrorLog", php80Controller.ClearErrorLog) + route.Get("clearSlowLog", php80Controller.ClearSlowLog) + route.Get("extensions", php80Controller.GetExtensionList) + route.Post("installExtension", php80Controller.InstallExtension) + route.Post("uninstallExtension", php80Controller.UninstallExtension) + }) } diff --git a/routes/web.go b/routes/web.go index 41b84b85..da918f85 100644 --- a/routes/web.go +++ b/routes/web.go @@ -37,6 +37,18 @@ func Web() { r.Prefix("website").Middleware(middleware.Jwt()).Group(func(r route.Route) { websiteController := controllers.NewWebsiteController() r.Get("list", websiteController.List) + r.Post("add", websiteController.Add) + r.Post("delete", websiteController.Delete) + r.Get("defaultConfig", websiteController.GetDefaultConfig) + r.Post("defaultConfig", websiteController.SaveDefaultConfig) + r.Get("config", websiteController.GetConfig) + r.Post("config", websiteController.SaveConfig) + r.Get("clearLog", websiteController.ClearLog) + r.Post("updateRemark", websiteController.UpdateRemark) + r.Get("backupList", websiteController.BackupList) + r.Post("createBackup", websiteController.CreateBackup) + r.Post("resetConfig", websiteController.ResetConfig) + r.Post("status", websiteController.Status) }) r.Prefix("plugin").Middleware(middleware.Jwt()).Group(func(r route.Route) { pluginController := controllers.NewPluginController() @@ -61,8 +73,8 @@ func Web() { r.Get("firewallStatus", safeController.GetFirewallStatus) r.Post("firewallStatus", safeController.SetFirewallStatus) r.Get("firewallRules", safeController.GetFirewallRules) - r.Post("addFirewallRules", safeController.AddFirewallRule) - r.Post("deleteFirewallRules", safeController.DeleteFirewallRule) + r.Post("addFirewallRule", safeController.AddFirewallRule) + r.Post("deleteFirewallRule", safeController.DeleteFirewallRule) r.Get("sshStatus", safeController.GetSshStatus) r.Post("sshStatus", safeController.SetSshStatus) r.Get("sshPort", safeController.GetSshPort) @@ -76,6 +88,7 @@ func Web() { r.Post("saveDays", monitorController.SaveDays) r.Post("clear", monitorController.Clear) r.Get("list", monitorController.List) + r.Get("switchAndDays", monitorController.SwitchAndDays) }) r.Prefix("setting").Middleware(middleware.Jwt()).Group(func(r route.Route) { settingController := controllers.NewSettingController() diff --git a/scripts/mysql/install.sh b/scripts/mysql/install.sh index a7f15df6..0dd72023 100644 --- a/scripts/mysql/install.sh +++ b/scripts/mysql/install.sh @@ -26,10 +26,11 @@ setupPath="/www" mysqlPath="${setupPath}/server/mysql" mysqlVersion="" mysqlPassword=$(cat /dev/urandom | head -n 16 | md5sum | head -c 16) +cpuCore=$(cat /proc/cpuinfo | grep "processor" | wc -l) -if [[ "${1}" == "8.0" ]]; then +if [[ "${1}" == "80" ]]; then mysqlVersion="8.0.33" -elif [[ "${1}" == "5.7" ]]; then +elif [[ "${1}" == "57" ]]; then mysqlVersion="5.7.42" else echo -e $HR @@ -37,7 +38,7 @@ else exit 1 fi -if [[ "${memTotal}" -lt "4096" ]] && [[ "${1}" == "8.0" ]]; then +if [[ "${memTotal}" -lt "4096" ]] && [[ "${1}" == "80" ]]; then echo -e $HR echo "错误:这点内存(${memTotal}M)还想装 MySQL 8.0?洗洗睡吧!" exit 1 @@ -102,7 +103,12 @@ cd src mkdir build cd build cmake .. -DCMAKE_INSTALL_PREFIX=${mysqlPath} -DMYSQL_DATADIR=${mysqlPath}/data -DSYSCONFDIR=${mysqlPath}/conf -DWITH_MYISAM_STORAGE_ENGINE=1 -DWITH_INNOBASE_STORAGE_ENGINE=1 -DWITH_PARTITION_STORAGE_ENGINE=1 -DWITH_ARCHIVE_STORAGE_ENGINE=1 -DWITH_FEDERATED_STORAGE_ENGINE=1 -DWITH_BLACKHOLE_STORAGE_ENGINE=1 -DWITH_EXTRA_CHARSETS=all -DEXTRA_CHARSETS=all -DDEFAULT_CHARSET=utf8mb4 -DDEFAULT_COLLATION=utf8mb4_general_ci -DENABLED_LOCAL_INFILE=1 -DWITH_SYSTEMD=1 -DSYSTEMD_PID_DIR=${mysqlPath} -DWITH_SSL=/usr/local/openssl-1.1 -DWITH_BOOST=../boost -make -j$(nproc) + +if [[ "${cpuCore}" -gt "1" ]]; then + make -j2 +else + make +fi # 安装 make install @@ -175,14 +181,13 @@ interactive-timeout EOF # 根据CPU核心数确定写入线程数 -cpuCore=$(cat /proc/cpuinfo | grep "processor" | wc -l) sed -i 's/innodb_write_io_threads = 4/innodb_write_io_threads = '${cpuCore}'/g' ${mysqlPath}/conf/my.cnf sed -i 's/innodb_read_io_threads = 4/innodb_read_io_threads = '${cpuCore}'/g' ${mysqlPath}/conf/my.cnf -if [[ "${1}" == "8.0" ]]; then +if [[ "${1}" == "80" ]]; then sed -i '/query_cache_size/d' ${mysqlPath}/conf/my.cnf fi -if [[ "${1}" == "5.7" ]]; then +if [[ "${1}" == "57" ]]; then sed -i '/innodb_redo_log_capacity/d' ${mysqlPath}/conf/my.cnf fi @@ -269,8 +274,8 @@ chmod -R 755 ${mysqlPath} rm -rf ${mysqlPath}/src rm -rf ${mysqlPath}/data mkdir -p ${mysqlPath}/data -chown -R mysql:mysql ${mysqlPath}/data -chmod -R 755 ${mysqlPath}/data +chown -R mysql:mysql ${mysqlPath} +chmod -R 755 ${mysqlPath} ${mysqlPath}/bin/mysqld --initialize-insecure --user=mysql --basedir=${mysqlPath} --datadir=${mysqlPath}/data @@ -279,7 +284,7 @@ source /etc/profile # 启动 cp ${mysqlPath}/lib/systemd/system/mysqld.service /etc/systemd/system/mysqld.service -sed -i "#ExecStartPre#d" /etc/systemd/system/mysqld.service +sed -i "/ExecStartPre/d" /etc/systemd/system/mysqld.service systemctl daemon-reload systemctl enable mysqld @@ -287,4 +292,7 @@ systemctl start mysqld ${mysqlPath}/bin/mysqladmin -u root password ${mysqlPassword} -echo "mysql root password: ${mysqlPassword}" +panel writePlugin mysqsl${1} ${mysqlVersion} +panel writeMysqlPassword ${mysqlPassword} + +echo -e "${HR}\nMySQL-${1} 安装完成\n${HR}" diff --git a/scripts/mysql/uninstall.sh b/scripts/mysql/uninstall.sh index 1e13cd6f..5dd92d19 100644 --- a/scripts/mysql/uninstall.sh +++ b/scripts/mysql/uninstall.sh @@ -36,6 +36,6 @@ groupdel mysql sed -i '#export PATH=/www/server/mysql#d' /etc/profile source /etc/profile -panel deletePlugin mysql +panel deletePlugin mysql${1} -echo -e "${HR}\nMySQL uninstall completed.\n${HR}" +echo -e "${HR}\nMySQL-${1} 卸载完成\n${HR}" diff --git a/scripts/openresty/install.sh b/scripts/openresty/install.sh index 7ca0c78f..66424427 100644 --- a/scripts/openresty/install.sh +++ b/scripts/openresty/install.sh @@ -24,6 +24,7 @@ downloadUrl="https://dl.cdn.haozi.net/panel/openresty" setupPath="/www" openrestyPath="${setupPath}/server/openresty" openrestyVersion="1.21.4.1" +cpuCore=$(cat /proc/cpuinfo | grep "processor" | wc -l) # 安装依赖 if [ "${OS}" == "centos" ]; then @@ -93,14 +94,15 @@ rm -f nginx-dav-ext-module-3.0.0.tar.gz mv nginx-dav-ext-module-3.0.0 nginx-dav-ext-module # waf -wget -T 20 -O ngx_waf.zip ${downloadUrl}/modules/ngx_waf-6.1.9.zip -unzip -o ngx_waf.zip -mv ngx_waf-6.1.9 ngx_waf -rm -f ngx_waf.zip wget -T 60 -O uthash.zip ${downloadUrl}/modules/uthash-2.3.0.zip unzip -o uthash.zip mv uthash-2.3.0 uthash rm -f uthash.zip +cd ../ +wget -T 20 -O ngx_waf.zip ${downloadUrl}/modules/ngx_waf-6.1.9.zip +unzip -o ngx_waf.zip +mv ngx_waf-6.1.9 ngx_waf +rm -f ngx_waf.zip cd ngx_waf/inc wget -T 60 -O libinjection.zip ${downloadUrl}/modules/libinjection-3.10.0.zip unzip -o libinjection.zip @@ -133,18 +135,17 @@ cd ${openrestyPath}/src export LD_LIBRARY_PATH=/usr/local/lib/:$LD_LIBRARY_PATH export LIB_UTHASH=${openrestyPath}/src/uthash -./configure --user=www --group=www --prefix=${openrestyPath} --with-luajit --add-module=${openrestyPath}/src/ngx_cache_purge --add-module=${openrestyPath}/src/nginx-sticky-module --with-openssl=${openrestyPath}/src/openssl --with-pcre=${openrestyPath}/src/pcre --with-http_v2_module --with-http_slice_module --with-threads --with-stream --with-stream_ssl_module --with-stream_realip_module --with-stream_geoip_module --with-stream_ssl_preread_module --with-http_stub_status_module --with-http_ssl_module --with-http_image_filter_module --with-http_gzip_static_module --with-http_gunzip_module --with-ipv6 --with-http_geoip_module --with-http_sub_module --with-http_flv_module --with-http_addition_module --with-http_realip_module --with-http_mp4_module --with-ld-opt="-Wl,-E" --with-cc-opt="-O2 -std=gnu99" --with-cpu-opt="amd64" --with-http_dav_module --add-module=${openrestyPath}/src/nginx-dav-ext-module --add-module=${openrestyPath}/src/ngx_brotli --add-module=${openrestyPath}/src/ngx_waf -make -j$(nproc) +./configure --user=www --group=www --prefix=${openrestyPath} --with-luajit --add-module=${openrestyPath}/src/ngx_cache_purge --add-module=${openrestyPath}/src/nginx-sticky-module --with-openssl=${openrestyPath}/src/openssl --with-pcre=${openrestyPath}/src/pcre --with-http_v2_module --with-http_slice_module --with-threads --with-stream --with-stream_ssl_module --with-stream_realip_module --with-stream_geoip_module --with-stream_ssl_preread_module --with-http_stub_status_module --with-http_ssl_module --with-http_image_filter_module --with-http_gzip_static_module --with-http_gunzip_module --with-ipv6 --with-http_geoip_module --with-http_sub_module --with-http_flv_module --with-http_addition_module --with-http_realip_module --with-http_mp4_module --with-ld-opt="-Wl,-E" --with-cc-opt="-O2 -std=gnu99" --with-cpu-opt="amd64" --with-http_dav_module --add-module=${openrestyPath}/src/nginx-dav-ext-module --add-module=${openrestyPath}/src/ngx_brotli --add-module=${openrestyPath}/ngx_waf +if [[ "${cpuCore}" -gt "1" ]]; then + make -j2 +else + make +fi if [ "$?" != "0" ]; then echo -e $HR - echo "提示:OpenResty多线程编译失败,尝试单线程编译..." - make - if [ "$?" != "0" ]; then - echo -e $HR - echo "错误:OpenResty编译失败,请截图错误信息寻求帮助。" - rm -rf ${openrestyPath} - exit 1 - fi + echo "错误:OpenResty编译失败,请截图错误信息寻求帮助。" + rm -rf ${openrestyPath} + exit 1 fi make install if [ ! -f "${openrestyPath}/nginx/sbin/nginx" ]; then @@ -364,4 +365,4 @@ systemctl start openresty.service panel writePlugin openresty ${openrestyVersion} -echo -e "${HR}\nOpenResty install completed.\n${HR}" +echo -e "${HR}\nOpenResty 安装完成\n${HR}" diff --git a/scripts/openresty/uninstall.sh b/scripts/openresty/uninstall.sh index df6ab135..65407746 100644 --- a/scripts/openresty/uninstall.sh +++ b/scripts/openresty/uninstall.sh @@ -28,4 +28,4 @@ rm -rf /www/server/openresty panel deletePlugin openresty -echo -e "${HR}\nOpenResty uninstall completed.\n${HR}" +echo -e "${HR}\nOpenResty 卸载完成\n${HR}" diff --git a/scripts/php/install.sh b/scripts/php/install.sh index 264d38f0..d7f500d1 100644 --- a/scripts/php/install.sh +++ b/scripts/php/install.sh @@ -23,7 +23,9 @@ OS=$(source /etc/os-release && { [[ "$ID" == "debian" ]] && echo "debian"; } || downloadUrl="https://dl.cdn.haozi.net/panel/php" setupPath="/www" phpVersion="${1}" +phpVersionCode="" phpPath="${setupPath}/server/php/${phpVersion}" +cpuCore=$(cat /proc/cpuinfo | grep "processor" | wc -l) # 安装依赖 if [ "${OS}" == "centos" ]; then @@ -55,19 +57,20 @@ cd ${phpPath} # 下载源码 if [ "${phpVersion}" == "74" ]; then - wget -O ${phpPath}/php-${phpVersion}.tar.gz ${downloadUrl}/php-7.4.33.tar.gz + phpVersionCode="7.4.33" elif [ "${phpVersion}" == "80" ]; then - wget -O ${phpPath}/php-${phpVersion}.tar.gz ${downloadUrl}/php-8.0.29.tar.gz + phpVersionCode="8.0.29" elif [ "${phpVersion}" == "81" ]; then - wget -O ${phpPath}/php-${phpVersion}.tar.gz ${downloadUrl}/php-8.1.21.tar.gz + phpVersionCode="8.1.21" elif [ "${phpVersion}" == "82" ]; then - wget -O ${phpPath}/php-${phpVersion}.tar.gz ${downloadUrl}/php-8.2.8.tar.gz + phpVersionCode="8.2.8" else echo -e $HR echo "错误:PHP-${phpVersion}不支持,请检查版本号是否正确。" exit 1 fi +wget -O ${phpPath}/php-${phpVersion}.tar.gz ${downloadUrl}/php-${phpVersionCode}.tar.gz if [ "$?" != "0" ]; then echo -e $HR echo "错误:PHP-${phpVersion}下载失败,请检查网络是否正常。" @@ -101,7 +104,11 @@ cd src ./configure --prefix=${phpPath} --with-config-file-path=${phpPath}/etc --enable-fpm --with-fpm-user=www --with-fpm-group=www --enable-mysqlnd --with-mysqli=mysqlnd --with-pdo-mysql=mysqlnd --with-freetype --with-jpeg --with-zlib --with-libxml-dir=/usr --enable-xml --disable-rpath --enable-bcmath --enable-shmop --enable-sysvsem --enable-inline-optimization --with-curl --enable-mbregex --enable-mbstring --enable-intl --enable-pcntl --enable-ftp --enable-gd --with-openssl --with-mhash --enable-pcntl --enable-sockets --with-xmlrpc --enable-soap --with-gettext --enable-fileinfo --enable-opcache --with-sodium --with-webp # 编译安装 -make -j${nproc} +if [[ "${cpuCore}" -gt "1" ]]; then + make -j2 +else + make +fi make install if [ ! -f "${phpPath}/bin/php" ]; then echo -e $HR @@ -242,6 +249,6 @@ systemctl daemon-reload systemctl enable php-fpm-${phpVersion}.service systemctl start php-fpm-${phpVersion}.service -panel writePlugin php${phpVersion} +panel writePlugin php${phpVersion} ${phpVersionCode} -echo -e "${HR}\nPHP-${phpVersion} 安装成功!\n${HR}" +echo -e "${HR}\nPHP-${phpVersion} 安装完成\n${HR}" diff --git a/scripts/php/uninstall.sh b/scripts/php/uninstall.sh index eb87bfe5..ea9c13af 100644 --- a/scripts/php/uninstall.sh +++ b/scripts/php/uninstall.sh @@ -30,6 +30,6 @@ systemctl daemon-reload rm -rf ${phpPath} rm -f /usr/bin/php-${phpVersion} -panel deletePlugin openresty +panel deletePlugin php${phpVersion} -echo -e "${HR}\nPHP uninstall completed.\n${HR}" +echo -e "${HR}\nPHP-${phpVersion} 卸载完成\n${HR}"