mirror of
https://github.com/acepanel/panel.git
synced 2026-02-04 06:47:20 +08:00
feat: 提交一批插件
This commit is contained in:
4
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
4
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -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
|
||||
|
||||
@@ -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
19
internal/apps/frp/init.go
Normal 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)
|
||||
},
|
||||
})
|
||||
}
|
||||
10
internal/apps/frp/request.go
Normal file
10
internal/apps/frp/request.go
Normal 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"`
|
||||
}
|
||||
73
internal/apps/frp/service.go
Normal file
73
internal/apps/frp/service.go
Normal 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)
|
||||
}
|
||||
19
internal/apps/gitea/init.go
Normal file
19
internal/apps/gitea/init.go
Normal 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)
|
||||
},
|
||||
})
|
||||
}
|
||||
5
internal/apps/gitea/request.go
Normal file
5
internal/apps/gitea/request.go
Normal file
@@ -0,0 +1,5 @@
|
||||
package gitea
|
||||
|
||||
type UpdateConfig struct {
|
||||
Config string `form:"config" json:"config"`
|
||||
}
|
||||
64
internal/apps/gitea/service.go
Normal file
64
internal/apps/gitea/service.go
Normal 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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
26
internal/apps/percona/init.go
Normal file
26
internal/apps/percona/init.go
Normal 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)
|
||||
},
|
||||
})
|
||||
}
|
||||
9
internal/apps/percona/request.go
Normal file
9
internal/apps/percona/request.go
Normal 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"`
|
||||
}
|
||||
248
internal/apps/percona/service.go
Normal file
248
internal/apps/percona/service.go
Normal 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
83
internal/apps/php/init.go
Normal 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)
|
||||
},
|
||||
})
|
||||
}
|
||||
9
internal/apps/php/request.go
Normal file
9
internal/apps/php/request.go
Normal 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"`
|
||||
}
|
||||
513
internal/apps/php/service.go
Normal file
513
internal/apps/php/service.go
Normal 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: "(需先安装PostgreSQL)pdo_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
|
||||
}
|
||||
21
internal/apps/phpmyadmin/init.go
Normal file
21
internal/apps/phpmyadmin/init.go
Normal 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)
|
||||
},
|
||||
})
|
||||
}
|
||||
9
internal/apps/phpmyadmin/request.go
Normal file
9
internal/apps/phpmyadmin/request.go
Normal 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"`
|
||||
}
|
||||
127
internal/apps/phpmyadmin/service.go
Normal file
127
internal/apps/phpmyadmin/service.go
Normal 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)
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user