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

feat: 提交一批插件

This commit is contained in:
耗子
2024-09-27 22:51:54 +08:00
parent a00b44c485
commit 9a295241c3
27 changed files with 1294 additions and 50 deletions

View File

@@ -104,8 +104,8 @@ body:
Please copy and paste any relevant log output.
可以把文件拖入这个区域以添加日志文件。
Files can be dragged into this area to add log files.
面板日志文件可在 `/www/panel/storage/logs` 中找到。
Panel log files can be found in `/www/panel/storage/logs`.
面板日志文件可在安装目录 `panel/storage/logs` 中找到。
Panel log files can be found in the installation directory `panel/storage/logs`.
validations:
required: false
- type: textarea

View File

@@ -11,6 +11,7 @@ import (
"github.com/TheTNB/panel/internal/biz"
"github.com/TheTNB/panel/internal/data"
"github.com/TheTNB/panel/internal/panel"
"github.com/TheTNB/panel/internal/service"
"github.com/TheTNB/panel/pkg/io"
"github.com/TheTNB/panel/pkg/os"
@@ -127,7 +128,7 @@ maxretry = ` + jailMaxRetry + `
findtime = ` + jailFindTime + `
bantime = ` + jailBanTime + `
action = %(action_mwl)s
logpath = /www/wwwlogs/` + website.Name + `.log
logpath = ` + panel.Root + `/wwwlogs/` + website.Name + `.log
# ` + jailWebsiteName + `-` + jailWebsiteMode + `-END
`
raw += rule
@@ -170,13 +171,13 @@ ignoreregex =
filter = "sshd"
port, err = shell.Execf("cat /etc/ssh/sshd_config | grep 'Port ' | awk '{print $2}'")
case "mysql":
logPath = "/www/server/mysql/mysql-error.log"
logPath = panel.Root + "/server/mysql/mysql-error.log"
filter = "mysqld-auth"
port, err = shell.Execf("cat /www/server/mysql/conf/my.cnf | grep 'port' | head -n 1 | awk '{print $3}'")
port, err = shell.Execf("cat %s/server/mysql/conf/my.cnf | grep 'port' | head -n 1 | awk '{print $3}'", panel.Root)
case "pure-ftpd":
logPath = "/var/log/messages"
filter = "pure-ftpd"
port, err = shell.Execf(`cat /www/server/pure-ftpd/etc/pure-ftpd.conf | grep "Bind" | awk '{print $2}' | awk -F "," '{print $2}'`)
port, err = shell.Execf(`cat %s/server/pure-ftpd/etc/pure-ftpd.conf | grep "Bind" | awk '{print $2}' | awk -F "," '{print $2}'`, panel.Root)
default:
service.Error(w, http.StatusUnprocessableEntity, "未知服务")
return

19
internal/apps/frp/init.go Normal file
View File

@@ -0,0 +1,19 @@
package frp
import (
"github.com/go-chi/chi/v5"
"github.com/TheTNB/panel/pkg/apploader"
"github.com/TheTNB/panel/pkg/types"
)
func init() {
apploader.Register(&types.App{
Slug: "frp",
Route: func(r chi.Router) {
service := NewService()
r.Get("/config", service.GetConfig)
r.Post("/config", service.UpdateConfig)
},
})
}

View File

@@ -0,0 +1,10 @@
package frp
type Name struct {
Name string `form:"name" json:"name"`
}
type UpdateConfig struct {
Name string `form:"name" json:"name"`
Config string `form:"config" json:"config"`
}

View File

@@ -0,0 +1,73 @@
package frp
import (
"fmt"
"net/http"
"github.com/TheTNB/panel/internal/panel"
"github.com/TheTNB/panel/internal/service"
"github.com/TheTNB/panel/pkg/io"
"github.com/TheTNB/panel/pkg/systemctl"
)
type Service struct{}
func NewService() *Service {
return &Service{}
}
// GetConfig
//
// @Summary 获取配置
// @Description 获取 Frp 配置
// @Tags 插件-Frp
// @Produce json
// @Security BearerToken
// @Param service query string false "服务"
// @Success 200 {object} controllers.SuccessResponse
// @Router /plugins/frp/config [get]
func (s *Service) GetConfig(w http.ResponseWriter, r *http.Request) {
req, err := service.Bind[Name](r)
if err != nil {
service.Error(w, http.StatusUnprocessableEntity, err.Error())
return
}
config, err := io.Read(fmt.Sprintf("%s/server/frp/%s.toml", panel.Root, req.Name))
if err != nil {
service.Error(w, http.StatusInternalServerError, err.Error())
return
}
service.Success(w, config)
}
// UpdateConfig
//
// @Summary 更新配置
// @Description 更新 Frp 配置
// @Tags 插件-Frp
// @Produce json
// @Security BearerToken
// @Param data body requests.UpdateConfig true "request"
// @Success 200 {object} controllers.SuccessResponse
// @Router /plugins/frp/config [post]
func (s *Service) UpdateConfig(w http.ResponseWriter, r *http.Request) {
req, err := service.Bind[UpdateConfig](r)
if err != nil {
service.Error(w, http.StatusUnprocessableEntity, err.Error())
return
}
if err = io.Write(fmt.Sprintf("%s/server/frp/%s.toml", panel.Root, req.Name), req.Config, 0644); err != nil {
service.Error(w, http.StatusInternalServerError, err.Error())
return
}
if err = systemctl.Restart(req.Name); err != nil {
service.Error(w, http.StatusInternalServerError, err.Error())
return
}
service.Success(w, nil)
}

View File

@@ -0,0 +1,19 @@
package gitea
import (
"github.com/go-chi/chi/v5"
"github.com/TheTNB/panel/pkg/apploader"
"github.com/TheTNB/panel/pkg/types"
)
func init() {
apploader.Register(&types.App{
Slug: "gitea",
Route: func(r chi.Router) {
service := NewService()
r.Get("/config", service.GetConfig)
r.Post("/config", service.UpdateConfig)
},
})
}

View File

@@ -0,0 +1,5 @@
package gitea
type UpdateConfig struct {
Config string `form:"config" json:"config"`
}

View File

@@ -0,0 +1,64 @@
package gitea
import (
"fmt"
"net/http"
"github.com/TheTNB/panel/internal/panel"
"github.com/TheTNB/panel/internal/service"
"github.com/TheTNB/panel/pkg/io"
"github.com/TheTNB/panel/pkg/systemctl"
)
type Service struct{}
func NewService() *Service {
return &Service{}
}
// GetConfig
//
// @Summary 获取配置
// @Tags 插件-Gitea
// @Produce json
// @Security BearerToken
// @Success 200 {object} controllers.SuccessResponse
// @Router /plugins/gitea/config [get]
func (s *Service) GetConfig(w http.ResponseWriter, r *http.Request) {
config, err := io.Read(fmt.Sprintf("%s/server/gitea/app.ini", panel.Root))
if err != nil {
service.Error(w, http.StatusInternalServerError, err.Error())
return
}
service.Success(w, config)
}
// UpdateConfig
//
// @Summary 更新配置
// @Tags 插件-Gitea
// @Produce json
// @Security BearerToken
// @Param data body requests.UpdateConfig true "request"
// @Success 200 {object} controllers.SuccessResponse
// @Router /plugins/gitea/config [post]
func (s *Service) UpdateConfig(w http.ResponseWriter, r *http.Request) {
req, err := service.Bind[UpdateConfig](r)
if err != nil {
service.Error(w, http.StatusUnprocessableEntity, err.Error())
return
}
if err = io.Write(fmt.Sprintf("%s/server/gitea/app.ini", panel.Root), req.Config, 0644); err != nil {
service.Error(w, http.StatusInternalServerError, err.Error())
return
}
if err = systemctl.Restart("gitea"); err != nil {
service.Error(w, http.StatusInternalServerError, err.Error())
return
}
service.Success(w, nil)
}

View File

@@ -9,6 +9,7 @@ import (
"github.com/go-resty/resty/v2"
"github.com/spf13/cast"
"github.com/TheTNB/panel/internal/panel"
"github.com/TheTNB/panel/internal/service"
"github.com/TheTNB/panel/pkg/io"
"github.com/TheTNB/panel/pkg/shell"
@@ -34,7 +35,7 @@ func NewService() *Service {
// @Success 200 {object} h.SuccessResponse
// @Router /plugins/openresty/config [get]
func (s *Service) GetConfig(w http.ResponseWriter, r *http.Request) {
config, err := io.Read("/www/server/openresty/conf/nginx.conf")
config, err := io.Read(fmt.Sprintf("%s/server/openresty/conf/nginx.conf", panel.Root))
if err != nil {
service.Error(w, http.StatusInternalServerError, "获取配置失败")
return
@@ -58,7 +59,7 @@ func (s *Service) SaveConfig(w http.ResponseWriter, r *http.Request) {
service.Error(w, http.StatusInternalServerError, "配置不能为空")
}
if err := io.Write("/www/server/openresty/conf/nginx.conf", config, 0644); err != nil {
if err := io.Write(fmt.Sprintf("%s/server/openresty/conf/nginx.conf", panel.Root), config, 0644); err != nil {
service.Error(w, http.StatusInternalServerError, "保存配置失败")
}
@@ -79,11 +80,11 @@ func (s *Service) SaveConfig(w http.ResponseWriter, r *http.Request) {
// @Success 200 {object} h.SuccessResponse
// @Router /plugins/openresty/errorLog [get]
func (s *Service) ErrorLog(w http.ResponseWriter, r *http.Request) {
if !io.Exists("/www/wwwlogs/nginx_error.log") {
if !io.Exists(fmt.Sprintf("%s/wwwlogs/nginx_error.log", panel.Root)) {
service.Success(w, "")
}
out, err := shell.Execf("tail -n 100 /www/wwwlogs/openresty_error.log")
out, err := shell.Execf("tail -n 100 %s/%s", panel.Root, "/wwwlogs/openresty_error.log")
if err != nil {
service.Error(w, http.StatusInternalServerError, out)
}
@@ -100,7 +101,7 @@ func (s *Service) ErrorLog(w http.ResponseWriter, r *http.Request) {
// @Success 200 {object} h.SuccessResponse
// @Router /plugins/openresty/clearErrorLog [post]
func (s *Service) ClearErrorLog(w http.ResponseWriter, r *http.Request) {
if out, err := shell.Execf("echo '' > /www/wwwlogs/openresty_error.log"); err != nil {
if out, err := shell.Execf("echo '' > %s/%s", panel.Root, "/wwwlogs/openresty_error.log"); err != nil {
service.Error(w, http.StatusInternalServerError, out)
}

View File

@@ -0,0 +1,26 @@
package percona
import (
"github.com/go-chi/chi/v5"
"github.com/TheTNB/panel/pkg/apploader"
"github.com/TheTNB/panel/pkg/types"
)
func init() {
apploader.Register(&types.App{
Slug: "percona",
Route: func(r chi.Router) {
service := NewService()
r.Get("load", service.Load)
r.Get("config", service.GetConfig)
r.Post("config", service.UpdateConfig)
r.Get("errorLog", service.ErrorLog)
r.Post("clearErrorLog", service.ClearErrorLog)
r.Get("slowLog", service.SlowLog)
r.Post("clearSlowLog", service.ClearSlowLog)
r.Get("rootPassword", service.GetRootPassword)
r.Post("rootPassword", service.SetRootPassword)
},
})
}

View File

@@ -0,0 +1,9 @@
package percona
type UpdateConfig struct {
Config string `form:"config" json:"config"`
}
type SetRootPassword struct {
Password string `form:"password" json:"password"`
}

View File

@@ -0,0 +1,248 @@
package percona
import (
"fmt"
"net/http"
"regexp"
"github.com/spf13/cast"
"github.com/TheTNB/panel/internal/biz"
"github.com/TheTNB/panel/internal/data"
"github.com/TheTNB/panel/internal/panel"
"github.com/TheTNB/panel/internal/service"
"github.com/TheTNB/panel/pkg/db"
"github.com/TheTNB/panel/pkg/io"
"github.com/TheTNB/panel/pkg/shell"
"github.com/TheTNB/panel/pkg/str"
"github.com/TheTNB/panel/pkg/systemctl"
"github.com/TheTNB/panel/pkg/types"
)
type Service struct {
settingRepo biz.SettingRepo
}
func NewService() *Service {
return &Service{
settingRepo: data.NewSettingRepo(),
}
}
// GetConfig 获取配置
func (s *Service) GetConfig(w http.ResponseWriter, r *http.Request) {
config, err := io.Read(panel.Root + "/server/mysql/conf/my.cnf")
if err != nil {
service.Error(w, http.StatusInternalServerError, "获取 Percona 配置失败")
return
}
service.Success(w, config)
}
// UpdateConfig 保存配置
func (s *Service) UpdateConfig(w http.ResponseWriter, r *http.Request) {
req, err := service.Bind[UpdateConfig](r)
if err != nil {
service.Error(w, http.StatusUnprocessableEntity, err.Error())
return
}
if err := io.Write(panel.Root+"/server/mysql/conf/my.cnf", req.Config, 0644); err != nil {
service.Error(w, http.StatusInternalServerError, "写入 Percona 配置失败")
return
}
if err := systemctl.Reload("mysqld"); err != nil {
service.Error(w, http.StatusInternalServerError, "重载 Percona 失败")
return
}
service.Success(w, nil)
}
// Load 获取负载
func (s *Service) Load(w http.ResponseWriter, r *http.Request) {
rootPassword, err := s.settingRepo.Get(biz.SettingKeyPerconaRootPassword)
if err != nil {
service.Error(w, http.StatusInternalServerError, "获取 Percona root密码失败")
return
}
if len(rootPassword) == 0 {
service.Error(w, http.StatusUnprocessableEntity, "Percona root密码为空")
return
}
status, _ := systemctl.Status("mysqld")
if !status {
service.Success(w, []types.NV{})
return
}
raw, err := shell.Execf("mysqladmin -uroot -p" + rootPassword + " extended-status 2>&1")
if err != nil {
service.Error(w, http.StatusInternalServerError, "获取MySQL负载失败")
return
}
var load []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_buffer_pool_reads\s+\|\s+(\d+)\s+\|`, "Innodb索引命中率"},
{`Created_tmp_disk_tables\s+\|\s+(\d+)\s+\|`, "创建临时表到磁盘"},
{`Open_tables\s+\|\s+(\d+)\s+\|`, "已打开的表"},
{`Select_full_join\s+\|\s+(\d+)\s+\|`, "没有使用索引的量"},
{`Select_full_range_join\s+\|\s+(\d+)\s+\|`, "没有索引的JOIN量"},
{`Select_range_check\s+\|\s+(\d+)\s+\|`, "没有索引的子查询量"},
{`Sort_merge_passes\s+\|\s+(\d+)\s+\|`, "排序后的合并次数"},
{`Table_locks_waited\s+\|\s+(\d+)\s+\|`, "锁表次数"},
}
for _, expression := range expressions {
re := regexp.MustCompile(expression.regex)
matches := re.FindStringSubmatch(raw)
if len(matches) > 1 {
d := map[string]string{"name": expression.name, "value": matches[1]}
if expression.name == "发送" || expression.name == "接收" {
d["value"] = str.FormatBytes(cast.ToFloat64(matches[1]))
}
load = append(load, d)
}
}
// 索引命中率
readRequests := cast.ToFloat64(load[9]["value"])
reads := cast.ToFloat64(load[10]["value"])
load[9]["value"] = fmt.Sprintf("%.2f%%", readRequests/(reads+readRequests)*100)
// Innodb 索引命中率
bufferPoolReads := cast.ToFloat64(load[11]["value"])
bufferPoolReadRequests := cast.ToFloat64(load[12]["value"])
load[10]["value"] = fmt.Sprintf("%.2f%%", bufferPoolReadRequests/(bufferPoolReads+bufferPoolReadRequests)*100)
service.Success(w, load)
}
// ErrorLog 获取错误日志
func (s *Service) ErrorLog(w http.ResponseWriter, r *http.Request) {
log, err := shell.Execf("tail -n 100 %s/server/mysql/mysql-error.log", panel.Root)
if err != nil {
service.Error(w, http.StatusInternalServerError, log)
return
}
service.Success(w, log)
}
// ClearErrorLog 清空错误日志
func (s *Service) ClearErrorLog(w http.ResponseWriter, r *http.Request) {
if out, err := shell.Execf("echo '' > %s/server/mysql/mysql-error.log", panel.Root); err != nil {
service.Error(w, http.StatusInternalServerError, out)
return
}
service.Success(w, nil)
}
// SlowLog 获取慢查询日志
func (s *Service) SlowLog(w http.ResponseWriter, r *http.Request) {
log, err := shell.Execf("tail -n 100 %s/server/mysql/mysql-slow.log", panel.Root)
if err != nil {
service.Error(w, http.StatusInternalServerError, log)
return
}
service.Success(w, log)
}
// ClearSlowLog 清空慢查询日志
func (s *Service) ClearSlowLog(w http.ResponseWriter, r *http.Request) {
if out, err := shell.Execf("echo '' > %s/server/mysql/mysql-slow.log", panel.Root); err != nil {
service.Error(w, http.StatusInternalServerError, out)
return
}
service.Success(w, nil)
}
// GetRootPassword 获取root密码
func (s *Service) GetRootPassword(w http.ResponseWriter, r *http.Request) {
rootPassword, err := s.settingRepo.Get(biz.SettingKeyPerconaRootPassword)
if err != nil {
service.Error(w, http.StatusInternalServerError, "获取 Percona root密码失败")
return
}
if len(rootPassword) == 0 {
service.Error(w, http.StatusUnprocessableEntity, "Percona root密码为空")
return
}
service.Success(w, rootPassword)
}
// SetRootPassword 设置root密码
func (s *Service) SetRootPassword(w http.ResponseWriter, r *http.Request) {
req, err := service.Bind[SetRootPassword](r)
if err != nil {
service.Error(w, http.StatusUnprocessableEntity, err.Error())
return
}
oldRootPassword, _ := s.settingRepo.Get(biz.SettingKeyPerconaRootPassword)
mysql, err := db.NewMySQL("root", oldRootPassword, s.getSock(), "unix")
if err != nil {
// 尝试安全模式直接改密
if err = db.MySQLResetRootPassword(req.Password); err != nil {
service.Error(w, http.StatusInternalServerError, err.Error())
return
}
} else {
if err = mysql.UserPassword("root", req.Password); err != nil {
service.Error(w, http.StatusInternalServerError, err.Error())
return
}
}
if err = s.settingRepo.Set(biz.SettingKeyPerconaRootPassword, req.Password); err != nil {
service.Error(w, http.StatusInternalServerError, fmt.Sprintf("设置保存失败: %v", err))
return
}
service.Success(w, nil)
}
func (s *Service) getSock() string {
if io.Exists("/tmp/mysql.sock") {
return "/tmp/mysql.sock"
}
if io.Exists(panel.Root + "/server/mysql/config/my.cnf") {
config, _ := io.Read(panel.Root + "/server/mysql/config/my.cnf")
re := regexp.MustCompile(`socket\s*=\s*(['"]?)([^'"]+)`)
matches := re.FindStringSubmatch(config)
if len(matches) > 2 {
return matches[2]
}
}
if io.Exists("/etc/my.cnf") {
config, _ := io.Read("/etc/my.cnf")
re := regexp.MustCompile(`socket\s*=\s*(['"]?)([^'"]+)`)
matches := re.FindStringSubmatch(config)
if len(matches) > 2 {
return matches[2]
}
}
return "/tmp/mysql.sock"
}

83
internal/apps/php/init.go Normal file
View File

@@ -0,0 +1,83 @@
package php
import (
"github.com/go-chi/chi/v5"
"github.com/TheTNB/panel/pkg/apploader"
"github.com/TheTNB/panel/pkg/types"
)
func init() {
apploader.Register(&types.App{
Slug: "php80",
Route: func(r chi.Router) {
service := NewService(80)
r.Get("/load", service.Load)
r.Get("/config", service.GetConfig)
r.Post("/config", service.UpdateConfig)
r.Get("/fpmConfig", service.GetFPMConfig)
r.Post("/fpmConfig", service.UpdateFPMConfig)
r.Get("/errorLog", service.ErrorLog)
r.Get("/slowLog", service.SlowLog)
r.Post("/clearErrorLog", service.ClearErrorLog)
r.Post("/clearSlowLog", service.ClearSlowLog)
r.Get("/extensions", service.ExtensionList)
r.Post("/extensions", service.InstallExtension)
r.Delete("/extensions", service.UninstallExtension)
},
})
apploader.Register(&types.App{
Slug: "php81",
Route: func(r chi.Router) {
service := NewService(81)
r.Get("/load", service.Load)
r.Get("/config", service.GetConfig)
r.Post("/config", service.UpdateConfig)
r.Get("/fpmConfig", service.GetFPMConfig)
r.Post("/fpmConfig", service.UpdateFPMConfig)
r.Get("/errorLog", service.ErrorLog)
r.Get("/slowLog", service.SlowLog)
r.Post("/clearErrorLog", service.ClearErrorLog)
r.Post("/clearSlowLog", service.ClearSlowLog)
r.Get("/extensions", service.ExtensionList)
r.Post("/extensions", service.InstallExtension)
r.Delete("/extensions", service.UninstallExtension)
},
})
apploader.Register(&types.App{
Slug: "php82",
Route: func(r chi.Router) {
service := NewService(82)
r.Get("/load", service.Load)
r.Get("/config", service.GetConfig)
r.Post("/config", service.UpdateConfig)
r.Get("/fpmConfig", service.GetFPMConfig)
r.Post("/fpmConfig", service.UpdateFPMConfig)
r.Get("/errorLog", service.ErrorLog)
r.Get("/slowLog", service.SlowLog)
r.Post("/clearErrorLog", service.ClearErrorLog)
r.Post("/clearSlowLog", service.ClearSlowLog)
r.Get("/extensions", service.ExtensionList)
r.Post("/extensions", service.InstallExtension)
r.Delete("/extensions", service.UninstallExtension)
},
})
apploader.Register(&types.App{
Slug: "php83",
Route: func(r chi.Router) {
service := NewService(83)
r.Get("/load", service.Load)
r.Get("/config", service.GetConfig)
r.Post("/config", service.UpdateConfig)
r.Get("/fpmConfig", service.GetFPMConfig)
r.Post("/fpmConfig", service.UpdateFPMConfig)
r.Get("/errorLog", service.ErrorLog)
r.Get("/slowLog", service.SlowLog)
r.Post("/clearErrorLog", service.ClearErrorLog)
r.Post("/clearSlowLog", service.ClearSlowLog)
r.Get("/extensions", service.ExtensionList)
r.Post("/extensions", service.InstallExtension)
r.Delete("/extensions", service.UninstallExtension)
},
})
}

View File

@@ -0,0 +1,9 @@
package php
type UpdateConfig struct {
Config string `form:"config" json:"config"`
}
type ExtensionSlug struct {
Slug string `form:"slug" json:"slug"`
}

View File

@@ -0,0 +1,513 @@
package php
import (
"fmt"
"net/http"
"regexp"
"slices"
"strings"
"time"
"github.com/go-resty/resty/v2"
"github.com/spf13/cast"
"github.com/TheTNB/panel/internal/biz"
"github.com/TheTNB/panel/internal/data"
"github.com/TheTNB/panel/internal/panel"
"github.com/TheTNB/panel/internal/service"
"github.com/TheTNB/panel/pkg/io"
"github.com/TheTNB/panel/pkg/shell"
"github.com/TheTNB/panel/pkg/types"
)
type Service struct {
version uint
taskRepo biz.TaskRepo
}
func NewService(version uint) *Service {
return &Service{
version: version,
taskRepo: data.NewTaskRepo(),
}
}
// GetConfig
//
// @Summary 获取配置
// @Tags 插件-PHP
// @Produce json
// @Security BearerToken
// @Param version path int true "PHP 版本"
// @Success 200 {object} controllers.SuccessResponse
// @Router /plugins/php/{version}/config [get]
func (s *Service) GetConfig(w http.ResponseWriter, r *http.Request) {
config, err := io.Read(fmt.Sprintf("%s/server/php/%d/etc/php.ini", panel.Root, s.version))
if err != nil {
service.Error(w, http.StatusInternalServerError, err.Error())
return
}
service.Success(w, config)
}
// UpdateConfig
//
// @Summary 保存配置
// @Tags 插件-PHP
// @Produce json
// @Security BearerToken
// @Param version path int true "PHP 版本"
// @Param config body string true "配置"
// @Success 200 {object} controllers.SuccessResponse
// @Router /plugins/php/{version}/config [post]
func (s *Service) UpdateConfig(w http.ResponseWriter, r *http.Request) {
req, err := service.Bind[UpdateConfig](r)
if err != nil {
service.Error(w, http.StatusUnprocessableEntity, err.Error())
return
}
if err = io.Write(fmt.Sprintf("%s/server/php/%d/etc/php.ini", panel.Root, s.version), req.Config, 0644); err != nil {
service.Error(w, http.StatusInternalServerError, err.Error())
return
}
service.Success(w, nil)
}
// GetFPMConfig
//
// @Summary 获取 FPM 配置
// @Tags 插件-PHP
// @Produce json
// @Security BearerToken
// @Param version path int true "PHP 版本"
// @Success 200 {object} controllers.SuccessResponse
// @Router /plugins/php/{version}/fpmConfig [get]
func (s *Service) GetFPMConfig(w http.ResponseWriter, r *http.Request) {
config, err := io.Read(fmt.Sprintf("%s/server/php/%d/etc/php-fpm.conf", panel.Root, s.version))
if err != nil {
service.Error(w, http.StatusInternalServerError, err.Error())
return
}
service.Success(w, config)
}
// UpdateFPMConfig
//
// @Summary 保存 FPM 配置
// @Tags 插件-PHP
// @Produce json
// @Security BearerToken
// @Param version path int true "PHP 版本"
// @Param config body string true "配置"
// @Success 200 {object} controllers.SuccessResponse
// @Router /plugins/php/{version}/fpmConfig [post]
func (s *Service) UpdateFPMConfig(w http.ResponseWriter, r *http.Request) {
req, err := service.Bind[UpdateConfig](r)
if err != nil {
service.Error(w, http.StatusUnprocessableEntity, err.Error())
return
}
if err = io.Write(fmt.Sprintf("%s/server/php/%d/etc/php-fpm.conf", panel.Root, s.version), req.Config, 0644); err != nil {
service.Error(w, http.StatusInternalServerError, err.Error())
return
}
service.Success(w, nil)
}
// Load
//
// @Summary 获取负载状态
// @Tags 插件-PHP
// @Produce json
// @Security BearerToken
// @Param version path int true "PHP 版本"
// @Success 200 {object} controllers.SuccessResponse
// @Router /plugins/php/{version}/load [get]
func (s *Service) Load(w http.ResponseWriter, r *http.Request) {
client := resty.New().SetTimeout(10 * time.Second)
resp, err := client.R().Get(fmt.Sprintf("http://127.0.0.1/phpfpm_status/%d", s.version))
if err != nil || !resp.IsSuccess() {
service.Error(w, http.StatusInternalServerError, "获取负载状态失败")
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"}
data := make([]types.NV, 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])
}
}
service.Success(w, data)
}
// ErrorLog
//
// @Summary 获取错误日志
// @Tags 插件-PHP
// @Produce json
// @Security BearerToken
// @Param version path int true "PHP 版本"
// @Success 200 {object} controllers.SuccessResponse
// @Router /plugins/php/{version}/errorLog [get]
func (s *Service) ErrorLog(w http.ResponseWriter, r *http.Request) {
log, _ := shell.Execf("tail -n 500 %s/server/php/%d/var/log/php-fpm.log", panel.Root, s.version)
service.Success(w, log)
}
// SlowLog
//
// @Summary 获取慢日志
// @Tags 插件-PHP
// @Produce json
// @Security BearerToken
// @Param version path int true "PHP 版本"
// @Success 200 {object} controllers.SuccessResponse
// @Router /plugins/php/{version}/slowLog [get]
func (s *Service) SlowLog(w http.ResponseWriter, r *http.Request) {
log, _ := shell.Execf("tail -n 500 %s/server/php/%d/var/log/slow.log", panel.Root, s.version)
service.Success(w, log)
}
// ClearErrorLog
//
// @Summary 清空错误日志
// @Tags 插件-PHP
// @Produce json
// @Security BearerToken
// @Param version path int true "PHP 版本"
// @Success 200 {object} controllers.SuccessResponse
// @Router /plugins/php/{version}/clearErrorLog [post]
func (s *Service) ClearErrorLog(w http.ResponseWriter, r *http.Request) {
if out, err := shell.Execf("echo '' > %s/server/php/%d/var/log/php-fpm.log", panel.Root, s.version); err != nil {
service.Error(w, http.StatusInternalServerError, out)
return
}
service.Success(w, nil)
}
// ClearSlowLog
//
// @Summary 清空慢日志
// @Tags 插件-PHP
// @Produce json
// @Security BearerToken
// @Param version path int true "PHP 版本"
// @Success 200 {object} controllers.SuccessResponse
// @Router /plugins/php/{version}/clearSlowLog [post]
func (s *Service) ClearSlowLog(w http.ResponseWriter, r *http.Request) {
if out, err := shell.Execf("echo '' > %s/server/php/%d/var/log/slow.log", panel.Root, s.version); err != nil {
service.Error(w, http.StatusInternalServerError, out)
return
}
service.Success(w, nil)
}
// ExtensionList
//
// @Summary 获取扩展列表
// @Tags 插件-PHP
// @Produce json
// @Security BearerToken
// @Param version path int true "PHP 版本"
// @Success 200 {object} controllers.SuccessResponse
// @Router /plugins/php/{version}/extensions [get]
func (s *Service) ExtensionList(w http.ResponseWriter, r *http.Request) {
extensions := s.getExtensions()
// ionCube 只支持 PHP 8.3 以下版本
if cast.ToUint(s.version) < 83 {
extensions = append(extensions, types.PHPExtension{
Name: "ionCube",
Slug: "ionCube Loader",
Description: "ionCube 是一个专业级的 PHP 加密解密工具。",
Installed: false,
})
}
raw, err := shell.Execf("%s/server/php/%d/bin/php -m", panel.Root, s.version)
if err != nil {
service.Error(w, http.StatusInternalServerError, err.Error())
return
}
extensionMap := make(map[string]*types.PHPExtension)
for i := range extensions {
extensionMap[extensions[i].Slug] = &extensions[i]
}
rawExtensionList := strings.Split(raw, "\n")
for _, item := range rawExtensionList {
if ext, exists := extensionMap[item]; exists && !strings.Contains(item, "[") && item != "" {
ext.Installed = true
}
}
service.Success(w, extensions)
}
// InstallExtension
//
// @Summary 安装扩展
// @Tags 插件-PHP
// @Produce json
// @Security BearerToken
// @Param version path int true "PHP 版本"
// @Param slug query string true "slug"
// @Success 200 {object} controllers.SuccessResponse
// @Router /plugins/php/{version}/extensions [post]
func (s *Service) InstallExtension(w http.ResponseWriter, r *http.Request) {
req, err := service.Bind[ExtensionSlug](r)
if err != nil {
service.Error(w, http.StatusUnprocessableEntity, err.Error())
return
}
if !s.checkExtension(req.Slug) {
service.Error(w, http.StatusUnprocessableEntity, "扩展不存在")
return
}
cmd := fmt.Sprintf(`bash '%s/panel/scripts/php_extensions/%s.sh' install %d >> '/tmp/%s.log' 2>&1`, panel.Root, req.Slug, s.version, req.Slug)
officials := []string{"fileinfo", "exif", "imap", "pdo_pgsql", "zip", "bz2", "readline", "snmp", "ldap", "enchant", "pspell", "calendar", "gmp", "sysvmsg", "sysvsem", "sysvshm", "xsl", "intl", "gettext"}
if slices.Contains(officials, req.Slug) {
cmd = fmt.Sprintf(`bash '%s/panel/scripts/php_extensions/official.sh' install '%d' '%s' >> '/tmp/%s.log' 2>&1`, panel.Root, s.version, req.Slug, req.Slug)
}
task := new(biz.Task)
task.Name = fmt.Sprintf("安装 PHP-%d 扩展 %s", s.version, req.Slug)
task.Status = biz.TaskStatusWaiting
task.Shell = cmd
task.Log = "/tmp/" + req.Slug + ".log"
if err = s.taskRepo.Push(task); err != nil {
service.Error(w, http.StatusInternalServerError, err.Error())
return
}
service.Success(w, nil)
}
// UninstallExtension
//
// @Summary 卸载扩展
// @Tags 插件-PHP
// @Produce json
// @Security BearerToken
// @Param version path int true "PHP 版本"
// @Param slug query string true "slug"
// @Success 200 {object} controllers.SuccessResponse
// @Router /plugins/php/{version}/extensions [delete]
func (s *Service) UninstallExtension(w http.ResponseWriter, r *http.Request) {
req, err := service.Bind[ExtensionSlug](r)
if err != nil {
service.Error(w, http.StatusUnprocessableEntity, err.Error())
return
}
if !s.checkExtension(req.Slug) {
service.Error(w, http.StatusUnprocessableEntity, "扩展不存在")
return
}
cmd := fmt.Sprintf(`bash '%s/panel/scripts/php_extensions/%s.sh' uninstall %d >> '/tmp/%s.log' 2>&1`, panel.Root, req.Slug, s.version, req.Slug)
officials := []string{"fileinfo", "exif", "imap", "pdo_pgsql", "zip", "bz2", "readline", "snmp", "ldap", "enchant", "pspell", "calendar", "gmp", "sysvmsg", "sysvsem", "sysvshm", "xsl", "intl", "gettext"}
if slices.Contains(officials, req.Slug) {
cmd = fmt.Sprintf(`bash '%s/panel/scripts/php_extensions/official.sh' uninstall '%d' '%s' >> '/tmp/%s.log' 2>&1`, panel.Root, s.version, req.Slug, req.Slug)
}
task := new(biz.Task)
task.Name = fmt.Sprintf("卸载 PHP-%d 扩展 %s", s.version, req.Slug)
task.Status = biz.TaskStatusWaiting
task.Shell = cmd
task.Log = "/tmp/" + req.Slug + ".log"
if err = s.taskRepo.Push(task); err != nil {
service.Error(w, http.StatusInternalServerError, err.Error())
return
}
service.Success(w, nil)
}
func (s *Service) getExtensions() []types.PHPExtension {
extensions := []types.PHPExtension{
{
Name: "fileinfo",
Slug: "fileinfo",
Description: "Fileinfo 是一个用于识别文件类型的库。",
},
{
Name: "OPcache",
Slug: "Zend OPcache",
Description: "OPcache 通过将 PHP 脚本预编译的字节码存储到共享内存中来提升 PHP 的性能,存储预编译字节码可以省去每次加载和解析 PHP 脚本的开销。",
},
{
Name: "PhpRedis",
Slug: "redis",
Description: "PhpRedis 是一个用 C 语言编写的 PHP 模块,用来连接并操作 Redis 数据库上的数据。",
},
{
Name: "ImageMagick",
Slug: "imagick",
Description: "ImageMagick 是一个免费的创建、编辑、合成图片的软件。",
},
{
Name: "exif",
Slug: "exif",
Description: "通过 exif 扩展,你可以操作图像元数据。",
},
{
Name: "pdo_pgsql",
Slug: "pdo_pgsql",
Description: "需先安装PostgreSQLpdo_pgsql 是一个驱动程序,它实现了 PHP 数据对象PDO接口以启用从 PHP 到 PostgreSQL 数据库的访问。",
},
{
Name: "imap",
Slug: "imap",
Description: "IMAP 扩展允许 PHP 读取、搜索、删除、下载和管理邮件。",
},
{
Name: "zip",
Slug: "zip",
Description: "Zip 是一个用于处理 ZIP 文件的库。",
},
{
Name: "bz2",
Slug: "bz2",
Description: "Bzip2 是一个用于压缩和解压缩文件的库。",
},
{
Name: "readline",
Slug: "readline",
Description: "Readline 是一个库,它提供了一种用于处理文本的接口。",
},
{
Name: "snmp",
Slug: "snmp",
Description: "SNMP 是一种用于网络管理的协议。",
},
{
Name: "ldap",
Slug: "ldap",
Description: "LDAP 是一种用于访问目录服务的协议。",
},
{
Name: "enchant",
Slug: "enchant",
Description: "Enchant 是一个拼写检查库。",
},
{
Name: "pspell",
Slug: "pspell",
Description: "Pspell 是一个拼写检查库。",
},
{
Name: "calendar",
Slug: "calendar",
Description: "Calendar 是一个用于处理日期的库。",
},
{
Name: "gmp",
Slug: "gmp",
Description: "GMP 是一个用于处理大整数的库。",
},
{
Name: "sysvmsg",
Slug: "sysvmsg",
Description: "Sysvmsg 是一个用于处理 System V 消息队列的库。",
},
{
Name: "sysvsem",
Slug: "sysvsem",
Description: "Sysvsem 是一个用于处理 System V 信号量的库。",
},
{
Name: "sysvshm",
Slug: "sysvshm",
Description: "Sysvshm 是一个用于处理 System V 共享内存的库。",
},
{
Name: "xsl",
Slug: "xsl",
Description: "XSL 是一个用于处理 XML 文档的库。",
},
{
Name: "intl",
Slug: "intl",
Description: "Intl 是一个用于处理国际化和本地化的库。",
},
{
Name: "gettext",
Slug: "gettext",
Description: "Gettext 是一个用于处理多语言的库。",
},
{
Name: "igbinary",
Slug: "igbinary",
Description: "Igbinary 是一个用于序列化和反序列化数据的库。",
},
{
Name: "Swoole",
Slug: "swoole",
Description: "Swoole 是一个用于构建高性能的异步并发服务器的 PHP 扩展。",
},
{
Name: "Swow",
Slug: "Swow",
Description: "Swow 是一个用于构建高性能的异步并发服务器的 PHP 扩展。",
},
}
// ionCube 只支持 PHP 8.3 以下版本
if cast.ToUint(s.version) < 83 {
extensions = append(extensions, types.PHPExtension{
Name: "ionCube",
Slug: "ionCube Loader",
Description: "ionCube 是一个专业级的 PHP 加密解密工具。",
Installed: false,
})
}
raw, _ := shell.Execf("%s/server/php/%d/bin/php -m", panel.Root, s.version)
extensionMap := make(map[string]*types.PHPExtension)
for i := range extensions {
extensionMap[extensions[i].Slug] = &extensions[i]
}
rawExtensionList := strings.Split(raw, "\n")
for _, item := range rawExtensionList {
if ext, exists := extensionMap[item]; exists && !strings.Contains(item, "[") && item != "" {
ext.Installed = true
}
}
return extensions
}
func (s *Service) checkExtension(slug string) bool {
extensions := s.getExtensions()
for _, item := range extensions {
if item.Slug == slug {
return true
}
}
return false
}

View File

@@ -0,0 +1,21 @@
package phpmyadmin
import (
"github.com/go-chi/chi/v5"
"github.com/TheTNB/panel/pkg/apploader"
"github.com/TheTNB/panel/pkg/types"
)
func init() {
apploader.Register(&types.App{
Slug: "phpmyadmin",
Route: func(r chi.Router) {
service := NewService()
r.Get("/info", service.Info)
r.Post("/port", service.UpdatePort)
r.Get("/config", service.GetConfig)
r.Post("/config", service.UpdateConfig)
},
})
}

View File

@@ -0,0 +1,9 @@
package phpmyadmin
type UpdateConfig struct {
Config string `form:"config" json:"config"`
}
type UpdatePort struct {
Port uint `form:"port" json:"port"`
}

View File

@@ -0,0 +1,127 @@
package phpmyadmin
import (
"fmt"
"net/http"
"regexp"
"strings"
"github.com/go-rat/chix"
"github.com/spf13/cast"
"github.com/TheTNB/panel/internal/panel"
"github.com/TheTNB/panel/internal/service"
"github.com/TheTNB/panel/pkg/firewall"
"github.com/TheTNB/panel/pkg/io"
"github.com/TheTNB/panel/pkg/shell"
"github.com/TheTNB/panel/pkg/systemctl"
)
type Service struct{}
func NewService() *Service {
return &Service{}
}
func (s *Service) Info(w http.ResponseWriter, r *http.Request) {
files, err := io.ReadDir(fmt.Sprintf("%s/server/phpmyadmin", panel.Root))
if err != nil {
service.Error(w, http.StatusInternalServerError, "找不到 phpMyAdmin 目录")
return
}
var phpmyadmin string
for _, f := range files {
if strings.HasPrefix(f.Name(), "phpmyadmin_") {
phpmyadmin = f.Name()
}
}
if len(phpmyadmin) == 0 {
service.Error(w, http.StatusInternalServerError, "找不到 phpMyAdmin 目录")
return
}
conf, err := io.Read(fmt.Sprintf("%s/server/vhost/phpmyadmin.conf", panel.Root))
if err != nil {
service.Error(w, http.StatusInternalServerError, err.Error())
return
}
match := regexp.MustCompile(`listen\s+(\d+);`).FindStringSubmatch(conf)
if len(match) == 0 {
service.Error(w, http.StatusInternalServerError, "找不到 phpMyAdmin 端口")
return
}
service.Success(w, chix.M{
"path": phpmyadmin,
"port": cast.ToInt(match[1]),
})
}
func (s *Service) UpdatePort(w http.ResponseWriter, r *http.Request) {
req, err := service.Bind[UpdatePort](r)
if err != nil {
service.Error(w, http.StatusUnprocessableEntity, err.Error())
return
}
conf, err := io.Read(fmt.Sprintf("%s/server/vhost/phpmyadmin.conf", panel.Root))
if err != nil {
service.Error(w, http.StatusInternalServerError, err.Error())
return
}
conf = regexp.MustCompile(`listen\s+(\d+);`).ReplaceAllString(conf, "listen "+cast.ToString(req.Port)+";")
if err = io.Write(fmt.Sprintf("%s/server/vhost/phpmyadmin.conf", panel.Root), conf, 0644); err != nil {
service.ErrorSystem(w)
return
}
fw := firewall.NewFirewall()
err = fw.Port(firewall.FireInfo{
Port: req.Port,
Protocol: "tcp",
}, firewall.OperationAdd)
if err != nil {
service.Error(w, http.StatusInternalServerError, err.Error())
return
}
if err = systemctl.Reload("openresty"); err != nil {
_, err = shell.Execf("openresty -t")
service.Error(w, http.StatusInternalServerError, fmt.Sprintf("重载OpenResty失败: %v", err))
return
}
service.Success(w, nil)
}
func (s *Service) GetConfig(w http.ResponseWriter, r *http.Request) {
config, err := io.Read(fmt.Sprintf("%s/server/vhost/phpmyadmin.conf", panel.Root))
if err != nil {
service.Error(w, http.StatusInternalServerError, err.Error())
return
}
service.Success(w, config)
}
func (s *Service) UpdateConfig(w http.ResponseWriter, r *http.Request) {
req, err := service.Bind[UpdateConfig](r)
if err != nil {
service.Error(w, http.StatusUnprocessableEntity, err.Error())
return
}
if err = io.Write(fmt.Sprintf("%s/server/vhost/phpmyadmin.conf", panel.Root), req.Config, 0644); err != nil {
service.Error(w, http.StatusInternalServerError, err.Error())
return
}
if err = systemctl.Reload("openresty"); err != nil {
_, err = shell.Execf("openresty -t")
service.Error(w, http.StatusInternalServerError, fmt.Sprintf("重载OpenResty失败: %v", err))
return
}
service.Success(w, nil)
}

View File

@@ -9,17 +9,17 @@ import (
type SettingKey string
const (
SettingKeyName SettingKey = "name"
SettingKeyVersion SettingKey = "version"
SettingKeyMonitor SettingKey = "monitor"
SettingKeyMonitorDays SettingKey = "monitor_days"
SettingKeyBackupPath SettingKey = "backup_path"
SettingKeyWebsitePath SettingKey = "website_path"
SettingKeyMysqlRootPassword SettingKey = "mysql_root_password"
SettingKeySshHost SettingKey = "ssh_host"
SettingKeySshPort SettingKey = "ssh_port"
SettingKeySshUser SettingKey = "ssh_user"
SettingKeySshPassword SettingKey = "ssh_password"
SettingKeyName SettingKey = "name"
SettingKeyVersion SettingKey = "version"
SettingKeyMonitor SettingKey = "monitor"
SettingKeyMonitorDays SettingKey = "monitor_days"
SettingKeyBackupPath SettingKey = "backup_path"
SettingKeyWebsitePath SettingKey = "website_path"
SettingKeyPerconaRootPassword SettingKey = "percona_root_password"
SettingKeySshHost SettingKey = "ssh_host"
SettingKeySshPort SettingKey = "ssh_port"
SettingKeySshUser SettingKey = "ssh_user"
SettingKeySshPassword SettingKey = "ssh_password"
)
type Setting struct {

View File

@@ -27,4 +27,5 @@ type TaskRepo interface {
Get(id uint) (*Task, error)
Delete(id uint) error
UpdateStatus(id uint, status TaskStatus) error
Push(task *Task) error
}

View File

@@ -7,7 +7,6 @@ import (
"slices"
"github.com/TheTNB/panel/internal/biz"
"github.com/TheTNB/panel/internal/job"
"github.com/TheTNB/panel/internal/panel"
"github.com/TheTNB/panel/pkg/api"
"github.com/TheTNB/panel/pkg/apploader"
@@ -145,13 +144,9 @@ func (r *appRepo) Install(slug string) error {
task.Status = biz.TaskStatusWaiting
task.Shell = fmt.Sprintf("%s >> /tmp/%s.log 2>&1", shellUrl, app.Slug)
task.Log = "/tmp/" + app.Slug + ".log"
if err = panel.Orm.Create(task).Error; err != nil {
if err = r.taskRepo.Push(task); err != nil {
return err
}
err = panel.Queue.Push(job.NewProcessTask(r.taskRepo), []any{
task.ID,
})
return err
}
@@ -189,13 +184,9 @@ func (r *appRepo) Uninstall(slug string) error {
task.Status = biz.TaskStatusWaiting
task.Shell = fmt.Sprintf("%s >> /tmp/%s.log 2>&1", shellUrl, app.Slug)
task.Log = "/tmp/" + app.Slug + ".log"
if err = panel.Orm.Create(task).Error; err != nil {
if err = r.taskRepo.Push(task); err != nil {
return err
}
err = panel.Queue.Push(job.NewProcessTask(r.taskRepo), []any{
task.ID,
})
return err
}
@@ -230,13 +221,9 @@ func (r *appRepo) Update(slug string) error {
task.Status = biz.TaskStatusWaiting
task.Shell = fmt.Sprintf("%s >> /tmp/%s.log 2>&1", shellUrl, app.Slug)
task.Log = "/tmp/" + app.Slug + ".log"
if err = panel.Orm.Create(task).Error; err != nil {
if err = r.taskRepo.Push(task); err != nil {
return err
}
err = panel.Queue.Push(job.NewProcessTask(r.taskRepo), []any{
task.ID,
})
return err
}

View File

@@ -171,7 +171,7 @@ func (r *certRepo) Renew(id uint) (*acme.Certificate, error) {
return nil, errors.New("通配符域名无法使用 HTTP 验证")
}
}
conf := fmt.Sprintf("/www/server/vhost/acme/%s.conf", cert.Website.Name)
conf := fmt.Sprintf("%s/server/vhost/acme/%s.conf", panel.Root, cert.Website.Name)
client.UseHTTP(conf, cert.Website.Path)
}
}

View File

@@ -2,6 +2,7 @@ package data
import (
"github.com/TheTNB/panel/internal/biz"
"github.com/TheTNB/panel/internal/job"
"github.com/TheTNB/panel/internal/panel"
)
@@ -37,3 +38,12 @@ func (r *taskRepo) Delete(id uint) error {
func (r *taskRepo) UpdateStatus(id uint, status biz.TaskStatus) error {
return panel.Orm.Model(&biz.Task{}).Where("id = ?", id).Update("status", status).Error
}
func (r *taskRepo) Push(task *biz.Task) error {
if err := panel.Orm.Create(task).Error; err != nil {
return err
}
return panel.Queue.Push(job.NewProcessTask(r), []any{
task.ID,
})
}

View File

@@ -332,7 +332,7 @@ server
return nil, err
}
rootPassword, err := r.settingRepo.Get(biz.SettingKeyMysqlRootPassword)
rootPassword, err := r.settingRepo.Get(biz.SettingKeyPerconaRootPassword)
if err == nil && req.DB && req.DBType == "mysql" {
mysql, err := db.NewMySQL("root", rootPassword, "/tmp/mysql.sock", "unix")
if err != nil {
@@ -610,7 +610,7 @@ func (r *websiteRepo) Delete(req *request.WebsiteDelete) error {
_ = io.Remove(website.Path)
}
if req.DB {
rootPassword, err := r.settingRepo.Get(biz.SettingKeyMysqlRootPassword)
rootPassword, err := r.settingRepo.Get(biz.SettingKeyPerconaRootPassword)
if err != nil {
return err
}

View File

@@ -127,7 +127,7 @@ func (s *InfoService) CountInfo(w http.ResponseWriter, r *http.Request) {
}
var databaseCount int64
if mysqlInstalled {
rootPassword, _ := s.settingRepo.Get(biz.SettingKeyMysqlRootPassword)
rootPassword, _ := s.settingRepo.Get(biz.SettingKeyPerconaRootPassword)
mysql, err := db.NewMySQL("root", rootPassword, "/tmp/mysql.sock")
if err == nil {
defer mysql.Close()
@@ -157,7 +157,7 @@ func (s *InfoService) CountInfo(w http.ResponseWriter, r *http.Request) {
}
}
if postgresqlInstalled {
postgres, err := db.NewPostgres("postgres", "", "127.0.0.1", 5432)
postgres, err := db.NewPostgres("postgres", "", "127.0.0.1", fmt.Sprintf("%s/server/postgresql/data/pg_hba.conf", panel.Root), 5432)
if err == nil {
defer postgres.Close()
if err = postgres.Ping(); err != nil {

View File

@@ -17,10 +17,11 @@ type Postgres struct {
username string
password string
address string
hbaFile string
port uint
}
func NewPostgres(username, password, address string, port uint) (*Postgres, error) {
func NewPostgres(username, password, address, hbaFile string, port uint) (*Postgres, error) {
dsn := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=postgres sslmode=disable", address, port, username, password)
if password == "" {
dsn = fmt.Sprintf("host=%s port=%d user=%s dbname=postgres sslmode=disable", address, port, username)
@@ -37,6 +38,7 @@ func NewPostgres(username, password, address string, port uint) (*Postgres, erro
username: username,
password: password,
address: address,
hbaFile: hbaFile,
port: port,
}, nil
}
@@ -90,7 +92,7 @@ func (m *Postgres) UserDrop(user string) error {
return err
}
_, _ = shell.Execf(`sed -i '/%s/d' /www/server/postgresql/data/pg_hba.conf`, user)
_, _ = shell.Execf(`sed -i '/%s/d' %s`, user, m.hbaFile)
return systemctl.Reload("postgresql")
}
@@ -117,7 +119,7 @@ func (m *Postgres) PrivilegesRevoke(user, database string) error {
func (m *Postgres) HostAdd(database, user, host string) error {
config := fmt.Sprintf("host %s %s %s scram-sha-256", database, user, host)
if err := io.WriteAppend("/www/server/postgresql/data/pg_hba.conf", config); err != nil {
if err := io.WriteAppend(m.hbaFile, config); err != nil {
return err
}
@@ -126,7 +128,7 @@ func (m *Postgres) HostAdd(database, user, host string) error {
func (m *Postgres) HostRemove(database, user, host string) error {
regex := fmt.Sprintf(`host\s+%s\s+%s\s+%s`, database, user, host)
if _, err := shell.Execf(`sed -i '/%s/d' /www/server/postgresql/data/pg_hba.conf`, regex); err != nil {
if _, err := shell.Execf(`sed -i '/%s/d' %s`, regex, m.hbaFile); err != nil {
return err
}

View File

@@ -13,6 +13,13 @@ import (
"github.com/TheTNB/panel/pkg/systemctl"
)
type Operation string
var (
OperationAdd Operation = "add"
OperationDel Operation = "remove"
)
type Firewall struct {
forwardListRegex *regexp.Regexp
richRuleRegex *regexp.Regexp
@@ -125,7 +132,7 @@ func (r *Firewall) ListRichRule() ([]FireInfo, error) {
return data, nil
}
func (r *Firewall) Port(port FireInfo, operation string) error {
func (r *Firewall) Port(port FireInfo, operation Operation) error {
stdout, err := shell.Execf("firewall-cmd --zone=public --%s-port=%d/%s --permanent", operation, port.Port, port.Protocol)
if err != nil {
return fmt.Errorf("%s port %d/%s failed, err: %s", operation, port.Port, port.Protocol, stdout)
@@ -135,7 +142,7 @@ func (r *Firewall) Port(port FireInfo, operation string) error {
return err
}
func (r *Firewall) RichRules(rule FireInfo, operation string) error {
func (r *Firewall) RichRules(rule FireInfo, operation Operation) error {
families := strings.Split(rule.Family, "/") // ipv4 ipv6
for _, family := range families {
@@ -162,7 +169,7 @@ func (r *Firewall) RichRules(rule FireInfo, operation string) error {
return err
}
func (r *Firewall) PortForward(info Forward, operation string) error {
func (r *Firewall) PortForward(info Forward, operation Operation) error {
if err := r.enableForward(); err != nil {
return err
}