mirror of
https://github.com/acepanel/panel.git
synced 2026-02-04 22:07:16 +08:00
feat: 测试插件
This commit is contained in:
@@ -25,6 +25,7 @@ type WebsiteRepo interface {
|
||||
UpdateDefaultConfig(req *request.WebsiteDefaultConfig) error
|
||||
Count() (int64, error)
|
||||
Get(id uint) (*types.WebsiteSetting, error)
|
||||
GetByName(name string) (*types.WebsiteSetting, error)
|
||||
List(page, limit uint) ([]*Website, int64, error)
|
||||
Create(req *request.WebsiteCreate) (*Website, error)
|
||||
Update(req *request.WebsiteUpdate) error
|
||||
|
||||
@@ -163,6 +163,16 @@ func (r *websiteRepo) Get(id uint) (*types.WebsiteSetting, error) {
|
||||
return setting, err
|
||||
}
|
||||
|
||||
func (r *websiteRepo) GetByName(name string) (*types.WebsiteSetting, error) {
|
||||
website := new(biz.Website)
|
||||
if err := app.Orm.Where("name", name).First(website).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return r.Get(website.ID)
|
||||
|
||||
}
|
||||
|
||||
func (r *websiteRepo) List(page, limit uint) ([]*biz.Website, int64, error) {
|
||||
var websites []*biz.Website
|
||||
var total int64
|
||||
|
||||
32
internal/plugin/fail2ban/init.go
Normal file
32
internal/plugin/fail2ban/init.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package fail2ban
|
||||
|
||||
import (
|
||||
"github.com/go-chi/chi/v5"
|
||||
|
||||
"github.com/TheTNB/panel/pkg/pluginloader"
|
||||
"github.com/TheTNB/panel/pkg/types"
|
||||
)
|
||||
|
||||
func init() {
|
||||
pluginloader.Register(&types.Plugin{
|
||||
Slug: "fail2ban",
|
||||
Name: "Fail2ban",
|
||||
Description: "Fail2ban 扫描系统日志文件并从中找出多次尝试失败的IP地址,将该IP地址加入防火墙的拒绝访问列表中",
|
||||
Version: "1.0.2",
|
||||
Requires: []string{},
|
||||
Excludes: []string{},
|
||||
Install: `bash /www/panel/scripts/fail2ban/install.sh`,
|
||||
Uninstall: `bash /www/panel/scripts/fail2ban/uninstall.sh`,
|
||||
Update: `bash /www/panel/scripts/fail2ban/update.sh`,
|
||||
Route: func(r chi.Router) {
|
||||
service := NewService()
|
||||
r.Get("/jails", service.List)
|
||||
r.Post("/jails", service.Add)
|
||||
r.Delete("/jails", service.Delete)
|
||||
r.Get("/jails/{name}", service.BanList)
|
||||
r.Post("/unban", service.Unban)
|
||||
r.Post("/whiteList", service.SetWhiteList)
|
||||
r.Get("/whiteList", service.GetWhiteList)
|
||||
},
|
||||
})
|
||||
}
|
||||
29
internal/plugin/fail2ban/request.go
Normal file
29
internal/plugin/fail2ban/request.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package fail2ban
|
||||
|
||||
type Add struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
MaxRetry string `json:"maxretry"`
|
||||
FindTime string `json:"findtime"`
|
||||
BanTime string `json:"bantime"`
|
||||
WebsiteName string `json:"website_name"`
|
||||
WebsiteMode string `json:"website_mode"`
|
||||
WebsitePath string `json:"website_path"`
|
||||
}
|
||||
|
||||
type Delete struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type BanList struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type Unban struct {
|
||||
Name string `json:"name"`
|
||||
IP string `json:"ip"`
|
||||
}
|
||||
|
||||
type SetWhiteList struct {
|
||||
IP string `json:"ip"`
|
||||
}
|
||||
361
internal/plugin/fail2ban/service.go
Normal file
361
internal/plugin/fail2ban/service.go
Normal file
@@ -0,0 +1,361 @@
|
||||
package fail2ban
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/go-rat/chix"
|
||||
"github.com/spf13/cast"
|
||||
|
||||
"github.com/TheTNB/panel/internal/biz"
|
||||
"github.com/TheTNB/panel/internal/data"
|
||||
"github.com/TheTNB/panel/internal/service"
|
||||
"github.com/TheTNB/panel/pkg/io"
|
||||
"github.com/TheTNB/panel/pkg/os"
|
||||
"github.com/TheTNB/panel/pkg/shell"
|
||||
"github.com/TheTNB/panel/pkg/str"
|
||||
"github.com/TheTNB/panel/pkg/types"
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
websiteRepo biz.WebsiteRepo
|
||||
}
|
||||
|
||||
func NewService() *Service {
|
||||
return &Service{
|
||||
websiteRepo: data.NewWebsiteRepo(),
|
||||
}
|
||||
}
|
||||
|
||||
// List 所有 Fail2ban 规则
|
||||
func (s *Service) List(w http.ResponseWriter, r *http.Request) {
|
||||
raw, err := io.Read("/etc/fail2ban/jail.local")
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusUnprocessableEntity, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
jailList := regexp.MustCompile(`\[(.*?)]`).FindAllStringSubmatch(raw, -1)
|
||||
if len(jailList) == 0 {
|
||||
service.Error(w, http.StatusUnprocessableEntity, "Fail2ban 规则为空")
|
||||
return
|
||||
}
|
||||
|
||||
var jails []types.Fail2banJail
|
||||
for i, jail := range jailList {
|
||||
if i == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
jailName := jail[1]
|
||||
jailRaw := str.Cut(raw, "# "+jailName+"-START", "# "+jailName+"-END")
|
||||
if len(jailRaw) == 0 {
|
||||
continue
|
||||
}
|
||||
jailEnabled := strings.Contains(jailRaw, "enabled = true")
|
||||
jailLogPath := regexp.MustCompile(`logpath = (.*)`).FindStringSubmatch(jailRaw)
|
||||
jailMaxRetry := regexp.MustCompile(`maxretry = (.*)`).FindStringSubmatch(jailRaw)
|
||||
jailFindTime := regexp.MustCompile(`findtime = (.*)`).FindStringSubmatch(jailRaw)
|
||||
jailBanTime := regexp.MustCompile(`bantime = (.*)`).FindStringSubmatch(jailRaw)
|
||||
|
||||
jails = append(jails, types.Fail2banJail{
|
||||
Name: jailName,
|
||||
Enabled: jailEnabled,
|
||||
LogPath: jailLogPath[1],
|
||||
MaxRetry: cast.ToInt(jailMaxRetry[1]),
|
||||
FindTime: cast.ToInt(jailFindTime[1]),
|
||||
BanTime: cast.ToInt(jailBanTime[1]),
|
||||
})
|
||||
}
|
||||
|
||||
paged, total := service.Paginate(r, jails)
|
||||
|
||||
service.Success(w, chix.M{
|
||||
"total": total,
|
||||
"items": paged,
|
||||
})
|
||||
}
|
||||
|
||||
// Add 添加 Fail2ban 规则
|
||||
func (s *Service) Add(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := service.Bind[Add](r)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusUnprocessableEntity, err.Error())
|
||||
return
|
||||
}
|
||||
jailName := req.Name
|
||||
jailType := req.Type
|
||||
jailMaxRetry := req.MaxRetry
|
||||
jailFindTime := req.FindTime
|
||||
jailBanTime := req.BanTime
|
||||
jailWebsiteName := req.WebsiteName
|
||||
jailWebsiteMode := req.WebsiteMode
|
||||
jailWebsitePath := req.WebsitePath
|
||||
|
||||
raw, err := io.Read("/etc/fail2ban/jail.local")
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusUnprocessableEntity, err.Error())
|
||||
return
|
||||
}
|
||||
if (strings.Contains(raw, "["+jailName+"]") && jailType == "service") || (strings.Contains(raw, "["+jailWebsiteName+"]"+"-cc") && jailType == "website" && jailWebsiteMode == "cc") || (strings.Contains(raw, "["+jailWebsiteName+"]"+"-path") && jailType == "website" && jailWebsiteMode == "path") {
|
||||
service.Error(w, http.StatusUnprocessableEntity, "规则已存在")
|
||||
return
|
||||
}
|
||||
|
||||
switch jailType {
|
||||
case "website":
|
||||
website, err := s.websiteRepo.GetByName(jailWebsiteName)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusUnprocessableEntity, fmt.Sprintf("获取网站配置失败:%s", err))
|
||||
return
|
||||
}
|
||||
var ports string
|
||||
for _, port := range website.Ports {
|
||||
fields := strings.Fields(cast.ToString(port))
|
||||
ports += fields[0] + ","
|
||||
}
|
||||
|
||||
rule := `
|
||||
# ` + jailWebsiteName + `-` + jailWebsiteMode + `-START
|
||||
[` + jailWebsiteName + `-` + jailWebsiteMode + `]
|
||||
enabled = true
|
||||
filter = haozi-` + jailWebsiteName + `-` + jailWebsiteMode + `
|
||||
port = ` + ports + `
|
||||
maxretry = ` + jailMaxRetry + `
|
||||
findtime = ` + jailFindTime + `
|
||||
bantime = ` + jailBanTime + `
|
||||
action = %(action_mwl)s
|
||||
logpath = /www/wwwlogs/` + website.Name + `.log
|
||||
# ` + jailWebsiteName + `-` + jailWebsiteMode + `-END
|
||||
`
|
||||
raw += rule
|
||||
if err = io.Write("/etc/fail2ban/jail.local", raw, 0644); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "写入Fail2ban规则失败")
|
||||
return
|
||||
}
|
||||
|
||||
var filter string
|
||||
if jailWebsiteMode == "cc" {
|
||||
filter = `
|
||||
[Definition]
|
||||
failregex = ^<HOST>\s-.*HTTP/.*$
|
||||
ignoreregex =
|
||||
`
|
||||
} else {
|
||||
filter = `
|
||||
[Definition]
|
||||
failregex = ^<HOST>\s-.*\s` + jailWebsitePath + `.*HTTP/.*$
|
||||
ignoreregex =
|
||||
`
|
||||
}
|
||||
if err = io.Write("/etc/fail2ban/filter.d/haozi-"+jailWebsiteName+"-"+jailWebsiteMode+".conf", filter, 0644); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "写入Fail2ban规则失败")
|
||||
return
|
||||
}
|
||||
|
||||
case "service":
|
||||
var logPath string
|
||||
var filter string
|
||||
var port string
|
||||
var err error
|
||||
switch jailName {
|
||||
case "ssh":
|
||||
if os.IsDebian() || os.IsUbuntu() {
|
||||
logPath = "/var/log/auth.log"
|
||||
} else {
|
||||
logPath = "/var/log/secure"
|
||||
}
|
||||
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"
|
||||
filter = "mysqld-auth"
|
||||
port, err = shell.Execf("cat /www/server/mysql/conf/my.cnf | grep 'port' | head -n 1 | awk '{print $3}'")
|
||||
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}'`)
|
||||
default:
|
||||
service.Error(w, http.StatusUnprocessableEntity, "未知服务")
|
||||
return
|
||||
}
|
||||
if len(port) == 0 || err != nil {
|
||||
service.Error(w, http.StatusUnprocessableEntity, "获取服务端口失败,请检查是否安装")
|
||||
return
|
||||
}
|
||||
|
||||
rule := `
|
||||
# ` + jailName + `-START
|
||||
[` + jailName + `]
|
||||
enabled = true
|
||||
filter = ` + filter + `
|
||||
port = ` + port + `
|
||||
maxretry = ` + jailMaxRetry + `
|
||||
findtime = ` + jailFindTime + `
|
||||
bantime = ` + jailBanTime + `
|
||||
action = %(action_mwl)s
|
||||
logpath = ` + logPath + `
|
||||
# ` + jailName + `-END
|
||||
`
|
||||
raw += rule
|
||||
if err := io.Write("/etc/fail2ban/jail.local", raw, 0644); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "写入Fail2ban规则失败")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := shell.Execf("fail2ban-client reload"); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "重载配置失败")
|
||||
return
|
||||
}
|
||||
|
||||
service.Success(w, nil)
|
||||
}
|
||||
|
||||
// Delete 删除规则
|
||||
func (s *Service) Delete(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := service.Bind[Delete](r)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusUnprocessableEntity, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
raw, err := io.Read("/etc/fail2ban/jail.local")
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusUnprocessableEntity, err.Error())
|
||||
return
|
||||
}
|
||||
if !strings.Contains(raw, "["+req.Name+"]") {
|
||||
service.Error(w, http.StatusUnprocessableEntity, "规则不存在")
|
||||
return
|
||||
}
|
||||
|
||||
rule := str.Cut(raw, "# "+req.Name+"-START", "# "+req.Name+"-END")
|
||||
raw = strings.Replace(raw, "\n# "+req.Name+"-START"+rule+"# "+req.Name+"-END", "", -1)
|
||||
raw = strings.TrimSpace(raw)
|
||||
if err := io.Write("/etc/fail2ban/jail.local", raw, 0644); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "写入Fail2ban规则失败")
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := shell.Execf("fail2ban-client reload"); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "重载配置失败")
|
||||
return
|
||||
}
|
||||
|
||||
service.Success(w, nil)
|
||||
}
|
||||
|
||||
// BanList 获取封禁列表
|
||||
func (s *Service) BanList(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := service.Bind[BanList](r)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusUnprocessableEntity, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
currentlyBan, err := shell.Execf(`fail2ban-client status %s | grep "Currently banned" | awk '{print $4}'`, req.Name)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "获取封禁列表失败")
|
||||
return
|
||||
}
|
||||
totalBan, err := shell.Execf(`fail2ban-client status %s | grep "Total banned" | awk '{print $4}'`, req.Name)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "获取封禁列表失败")
|
||||
return
|
||||
}
|
||||
bannedIp, err := shell.Execf(`fail2ban-client status %s | grep "Banned IP list" | awk -F ":" '{print $2}'`, req.Name)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "获取封禁列表失败")
|
||||
return
|
||||
}
|
||||
bannedIpList := strings.Split(bannedIp, " ")
|
||||
|
||||
var list []map[string]string
|
||||
for _, ip := range bannedIpList {
|
||||
if len(ip) > 0 {
|
||||
list = append(list, map[string]string{
|
||||
"name": req.Name,
|
||||
"ip": ip,
|
||||
})
|
||||
}
|
||||
}
|
||||
if list == nil {
|
||||
list = []map[string]string{}
|
||||
}
|
||||
|
||||
service.Success(w, chix.M{
|
||||
"currently_ban": currentlyBan,
|
||||
"total_ban": totalBan,
|
||||
"baned_list": list,
|
||||
})
|
||||
}
|
||||
|
||||
// Unban 解封
|
||||
func (s *Service) Unban(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := service.Bind[Unban](r)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusUnprocessableEntity, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if _, err = shell.Execf("fail2ban-client set %s unbanip %s", req.Name, req.IP); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "解封失败")
|
||||
return
|
||||
}
|
||||
|
||||
service.Success(w, nil)
|
||||
}
|
||||
|
||||
// SetWhiteList 设置白名单
|
||||
func (s *Service) SetWhiteList(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := service.Bind[SetWhiteList](r)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusUnprocessableEntity, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
raw, err := io.Read("/etc/fail2ban/jail.local")
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusUnprocessableEntity, err.Error())
|
||||
return
|
||||
}
|
||||
// 正则替换
|
||||
reg := regexp.MustCompile(`ignoreip\s*=\s*.*\n`)
|
||||
if reg.MatchString(raw) {
|
||||
raw = reg.ReplaceAllString(raw, "ignoreip = "+req.IP+"\n")
|
||||
} else {
|
||||
service.Error(w, http.StatusInternalServerError, "解析Fail2ban规则失败,Fail2ban可能已损坏")
|
||||
return
|
||||
}
|
||||
|
||||
if err = io.Write("/etc/fail2ban/jail.local", raw, 0644); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "写入Fail2ban规则失败")
|
||||
return
|
||||
}
|
||||
|
||||
if _, err = shell.Execf("fail2ban-client reload"); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "重载配置失败")
|
||||
return
|
||||
}
|
||||
service.Success(w, nil)
|
||||
}
|
||||
|
||||
// GetWhiteList 获取白名单
|
||||
func (s *Service) GetWhiteList(w http.ResponseWriter, r *http.Request) {
|
||||
raw, err := io.Read("/etc/fail2ban/jail.local")
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusUnprocessableEntity, err.Error())
|
||||
return
|
||||
}
|
||||
reg := regexp.MustCompile(`ignoreip\s*=\s*(.*)\n`)
|
||||
if reg.MatchString(raw) {
|
||||
ignoreIp := reg.FindStringSubmatch(raw)[1]
|
||||
service.Success(w, ignoreIp)
|
||||
} else {
|
||||
service.Error(w, http.StatusInternalServerError, "解析Fail2ban规则失败,Fail2ban可能已损坏")
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package plugin
|
||||
import (
|
||||
"github.com/go-chi/chi/v5"
|
||||
|
||||
_ "github.com/TheTNB/panel/internal/plugin/fail2ban"
|
||||
_ "github.com/TheTNB/panel/internal/plugin/openresty"
|
||||
"github.com/TheTNB/panel/pkg/pluginloader"
|
||||
)
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package openresty
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
|
||||
"github.com/TheTNB/panel/pkg/pluginloader"
|
||||
@@ -11,12 +9,23 @@ import (
|
||||
|
||||
func init() {
|
||||
pluginloader.Register(&types.Plugin{
|
||||
Slug: "openresty",
|
||||
Name: "OpenResty",
|
||||
Order: -100,
|
||||
Slug: "openresty",
|
||||
Name: "OpenResty",
|
||||
Description: "OpenResty® 是一款基于 NGINX 和 LuaJIT 的 Web 平台",
|
||||
Version: "1.25.3.1",
|
||||
Requires: []string{},
|
||||
Excludes: []string{},
|
||||
Install: "bash /www/panel/scripts/openresty/install.sh",
|
||||
Uninstall: "bash /www/panel/scripts/openresty/uninstall.sh",
|
||||
Update: "bash /www/panel/scripts/openresty/install.sh",
|
||||
Route: func(r chi.Router) {
|
||||
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
_, _ = w.Write([]byte("Hello, World!"))
|
||||
})
|
||||
service := NewService()
|
||||
r.Get("/load", service.Load)
|
||||
r.Get("/config", service.GetConfig)
|
||||
r.Post("/config", service.SaveConfig)
|
||||
r.Get("/errorLog", service.ErrorLog)
|
||||
r.Post("/clearErrorLog", service.ClearErrorLog)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1 +1,188 @@
|
||||
package openresty
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"time"
|
||||
|
||||
"github.com/go-resty/resty/v2"
|
||||
"github.com/spf13/cast"
|
||||
|
||||
"github.com/TheTNB/panel/internal/service"
|
||||
"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 {
|
||||
// Dependent services
|
||||
}
|
||||
|
||||
func NewService() *Service {
|
||||
return &Service{}
|
||||
}
|
||||
|
||||
// GetConfig
|
||||
//
|
||||
// @Summary 获取配置
|
||||
// @Tags 插件-OpenResty
|
||||
// @Produce json
|
||||
// @Security BearerToken
|
||||
// @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")
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "获取配置失败")
|
||||
return
|
||||
}
|
||||
|
||||
service.Success(w, config)
|
||||
}
|
||||
|
||||
// SaveConfig
|
||||
//
|
||||
// @Summary 保存配置
|
||||
// @Tags 插件-OpenResty
|
||||
// @Produce json
|
||||
// @Security BearerToken
|
||||
// @Param config body string true "配置"
|
||||
// @Success 200 {object} h.SuccessResponse
|
||||
// @Router /plugins/openresty/config [post]
|
||||
func (s *Service) SaveConfig(w http.ResponseWriter, r *http.Request) {
|
||||
config := r.FormValue("config")
|
||||
if len(config) == 0 {
|
||||
service.Error(w, http.StatusInternalServerError, "配置不能为空")
|
||||
}
|
||||
|
||||
if err := io.Write("/www/server/openresty/conf/nginx.conf", config, 0644); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "保存配置失败")
|
||||
}
|
||||
|
||||
if err := systemctl.Reload("openresty"); err != nil {
|
||||
_, err = shell.Execf("openresty -t")
|
||||
service.Error(w, http.StatusInternalServerError, fmt.Sprintf("重载服务失败: %v", err))
|
||||
}
|
||||
|
||||
service.Success(w, nil)
|
||||
}
|
||||
|
||||
// ErrorLog
|
||||
//
|
||||
// @Summary 获取错误日志
|
||||
// @Tags 插件-OpenResty
|
||||
// @Produce json
|
||||
// @Security BearerToken
|
||||
// @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") {
|
||||
service.Success(w, "")
|
||||
}
|
||||
|
||||
out, err := shell.Execf("tail -n 100 /www/wwwlogs/openresty_error.log")
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, out)
|
||||
}
|
||||
|
||||
service.Success(w, out)
|
||||
}
|
||||
|
||||
// ClearErrorLog
|
||||
//
|
||||
// @Summary 清空错误日志
|
||||
// @Tags 插件-OpenResty
|
||||
// @Produce json
|
||||
// @Security BearerToken
|
||||
// @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 {
|
||||
service.Error(w, http.StatusInternalServerError, out)
|
||||
}
|
||||
|
||||
service.Success(w, nil)
|
||||
}
|
||||
|
||||
// Load
|
||||
//
|
||||
// @Summary 获取负载状态
|
||||
// @Tags 插件-OpenResty
|
||||
// @Produce json
|
||||
// @Security BearerToken
|
||||
// @Success 200 {object} h.SuccessResponse
|
||||
// @Router /plugins/openresty/load [get]
|
||||
func (s *Service) Load(w http.ResponseWriter, r *http.Request) {
|
||||
client := resty.New().SetTimeout(10 * time.Second)
|
||||
resp, err := client.R().Get("http://127.0.0.1/nginx_status")
|
||||
if err != nil || !resp.IsSuccess() {
|
||||
service.Success(w, []types.NV{})
|
||||
}
|
||||
|
||||
raw := resp.String()
|
||||
var data []types.NV
|
||||
|
||||
workers, err := shell.Execf("ps aux | grep nginx | grep 'worker process' | wc -l")
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "获取负载失败")
|
||||
}
|
||||
data = append(data, types.NV{
|
||||
Name: "工作进程",
|
||||
Value: workers,
|
||||
})
|
||||
|
||||
out, err := shell.Execf("ps aux | grep nginx | grep 'worker process' | awk '{memsum+=$6};END {print memsum}'")
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "获取负载失败")
|
||||
}
|
||||
mem := str.FormatBytes(cast.ToFloat64(out))
|
||||
data = append(data, types.NV{
|
||||
Name: "内存占用",
|
||||
Value: mem,
|
||||
})
|
||||
|
||||
match := regexp.MustCompile(`Active connections:\s+(\d+)`).FindStringSubmatch(raw)
|
||||
if len(match) == 2 {
|
||||
data = append(data, types.NV{
|
||||
Name: "活跃连接数",
|
||||
Value: match[1],
|
||||
})
|
||||
}
|
||||
|
||||
match = regexp.MustCompile(`server accepts handled requests\s+(\d+)\s+(\d+)\s+(\d+)`).FindStringSubmatch(raw)
|
||||
if len(match) == 4 {
|
||||
data = append(data, types.NV{
|
||||
Name: "总连接次数",
|
||||
Value: match[1],
|
||||
})
|
||||
data = append(data, types.NV{
|
||||
Name: "总握手次数",
|
||||
Value: match[2],
|
||||
})
|
||||
data = append(data, types.NV{
|
||||
Name: "总请求次数",
|
||||
Value: match[3],
|
||||
})
|
||||
}
|
||||
|
||||
match = regexp.MustCompile(`Reading:\s+(\d+)\s+Writing:\s+(\d+)\s+Waiting:\s+(\d+)`).FindStringSubmatch(raw)
|
||||
if len(match) == 4 {
|
||||
data = append(data, types.NV{
|
||||
Name: "请求数",
|
||||
Value: match[1],
|
||||
})
|
||||
data = append(data, types.NV{
|
||||
Name: "响应数",
|
||||
Value: match[2],
|
||||
})
|
||||
data = append(data, types.NV{
|
||||
Name: "驻留进程",
|
||||
Value: match[3],
|
||||
})
|
||||
}
|
||||
|
||||
service.Success(w, data)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
package pluginloader
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"fmt"
|
||||
"slices"
|
||||
"sync"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
@@ -31,6 +33,12 @@ func All() []*types.Plugin {
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
// 排序
|
||||
slices.SortFunc(list, func(a, b *types.Plugin) int {
|
||||
return cmp.Compare(a.Order, b.Order)
|
||||
})
|
||||
|
||||
return list
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import "github.com/go-chi/chi/v5"
|
||||
|
||||
// Plugin 插件元数据结构
|
||||
type Plugin struct {
|
||||
Order int `json:"-"` // 排序
|
||||
Slug string `json:"slug"` // 插件标识
|
||||
Name string `json:"name"` // 插件名称
|
||||
Description string `json:"description"` // 插件描述
|
||||
|
||||
@@ -1,265 +0,0 @@
|
||||
package types
|
||||
|
||||
var PluginOpenResty = Plugin{
|
||||
Name: "OpenResty",
|
||||
Description: "OpenResty® 是一款基于 NGINX 和 LuaJIT 的 Web 平台",
|
||||
Slug: "openresty",
|
||||
Version: "1.25.3.1",
|
||||
Requires: []string{},
|
||||
Excludes: []string{},
|
||||
Install: "bash /www/panel/scripts/openresty/install.sh",
|
||||
Uninstall: "bash /www/panel/scripts/openresty/uninstall.sh",
|
||||
Update: "bash /www/panel/scripts/openresty/install.sh",
|
||||
}
|
||||
|
||||
var PluginMySQL57 = Plugin{
|
||||
Name: "MySQL-5.7",
|
||||
Description: "MySQL 是最流行的关系型数据库管理系统之一,Oracle 旗下产品(已停止维护,不建议使用!预计 2025 年 12 月移除)",
|
||||
Slug: "mysql57",
|
||||
Version: "5.7.44",
|
||||
Requires: []string{},
|
||||
Excludes: []string{"mysql80", "mysql84"},
|
||||
Install: `bash /www/panel/scripts/mysql/install.sh 57`,
|
||||
Uninstall: `bash /www/panel/scripts/mysql/uninstall.sh 57`,
|
||||
Update: `bash /www/panel/scripts/mysql/update.sh 57`,
|
||||
}
|
||||
|
||||
var PluginMySQL80 = Plugin{
|
||||
Name: "MySQL-8.0",
|
||||
Description: "MySQL 是最流行的关系型数据库管理系统之一,Oracle 旗下产品(建议内存 > 2G 安装)",
|
||||
Slug: "mysql80",
|
||||
Version: "8.0.37",
|
||||
Requires: []string{},
|
||||
Excludes: []string{"mysql57", "mysql84"},
|
||||
Install: `bash /www/panel/scripts/mysql/install.sh 80`,
|
||||
Uninstall: `bash /www/panel/scripts/mysql/uninstall.sh 80`,
|
||||
Update: `bash /www/panel/scripts/mysql/update.sh 80`,
|
||||
}
|
||||
|
||||
var PluginMySQL84 = Plugin{
|
||||
Name: "MySQL-8.4",
|
||||
Description: "MySQL 是最流行的关系型数据库管理系统之一,Oracle 旗下产品(建议内存 > 2G 安装)",
|
||||
Slug: "mysql84",
|
||||
Version: "8.4.0",
|
||||
Requires: []string{},
|
||||
Excludes: []string{"mysql57", "mysql80"},
|
||||
Install: `bash /www/panel/scripts/mysql/install.sh 84`,
|
||||
Uninstall: `bash /www/panel/scripts/mysql/uninstall.sh 84`,
|
||||
Update: `bash /www/panel/scripts/mysql/update.sh 84`,
|
||||
}
|
||||
|
||||
var PluginPostgreSQL15 = Plugin{
|
||||
Name: "PostgreSQL-15",
|
||||
Description: "PostgreSQL 是世界上最先进的开源关系数据库,在类似 BSD 与 MIT 许可的 PostgreSQL 许可下发行",
|
||||
Slug: "postgresql15",
|
||||
Version: "15.7",
|
||||
Requires: []string{},
|
||||
Excludes: []string{"postgresql16"},
|
||||
Install: `bash /www/panel/scripts/postgresql/install.sh 15`,
|
||||
Uninstall: `bash /www/panel/scripts/postgresql/uninstall.sh 15`,
|
||||
Update: `bash /www/panel/scripts/postgresql/update.sh 15`,
|
||||
}
|
||||
|
||||
var PluginPostgreSQL16 = Plugin{
|
||||
Name: "PostgreSQL-16",
|
||||
Description: "PostgreSQL 是世界上最先进的开源关系数据库,在类似 BSD 与 MIT 许可的 PostgreSQL 许可下发行",
|
||||
Slug: "postgresql16",
|
||||
Version: "16.3",
|
||||
Requires: []string{},
|
||||
Excludes: []string{"postgresql15"},
|
||||
Install: `bash /www/panel/scripts/postgresql/install.sh 16`,
|
||||
Uninstall: `bash /www/panel/scripts/postgresql/uninstall.sh 16`,
|
||||
Update: `bash /www/panel/scripts/postgresql/update.sh 16`,
|
||||
}
|
||||
|
||||
var PluginPHP74 = Plugin{
|
||||
Name: "PHP-7.4",
|
||||
Description: "PHP 是一种创建动态交互性站点的强有力的服务器端脚本语言(已停止维护,不建议使用!预计 2024 年 12 月移除)",
|
||||
Slug: "php74",
|
||||
Version: "7.4.33",
|
||||
Requires: []string{},
|
||||
Excludes: []string{},
|
||||
Install: `bash /www/panel/scripts/php/install.sh 74`,
|
||||
Uninstall: `bash /www/panel/scripts/php/uninstall.sh 74`,
|
||||
Update: `bash /www/panel/scripts/php/install.sh 74`,
|
||||
}
|
||||
|
||||
var PluginPHP80 = Plugin{
|
||||
Name: "PHP-8.0",
|
||||
Description: "PHP 是一种创建动态交互性站点的强有力的服务器端脚本语言(已停止维护,不建议使用!预计 2025 年 12 月移除)",
|
||||
Slug: "php80",
|
||||
Version: "8.0.30",
|
||||
Requires: []string{},
|
||||
Excludes: []string{},
|
||||
Install: `bash /www/panel/scripts/php/install.sh 80`,
|
||||
Uninstall: `bash /www/panel/scripts/php/uninstall.sh 80`,
|
||||
Update: `bash /www/panel/scripts/php/install.sh 80`,
|
||||
}
|
||||
|
||||
var PluginPHP81 = Plugin{
|
||||
Name: "PHP-8.1",
|
||||
Description: "PHP 是一种创建动态交互性站点的强有力的服务器端脚本语言",
|
||||
Slug: "php81",
|
||||
Version: "8.1.29",
|
||||
Requires: []string{},
|
||||
Excludes: []string{},
|
||||
Install: `bash /www/panel/scripts/php/install.sh 81`,
|
||||
Uninstall: `bash /www/panel/scripts/php/uninstall.sh 81`,
|
||||
Update: `bash /www/panel/scripts/php/install.sh 81`,
|
||||
}
|
||||
|
||||
var PluginPHP82 = Plugin{
|
||||
Name: "PHP-8.2",
|
||||
Description: "PHP 是一种创建动态交互性站点的强有力的服务器端脚本语言",
|
||||
Slug: "php82",
|
||||
Version: "8.2.20",
|
||||
Requires: []string{},
|
||||
Excludes: []string{},
|
||||
Install: `bash /www/panel/scripts/php/install.sh 82`,
|
||||
Uninstall: `bash /www/panel/scripts/php/uninstall.sh 82`,
|
||||
Update: `bash /www/panel/scripts/php/install.sh 82`,
|
||||
}
|
||||
|
||||
var PluginPHP83 = Plugin{
|
||||
Name: "PHP-8.3",
|
||||
Description: "PHP 是一种创建动态交互性站点的强有力的服务器端脚本语言",
|
||||
Slug: "php83",
|
||||
Version: "8.3.8",
|
||||
Requires: []string{},
|
||||
Excludes: []string{},
|
||||
Install: `bash /www/panel/scripts/php/install.sh 83`,
|
||||
Uninstall: `bash /www/panel/scripts/php/uninstall.sh 83`,
|
||||
Update: `bash /www/panel/scripts/php/install.sh 83`,
|
||||
}
|
||||
|
||||
var PluginPHPMyAdmin = Plugin{
|
||||
Name: "phpMyAdmin",
|
||||
Description: "phpMyAdmin 是一个以 PHP 为基础,以 Web-Base 方式架构在网站主机上的 MySQL 数据库管理工具",
|
||||
Slug: "phpmyadmin",
|
||||
Version: "5.2.1",
|
||||
Requires: []string{"openresty"},
|
||||
Excludes: []string{},
|
||||
Install: `bash /www/panel/scripts/phpmyadmin/install.sh`,
|
||||
Uninstall: `bash /www/panel/scripts/phpmyadmin/uninstall.sh`,
|
||||
Update: `bash /www/panel/scripts/phpmyadmin/uninstall.sh && bash /www/panel/scripts/phpmyadmin/install.sh`,
|
||||
}
|
||||
|
||||
var PluginPureFTPd = Plugin{
|
||||
Name: "Pure-FTPd",
|
||||
Description: "Pure-Ftpd 是一个快速、高效、轻便、安全的 FTP 服务器,它以安全和配置简单为设计目标,支持虚拟主机,IPV6,PAM 等功能",
|
||||
Slug: "pureftpd",
|
||||
Version: "1.0.50",
|
||||
Requires: []string{},
|
||||
Excludes: []string{},
|
||||
Install: `bash /www/panel/scripts/pureftpd/install.sh`,
|
||||
Uninstall: `bash /www/panel/scripts/pureftpd/uninstall.sh`,
|
||||
Update: `bash /www/panel/scripts/pureftpd/update.sh`,
|
||||
}
|
||||
|
||||
var PluginRedis = Plugin{
|
||||
Name: "Redis",
|
||||
Description: "Redis 是一个开源的使用 ANSI C 语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value 数据库,并提供多种语言的 API",
|
||||
Slug: "redis",
|
||||
Version: "7.2.5",
|
||||
Requires: []string{},
|
||||
Excludes: []string{},
|
||||
Install: `bash /www/panel/scripts/redis/install.sh`,
|
||||
Uninstall: `bash /www/panel/scripts/redis/uninstall.sh`,
|
||||
Update: `bash /www/panel/scripts/redis/update.sh`,
|
||||
}
|
||||
|
||||
var PluginS3fs = Plugin{
|
||||
Name: "S3fs",
|
||||
Description: "S3fs 通过 FUSE 挂载兼容 S3 标准的存储桶,例如 Amazon S3、阿里云 OSS、腾讯云 COS、七牛云 Kodo 等",
|
||||
Slug: "s3fs",
|
||||
Version: "1.9",
|
||||
Requires: []string{},
|
||||
Excludes: []string{},
|
||||
Install: `bash /www/panel/scripts/s3fs/install.sh`,
|
||||
Uninstall: `bash /www/panel/scripts/s3fs/uninstall.sh`,
|
||||
Update: `bash /www/panel/scripts/s3fs/update.sh`,
|
||||
}
|
||||
|
||||
var PluginRsync = Plugin{
|
||||
Name: "Rsync",
|
||||
Description: "Rsync 是一款提供快速增量文件传输的开源工具",
|
||||
Slug: "rsync",
|
||||
Version: "3.2.7",
|
||||
Requires: []string{},
|
||||
Excludes: []string{},
|
||||
Install: `bash /www/panel/scripts/rsync/install.sh`,
|
||||
Uninstall: `bash /www/panel/scripts/rsync/uninstall.sh`,
|
||||
Update: `bash /www/panel/scripts/rsync/install.sh`,
|
||||
}
|
||||
|
||||
var PluginSupervisor = Plugin{
|
||||
Name: "Supervisor",
|
||||
Description: "Supervisor 是一个客户端/服务器系统,允许用户监视和控制类 UNIX 操作系统上的多个进程",
|
||||
Slug: "supervisor",
|
||||
Version: "4.2.5",
|
||||
Requires: []string{},
|
||||
Excludes: []string{},
|
||||
Install: `bash /www/panel/scripts/supervisor/install.sh`,
|
||||
Uninstall: `bash /www/panel/scripts/supervisor/uninstall.sh`,
|
||||
Update: `bash /www/panel/scripts/supervisor/update.sh`,
|
||||
}
|
||||
|
||||
var PluginFail2ban = Plugin{
|
||||
Name: "Fail2ban",
|
||||
Description: "Fail2ban 扫描系统日志文件并从中找出多次尝试失败的IP地址,将该IP地址加入防火墙的拒绝访问列表中",
|
||||
Slug: "fail2ban",
|
||||
Version: "1.0.2",
|
||||
Requires: []string{},
|
||||
Excludes: []string{},
|
||||
Install: `bash /www/panel/scripts/fail2ban/install.sh`,
|
||||
Uninstall: `bash /www/panel/scripts/fail2ban/uninstall.sh`,
|
||||
Update: `bash /www/panel/scripts/fail2ban/update.sh`,
|
||||
}
|
||||
|
||||
var PluginPodman = Plugin{
|
||||
Name: "Podman",
|
||||
Description: "Podman(POD MANager)是一款用于管理容器和镜像、挂载到这些容器中的卷以及由容器组构成的 Pod 的工具",
|
||||
Slug: "podman",
|
||||
Version: "4.0.0",
|
||||
Requires: []string{},
|
||||
Excludes: []string{"docker"},
|
||||
Install: `bash /www/panel/scripts/podman/install.sh`,
|
||||
Uninstall: `bash /www/panel/scripts/podman/uninstall.sh`,
|
||||
Update: `bash /www/panel/scripts/podman/update.sh`,
|
||||
}
|
||||
|
||||
var PluginFrp = Plugin{
|
||||
Name: "Frp",
|
||||
Description: "frp 是一个专注于内网穿透的高性能的反向代理应用",
|
||||
Slug: "frp",
|
||||
Version: "0.58.0",
|
||||
Requires: []string{},
|
||||
Excludes: []string{},
|
||||
Install: `bash /www/panel/scripts/frp/install.sh`,
|
||||
Uninstall: `bash /www/panel/scripts/frp/uninstall.sh`,
|
||||
Update: `bash /www/panel/scripts/frp/update.sh`,
|
||||
}
|
||||
|
||||
var PluginGitea = Plugin{
|
||||
Name: "Gitea",
|
||||
Description: "Gitea 是一款极易搭建的自助 Git 服务,它包括 Git 托管、代码审查、团队协作、软件包注册和 CI/CD",
|
||||
Slug: "gitea",
|
||||
Version: "1.22.0",
|
||||
Requires: []string{},
|
||||
Excludes: []string{},
|
||||
Install: `bash /www/panel/scripts/gitea/install.sh`,
|
||||
Uninstall: `bash /www/panel/scripts/gitea/uninstall.sh`,
|
||||
Update: `bash /www/panel/scripts/gitea/update.sh`,
|
||||
}
|
||||
|
||||
var PluginToolBox = Plugin{
|
||||
Name: "系统工具箱",
|
||||
Description: "可视化调整一些常用的配置项,如 DNS、SWAP、时区等",
|
||||
Slug: "toolbox",
|
||||
Version: "1.0.0",
|
||||
Requires: []string{},
|
||||
Excludes: []string{},
|
||||
Install: `panel writePlugin toolbox 1.0.0`,
|
||||
Uninstall: `panel deletePlugin toolbox`,
|
||||
Update: `panel writePlugin toolbox 1.0.0`,
|
||||
}
|
||||
Reference in New Issue
Block a user