mirror of
https://github.com/acepanel/panel.git
synced 2026-02-04 13:47:15 +08:00
feat: CLI 添加插件和网站的命令
This commit is contained in:
@@ -48,6 +48,7 @@ func (receiver *Panel) Handle(ctx console.Context) error {
|
||||
arg2 := ctx.Argument(2)
|
||||
arg3 := ctx.Argument(3)
|
||||
arg4 := ctx.Argument(4)
|
||||
arg5 := ctx.Argument(5)
|
||||
|
||||
switch action {
|
||||
case "init":
|
||||
@@ -564,6 +565,127 @@ func (receiver *Panel) Handle(ctx console.Context) error {
|
||||
|
||||
color.Greenln("删除设置成功")
|
||||
|
||||
case "addSite":
|
||||
name := arg1
|
||||
domain := arg2
|
||||
port := arg3
|
||||
path := arg4
|
||||
php := arg5
|
||||
if len(name) == 0 || len(domain) == 0 || len(port) == 0 || len(path) == 0 {
|
||||
color.Redln("参数错误")
|
||||
return nil
|
||||
}
|
||||
|
||||
domains := strings.Split(domain, ",")
|
||||
ports := strings.Split(port, ",")
|
||||
if len(domains) == 0 || len(ports) == 0 {
|
||||
color.Redln("参数错误")
|
||||
return nil
|
||||
}
|
||||
|
||||
var uintPorts []uint
|
||||
for _, p := range ports {
|
||||
uintPorts = append(uintPorts, cast.ToUint(p))
|
||||
}
|
||||
|
||||
website := services.NewWebsiteImpl()
|
||||
id, err := website.GetIDByName(name)
|
||||
if err != nil {
|
||||
color.Redln(err.Error())
|
||||
return nil
|
||||
}
|
||||
if id != 0 {
|
||||
color.Redln("网站名已存在")
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err = website.Add(internal.PanelWebsite{
|
||||
Name: name,
|
||||
Status: true,
|
||||
Domains: domains,
|
||||
Ports: uintPorts,
|
||||
Path: path,
|
||||
Php: php,
|
||||
Ssl: false,
|
||||
Db: false,
|
||||
})
|
||||
if err != nil {
|
||||
color.Redln(err.Error())
|
||||
return nil
|
||||
}
|
||||
|
||||
color.Greenln("网站添加成功")
|
||||
|
||||
case "removeSite":
|
||||
name := arg1
|
||||
if len(name) == 0 {
|
||||
color.Redln("参数错误")
|
||||
return nil
|
||||
}
|
||||
|
||||
website := services.NewWebsiteImpl()
|
||||
id, err := website.GetIDByName(name)
|
||||
if err != nil {
|
||||
color.Redln(err.Error())
|
||||
return nil
|
||||
}
|
||||
if id == 0 {
|
||||
color.Redln("网站名不存在")
|
||||
return nil
|
||||
}
|
||||
|
||||
if err = website.Delete(id); err != nil {
|
||||
color.Redln(err.Error())
|
||||
return nil
|
||||
}
|
||||
|
||||
color.Greenln("网站删除成功")
|
||||
|
||||
case "installPlugin":
|
||||
slug := arg1
|
||||
if len(slug) == 0 {
|
||||
color.Redln("参数错误")
|
||||
return nil
|
||||
}
|
||||
|
||||
plugin := services.NewPluginImpl()
|
||||
if err := plugin.Install(slug); err != nil {
|
||||
color.Redln(err.Error())
|
||||
return nil
|
||||
}
|
||||
|
||||
color.Greenln("任务已提交")
|
||||
|
||||
case "uninstallPlugin":
|
||||
slug := arg1
|
||||
if len(slug) == 0 {
|
||||
color.Redln("参数错误")
|
||||
return nil
|
||||
}
|
||||
|
||||
plugin := services.NewPluginImpl()
|
||||
if err := plugin.Uninstall(slug); err != nil {
|
||||
color.Redln(err.Error())
|
||||
return nil
|
||||
}
|
||||
|
||||
color.Greenln("任务已提交")
|
||||
|
||||
case "updatePlugin":
|
||||
slug := arg1
|
||||
if len(slug) == 0 {
|
||||
color.Redln("参数错误")
|
||||
return nil
|
||||
}
|
||||
|
||||
plugin := services.NewPluginImpl()
|
||||
if err := plugin.Update(slug); err != nil {
|
||||
color.Redln(err.Error())
|
||||
return nil
|
||||
}
|
||||
|
||||
color.Greenln("任务已提交")
|
||||
|
||||
default:
|
||||
color.Yellowln(facades.Config().GetString("panel.name") + "命令行工具 - " + facades.Config().GetString("panel.version"))
|
||||
color.Greenln("请使用以下命令:")
|
||||
@@ -575,6 +697,11 @@ func (receiver *Panel) Handle(ctx console.Context) error {
|
||||
color.Greenln("panel cleanTask 清理面板运行中和等待中的任务[任务卡住时使用]")
|
||||
color.Greenln("panel backup {website/mysql/postgresql} {name} {path} {save_copies} 备份网站 / MySQL数据库 / PostgreSQL数据库到指定目录并保留指定数量")
|
||||
color.Greenln("panel cutoff {website_name} {save_copies} 切割网站日志并保留指定数量")
|
||||
color.Greenln("panel installPlugin {slug} 安装插件")
|
||||
color.Greenln("panel uninstallPlugin {slug} 卸载插件")
|
||||
color.Greenln("panel updatePlugin {slug} 更新插件")
|
||||
color.Greenln("panel addSite {name} {domain} {port} {path} {php} 添加网站[域名和端口用英文逗号分隔]")
|
||||
color.Greenln("panel removeSite {name} 删除网站")
|
||||
color.Redln("以下命令请在开发者指导下使用:")
|
||||
color.Yellowln("panel init 初始化面板")
|
||||
color.Yellowln("panel writePlugin {slug} {version} 写入插件安装状态")
|
||||
|
||||
@@ -92,162 +92,33 @@ func (r *PluginController) List(ctx http.Context) http.Response {
|
||||
// Install 安装插件
|
||||
func (r *PluginController) Install(ctx http.Context) http.Response {
|
||||
slug := ctx.Request().Input("slug")
|
||||
plugin := r.plugin.GetBySlug(slug)
|
||||
installedPlugin := r.plugin.GetInstalledBySlug(slug)
|
||||
installedPlugins, err := r.plugin.AllInstalled()
|
||||
if err != nil {
|
||||
facades.Log().Request(ctx.Request()).Tags("面板", "插件中心").With(map[string]any{
|
||||
"slug": slug,
|
||||
}).Info("检查插件安装状态失败")
|
||||
|
||||
if err := r.plugin.Install(slug); err != nil {
|
||||
return ErrorSystem(ctx)
|
||||
}
|
||||
|
||||
if installedPlugin.ID != 0 {
|
||||
return Error(ctx, http.StatusUnprocessableEntity, "插件已安装")
|
||||
}
|
||||
|
||||
pluginsMap := make(map[string]bool)
|
||||
|
||||
for _, p := range installedPlugins {
|
||||
pluginsMap[p.Slug] = true
|
||||
}
|
||||
|
||||
for _, require := range plugin.Requires {
|
||||
_, requireFound := pluginsMap[require]
|
||||
if !requireFound {
|
||||
return Error(ctx, http.StatusForbidden, "插件 "+slug+" 需要依赖 "+require+" 插件")
|
||||
}
|
||||
}
|
||||
|
||||
for _, exclude := range plugin.Excludes {
|
||||
_, excludeFound := pluginsMap[exclude]
|
||||
if excludeFound {
|
||||
return Error(ctx, http.StatusForbidden, "插件 "+slug+" 不兼容 "+exclude+" 插件")
|
||||
}
|
||||
}
|
||||
|
||||
var task models.Task
|
||||
task.Name = "安装插件 " + plugin.Name
|
||||
task.Status = models.TaskStatusWaiting
|
||||
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().Request(ctx.Request()).Tags("面板", "插件中心").With(map[string]any{
|
||||
"slug": slug,
|
||||
"err": err.Error(),
|
||||
}).Info("创建任务失败")
|
||||
return ErrorSystem(ctx)
|
||||
}
|
||||
|
||||
r.task.Process(task.ID)
|
||||
return Success(ctx, "任务已提交")
|
||||
}
|
||||
|
||||
// Uninstall 卸载插件
|
||||
func (r *PluginController) Uninstall(ctx http.Context) http.Response {
|
||||
slug := ctx.Request().Input("slug")
|
||||
plugin := r.plugin.GetBySlug(slug)
|
||||
installedPlugin := r.plugin.GetInstalledBySlug(slug)
|
||||
installedPlugins, err := r.plugin.AllInstalled()
|
||||
if err != nil {
|
||||
facades.Log().Request(ctx.Request()).Tags("面板", "插件中心").With(map[string]any{
|
||||
"slug": slug,
|
||||
}).Info("检查插件安装状态失败")
|
||||
|
||||
if err := r.plugin.Uninstall(slug); err != nil {
|
||||
return ErrorSystem(ctx)
|
||||
}
|
||||
|
||||
if installedPlugin.ID == 0 {
|
||||
return Error(ctx, http.StatusUnprocessableEntity, "插件未安装")
|
||||
}
|
||||
|
||||
pluginsMap := make(map[string]bool)
|
||||
|
||||
for _, p := range installedPlugins {
|
||||
pluginsMap[p.Slug] = true
|
||||
}
|
||||
|
||||
for _, require := range plugin.Requires {
|
||||
_, requireFound := pluginsMap[require]
|
||||
if !requireFound {
|
||||
return Error(ctx, http.StatusForbidden, "插件 "+slug+" 需要依赖 "+require+" 插件")
|
||||
}
|
||||
}
|
||||
|
||||
for _, exclude := range plugin.Excludes {
|
||||
_, excludeFound := pluginsMap[exclude]
|
||||
if excludeFound {
|
||||
return Error(ctx, http.StatusForbidden, "插件 "+slug+" 不兼容 "+exclude+" 插件")
|
||||
}
|
||||
}
|
||||
|
||||
var task models.Task
|
||||
task.Name = "卸载插件 " + plugin.Name
|
||||
task.Status = models.TaskStatusWaiting
|
||||
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().Request(ctx.Request()).Tags("面板", "插件中心").With(map[string]any{
|
||||
"slug": slug,
|
||||
"err": err.Error(),
|
||||
}).Info("创建任务失败")
|
||||
return ErrorSystem(ctx)
|
||||
}
|
||||
|
||||
r.task.Process(task.ID)
|
||||
return Success(ctx, "任务已提交")
|
||||
}
|
||||
|
||||
// Update 更新插件
|
||||
func (r *PluginController) Update(ctx http.Context) http.Response {
|
||||
slug := ctx.Request().Input("slug")
|
||||
plugin := r.plugin.GetBySlug(slug)
|
||||
installedPlugin := r.plugin.GetInstalledBySlug(slug)
|
||||
installedPlugins, err := r.plugin.AllInstalled()
|
||||
if err != nil {
|
||||
facades.Log().Request(ctx.Request()).Tags("面板", "插件中心").With(map[string]any{
|
||||
"slug": slug,
|
||||
}).Info("检查插件安装状态失败")
|
||||
|
||||
if err := r.plugin.Update(slug); err != nil {
|
||||
return ErrorSystem(ctx)
|
||||
}
|
||||
|
||||
if installedPlugin.ID == 0 {
|
||||
return Error(ctx, http.StatusUnprocessableEntity, "插件未安装")
|
||||
}
|
||||
|
||||
pluginsMap := make(map[string]bool)
|
||||
|
||||
for _, p := range installedPlugins {
|
||||
pluginsMap[p.Slug] = true
|
||||
}
|
||||
|
||||
for _, require := range plugin.Requires {
|
||||
_, requireFound := pluginsMap[require]
|
||||
if !requireFound {
|
||||
return Error(ctx, http.StatusForbidden, "插件 "+slug+" 需要依赖 "+require+" 插件")
|
||||
}
|
||||
}
|
||||
|
||||
for _, exclude := range plugin.Excludes {
|
||||
_, excludeFound := pluginsMap[exclude]
|
||||
if excludeFound {
|
||||
return Error(ctx, http.StatusForbidden, "插件 "+slug+" 不兼容 "+exclude+" 插件")
|
||||
}
|
||||
}
|
||||
|
||||
var task models.Task
|
||||
task.Name = "更新插件 " + plugin.Name
|
||||
task.Status = models.TaskStatusWaiting
|
||||
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().Request(ctx.Request()).Tags("面板", "插件中心").With(map[string]any{
|
||||
"slug": slug,
|
||||
"err": err.Error(),
|
||||
}).Info("创建任务失败")
|
||||
return ErrorSystem(ctx)
|
||||
}
|
||||
|
||||
r.task.Process(task.ID)
|
||||
return Success(ctx, "任务已提交")
|
||||
}
|
||||
|
||||
|
||||
@@ -20,4 +20,7 @@ type Plugin interface {
|
||||
All() []PanelPlugin
|
||||
GetBySlug(slug string) PanelPlugin
|
||||
GetInstalledBySlug(slug string) models.Plugin
|
||||
Install(slug string) error
|
||||
Uninstall(slug string) error
|
||||
Update(slug string) error
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/goravel/framework/facades"
|
||||
|
||||
"panel/app/models"
|
||||
@@ -9,10 +11,13 @@ import (
|
||||
)
|
||||
|
||||
type PluginImpl struct {
|
||||
task internal.Task
|
||||
}
|
||||
|
||||
func NewPluginImpl() *PluginImpl {
|
||||
return &PluginImpl{}
|
||||
return &PluginImpl{
|
||||
task: NewTaskImpl(),
|
||||
}
|
||||
}
|
||||
|
||||
// AllInstalled 获取已安装的所有插件
|
||||
@@ -71,3 +76,141 @@ func (r *PluginImpl) GetInstalledBySlug(slug string) models.Plugin {
|
||||
|
||||
return plugin
|
||||
}
|
||||
|
||||
// Install 安装插件
|
||||
func (r *PluginImpl) Install(slug string) error {
|
||||
plugin := r.GetBySlug(slug)
|
||||
installedPlugin := r.GetInstalledBySlug(slug)
|
||||
installedPlugins, err := r.AllInstalled()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if installedPlugin.ID != 0 {
|
||||
return errors.New("插件已安装")
|
||||
}
|
||||
|
||||
pluginsMap := make(map[string]bool)
|
||||
|
||||
for _, p := range installedPlugins {
|
||||
pluginsMap[p.Slug] = true
|
||||
}
|
||||
|
||||
for _, require := range plugin.Requires {
|
||||
_, requireFound := pluginsMap[require]
|
||||
if !requireFound {
|
||||
return errors.New("插件 " + slug + " 需要依赖 " + require + " 插件")
|
||||
}
|
||||
}
|
||||
|
||||
for _, exclude := range plugin.Excludes {
|
||||
_, excludeFound := pluginsMap[exclude]
|
||||
if excludeFound {
|
||||
return errors.New("插件 " + slug + " 不兼容 " + exclude + " 插件")
|
||||
}
|
||||
}
|
||||
|
||||
var task models.Task
|
||||
task.Name = "安装插件 " + plugin.Name
|
||||
task.Status = models.TaskStatusWaiting
|
||||
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 {
|
||||
return errors.New("创建任务失败")
|
||||
}
|
||||
|
||||
r.task.Process(task.ID)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Uninstall 卸载插件
|
||||
func (r *PluginImpl) Uninstall(slug string) error {
|
||||
plugin := r.GetBySlug(slug)
|
||||
installedPlugin := r.GetInstalledBySlug(slug)
|
||||
installedPlugins, err := r.AllInstalled()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if installedPlugin.ID == 0 {
|
||||
return errors.New("插件未安装")
|
||||
}
|
||||
|
||||
pluginsMap := make(map[string]bool)
|
||||
|
||||
for _, p := range installedPlugins {
|
||||
pluginsMap[p.Slug] = true
|
||||
}
|
||||
|
||||
for _, require := range plugin.Requires {
|
||||
_, requireFound := pluginsMap[require]
|
||||
if !requireFound {
|
||||
return errors.New("插件 " + slug + " 需要依赖 " + require + " 插件")
|
||||
}
|
||||
}
|
||||
|
||||
for _, exclude := range plugin.Excludes {
|
||||
_, excludeFound := pluginsMap[exclude]
|
||||
if excludeFound {
|
||||
return errors.New("插件 " + slug + " 不兼容 " + exclude + " 插件")
|
||||
}
|
||||
}
|
||||
|
||||
var task models.Task
|
||||
task.Name = "卸载插件 " + plugin.Name
|
||||
task.Status = models.TaskStatusWaiting
|
||||
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 {
|
||||
return errors.New("创建任务失败")
|
||||
}
|
||||
|
||||
r.task.Process(task.ID)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update 更新插件
|
||||
func (r *PluginImpl) Update(slug string) error {
|
||||
plugin := r.GetBySlug(slug)
|
||||
installedPlugin := r.GetInstalledBySlug(slug)
|
||||
installedPlugins, err := r.AllInstalled()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if installedPlugin.ID == 0 {
|
||||
return errors.New("插件未安装")
|
||||
}
|
||||
|
||||
pluginsMap := make(map[string]bool)
|
||||
|
||||
for _, p := range installedPlugins {
|
||||
pluginsMap[p.Slug] = true
|
||||
}
|
||||
|
||||
for _, require := range plugin.Requires {
|
||||
_, requireFound := pluginsMap[require]
|
||||
if !requireFound {
|
||||
return errors.New("插件 " + slug + " 需要依赖 " + require + " 插件")
|
||||
}
|
||||
}
|
||||
|
||||
for _, exclude := range plugin.Excludes {
|
||||
_, excludeFound := pluginsMap[exclude]
|
||||
if excludeFound {
|
||||
return errors.New("插件 " + slug + " 不兼容 " + exclude + " 插件")
|
||||
}
|
||||
}
|
||||
|
||||
var task models.Task
|
||||
task.Name = "更新插件 " + plugin.Name
|
||||
task.Status = models.TaskStatusWaiting
|
||||
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 {
|
||||
return errors.New("创建任务失败")
|
||||
}
|
||||
|
||||
r.task.Process(task.ID)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -620,3 +620,13 @@ func (r *WebsiteImpl) GetConfigByName(name string) (internal.WebsiteSetting, err
|
||||
|
||||
return r.GetConfig(website.ID)
|
||||
}
|
||||
|
||||
// GetIDByName 根据网站名称获取网站ID
|
||||
func (r *WebsiteImpl) GetIDByName(name string) (uint, error) {
|
||||
var website models.Website
|
||||
if err := facades.Orm().Query().Where("name", name).First(&website); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return website.ID, nil
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ type Website interface {
|
||||
Delete(id uint) error
|
||||
GetConfig(id uint) (WebsiteSetting, error)
|
||||
GetConfigByName(name string) (WebsiteSetting, error)
|
||||
GetIDByName(name string) (uint, error)
|
||||
}
|
||||
|
||||
type PanelWebsite struct {
|
||||
|
||||
Reference in New Issue
Block a user