diff --git a/internal/biz/website.go b/internal/biz/website.go index 1191c290..7f5edba6 100644 --- a/internal/biz/website.go +++ b/internal/biz/website.go @@ -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 diff --git a/internal/data/website.go b/internal/data/website.go index 31e62190..037b27c3 100644 --- a/internal/data/website.go +++ b/internal/data/website.go @@ -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 diff --git a/internal/plugin/fail2ban/init.go b/internal/plugin/fail2ban/init.go new file mode 100644 index 00000000..2b1f641f --- /dev/null +++ b/internal/plugin/fail2ban/init.go @@ -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) + }, + }) +} diff --git a/internal/plugin/fail2ban/request.go b/internal/plugin/fail2ban/request.go new file mode 100644 index 00000000..f875d2ba --- /dev/null +++ b/internal/plugin/fail2ban/request.go @@ -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"` +} diff --git a/internal/plugin/fail2ban/service.go b/internal/plugin/fail2ban/service.go new file mode 100644 index 00000000..342d65d8 --- /dev/null +++ b/internal/plugin/fail2ban/service.go @@ -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 = ^\s-.*HTTP/.*$ +ignoreregex = +` + } else { + filter = ` +[Definition] +failregex = ^\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 + } +} diff --git a/internal/plugin/init.go b/internal/plugin/init.go index f334855d..9ae3e9fe 100644 --- a/internal/plugin/init.go +++ b/internal/plugin/init.go @@ -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" ) diff --git a/internal/plugin/openresty/init.go b/internal/plugin/openresty/init.go index 2de36be7..b03226ec 100644 --- a/internal/plugin/openresty/init.go +++ b/internal/plugin/openresty/init.go @@ -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) }, }) } diff --git a/internal/plugin/openresty/service.go b/internal/plugin/openresty/service.go index 83ccc157..7e969916 100644 --- a/internal/plugin/openresty/service.go +++ b/internal/plugin/openresty/service.go @@ -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) +} diff --git a/pkg/pluginloader/plugin.go b/pkg/pluginloader/plugin.go index fa92b64a..73291242 100644 --- a/pkg/pluginloader/plugin.go +++ b/pkg/pluginloader/plugin.go @@ -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 } diff --git a/pkg/types/plugin.go b/pkg/types/plugin.go index beb0c285..df5b0716 100644 --- a/pkg/types/plugin.go +++ b/pkg/types/plugin.go @@ -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"` // 插件描述 diff --git a/pkg/types/plugin_list.go b/pkg/types/plugin_list.go deleted file mode 100644 index 31605198..00000000 --- a/pkg/types/plugin_list.go +++ /dev/null @@ -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`, -}