Compare commits

...

32 Commits

Author SHA1 Message Date
耗子 8aff513284 fix: 禁止80端口使用HTTPS 2024-06-25 01:13:24 +08:00
耗子 368e41a512 feat: 发布v2.2.16 2024-06-25 01:01:07 +08:00
耗子 924fdee7c5 feat: 支持设置HTTPS端口 2024-06-25 01:00:08 +08:00
耗子 438ba1db1e fix: lint 2024-06-24 23:50:53 +08:00
耗子 d4e52a2d87 feat: ZeroSSL支持自动获取EAB 2024-06-24 23:46:13 +08:00
耗子 4432a12c7a feat: 临时性手段阻止任务重复添加 2024-06-24 23:28:35 +08:00
耗子 a75e36e447 feat: 优化任务 2024-06-24 23:10:39 +08:00
耗子 05674ee04e feat: 优化迁移 2024-06-24 22:53:43 +08:00
耗子 09703761eb fix(文件): 权限设置错误 2024-06-24 22:20:41 +08:00
耗子 dfa14c3fd4 refactor: MySQL改密逻辑 2024-06-24 21:52:46 +08:00
耗子 7b80f034ea feat: 安装前先清理文件 2024-06-24 19:46:43 +08:00
耗子 71923093fd feat(文件): 解压不再判断路径存在 2024-06-24 19:11:01 +08:00
耗子 aae3cf0518 fix: 统一格式 2024-06-24 03:46:21 +08:00
耗子 ed22e79552 feat: 防止密码爆破攻击 2024-06-24 03:45:38 +08:00
耗子 15dd5bb718 feat: 发布v2.2.15 2024-06-23 05:09:30 +08:00
耗子 0e7af2914e feat: 发布v2.2.14 2024-06-23 04:52:18 +08:00
耗子 7ec378829b feat: 优化https证书签发 2024-06-23 04:44:14 +08:00
耗子 f2e8ff86c6 feat: 发布v2.2.13 2024-06-23 03:19:39 +08:00
耗子 eed03d644f fix: test 2024-06-23 03:19:13 +08:00
耗子 d9be877710 feat: 发布v2.2.12 2024-06-23 03:03:38 +08:00
耗子 10439d7d97 feat: 优化证书信息获取 2024-06-23 02:42:01 +08:00
耗子 55eaa26970 feat: 添加面板HTTPS接口 2024-06-23 02:35:25 +08:00
耗子 0d769d3400 fix: lint 2024-06-23 01:46:14 +08:00
耗子 7ff084e5a8 refactor: 重构types包 2024-06-23 01:32:45 +08:00
耗子 89e133c626 refactor: 重构str包 2024-06-23 01:12:51 +08:00
耗子 c876f4c484 refactor: 重构os包 2024-06-23 01:08:07 +08:00
耗子 85c38e5bc5 refactor: 重构io包 2024-06-23 00:59:07 +08:00
耗子 6bd07251fc refactor: 重构systemctl包 2024-06-23 00:48:37 +08:00
耗子 11d6b40dce refactor: 重构shell命令执行 2024-06-23 00:44:14 +08:00
耗子 aa9b55d1eb feat: bump v2.2.11 2024-06-19 02:06:58 +08:00
耗子 2e97812776 fix: 修复版本号比较错误 2024-06-19 02:06:39 +08:00
耗子 e0a7ada6d5 feat(监控): 去掉cpu型号信息 2024-06-19 01:58:40 +08:00
114 changed files with 2372 additions and 1846 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
# These are supported funding model platforms
open_collective: tnb
custom: ['https://afdian.net/a/TheTNB']
custom: [ 'https://afdian.net/a/TheTNB' ]
+10 -14
View File
@@ -2,8 +2,6 @@ package commands
import (
"context"
"crypto/x509"
"encoding/pem"
"github.com/goravel/framework/contracts/console"
"github.com/goravel/framework/contracts/console/command"
@@ -12,7 +10,8 @@ import (
"github.com/TheTNB/panel/app/models"
"github.com/TheTNB/panel/internal/services"
"github.com/TheTNB/panel/types"
panelcert "github.com/TheTNB/panel/pkg/cert"
"github.com/TheTNB/panel/pkg/types"
)
// CertRenew 证书续签
@@ -54,18 +53,15 @@ func (receiver *CertRenew) Handle(console.Context) error {
continue
}
block, _ := pem.Decode([]byte(cert.Cert))
if block != nil {
data, err := x509.ParseCertificate(block.Bytes)
if err != nil {
continue
}
decode, err := panelcert.ParseCert(cert.Cert)
if err != nil {
continue
}
// 结束时间大于 7 天的证书不续签
endTime := carbon.FromStdTime(data.NotAfter)
if endTime.Gt(carbon.Now().AddDays(7)) {
continue
}
// 结束时间大于 7 天的证书不续签
endTime := carbon.FromStdTime(decode.NotAfter)
if endTime.Gt(carbon.Now().AddDays(7)) {
continue
}
certService := services.NewCertImpl()
+4 -11
View File
@@ -13,7 +13,7 @@ import (
"github.com/TheTNB/panel/app/models"
"github.com/TheTNB/panel/internal/services"
"github.com/TheTNB/panel/pkg/tools"
"github.com/TheTNB/panel/types"
"github.com/TheTNB/panel/pkg/types"
)
// Monitoring 系统监控
@@ -46,8 +46,8 @@ func (receiver *Monitoring) Handle(console.Context) error {
// 将等待中的任务分发
// TODO 有bug,需要设计一个锁机制防止重复分发
//task := services.NewTaskImpl()
//_ = task.DispatchWaiting()
task := services.NewTaskImpl()
_ = task.DispatchWaiting()
setting := services.NewSettingImpl()
monitor := setting.Get(models.SettingKeyMonitor)
@@ -60,14 +60,7 @@ func (receiver *Monitoring) Handle(console.Context) error {
// 去除部分数据以减少数据库存储
info.Disk = nil
for _, cpu := range info.Cpus {
cpu.VendorID = ""
cpu.Family = ""
cpu.Model = ""
cpu.PhysicalID = ""
cpu.ModelName = ""
cpu.Flags = nil
}
info.Cpus = nil
if types.Status != types.StatusNormal {
return nil
+32 -28
View File
@@ -17,8 +17,12 @@ import (
"github.com/TheTNB/panel/app/models"
"github.com/TheTNB/panel/internal/services"
"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/tools"
"github.com/TheTNB/panel/types"
"github.com/TheTNB/panel/pkg/types"
)
// Panel 面板命令行
@@ -70,7 +74,7 @@ func (receiver *Panel) Handle(ctx console.Context) error {
return nil
}
hash, err := facades.Hash().Make(tools.RandomString(32))
hash, err := facades.Hash().Make(str.RandomString(32))
if err != nil {
color.Red().Printfln(translate.Get("commands.panel.init.fail"))
return nil
@@ -104,7 +108,7 @@ func (receiver *Panel) Handle(ctx console.Context) error {
}
// 停止面板服务,因为在shell中运行的和systemd的不同
_ = tools.ServiceStop("panel")
_ = systemctl.Stop("panel")
types.Status = types.StatusUpgrade
if err = tools.UpdatePanel(panel); err != nil {
@@ -125,16 +129,16 @@ func (receiver *Panel) Handle(ctx console.Context) error {
return nil
}
password := tools.RandomString(16)
password := str.RandomString(16)
hash, err := facades.Hash().Make(password)
if err != nil {
color.Red().Printfln(translate.Get("commands.panel.getInfo.passwordGenerationFail"))
return nil
}
user.Username = tools.RandomString(8)
user.Username = str.RandomString(8)
user.Password = hash
if user.Email == "" {
user.Email = tools.RandomString(8) + "@example.com"
user.Email = str.RandomString(8) + "@example.com"
}
err = facades.Orm().Query().Save(&user)
@@ -143,7 +147,7 @@ func (receiver *Panel) Handle(ctx console.Context) error {
return nil
}
port, err := tools.Exec(`cat /www/panel/panel.conf | grep APP_PORT | awk -F '=' '{print $2}' | tr -d '\n'`)
port, err := shell.Execf(`cat /www/panel/panel.conf | grep APP_PORT | awk -F '=' '{print $2}' | tr -d '\n'`)
if err != nil {
color.Red().Printfln(translate.Get("commands.panel.portFail"))
return nil
@@ -164,7 +168,7 @@ func (receiver *Panel) Handle(ctx console.Context) error {
color.Green().Printfln(translate.Get("commands.panel.getInfo.address") + ": " + protocol + "://" + ip + ":" + port + facades.Config().GetString("http.entrance"))
case "getPort":
port, err := tools.Exec(`cat /www/panel/panel.conf | grep APP_PORT | awk -F '=' '{print $2}' | tr -d '\n'`)
port, err := shell.Execf(`cat /www/panel/panel.conf | grep APP_PORT | awk -F '=' '{print $2}' | tr -d '\n'`)
if err != nil {
color.Red().Printfln(translate.Get("commands.panel.portFail"))
return nil
@@ -176,12 +180,12 @@ func (receiver *Panel) Handle(ctx console.Context) error {
color.Green().Printfln(translate.Get("commands.panel.entrance") + ": " + facades.Config().GetString("http.entrance"))
case "deleteEntrance":
oldEntrance, err := tools.Exec(`cat /www/panel/panel.conf | grep APP_ENTRANCE | awk -F '=' '{print $2}' | tr -d '\n'`)
oldEntrance, err := shell.Execf(`cat /www/panel/panel.conf | grep APP_ENTRANCE | awk -F '=' '{print $2}' | tr -d '\n'`)
if err != nil {
color.Red().Printfln(translate.Get("commands.panel.deleteEntrance.fail"))
return nil
}
if _, err = tools.Exec("sed -i 's!APP_ENTRANCE=" + oldEntrance + "!APP_ENTRANCE=/!g' /www/panel/panel.conf"); err != nil {
if _, err = shell.Execf("sed -i 's!APP_ENTRANCE=" + oldEntrance + "!APP_ENTRANCE=/!g' /www/panel/panel.conf"); err != nil {
color.Red().Printfln(translate.Get("commands.panel.deleteEntrance.fail"))
return nil
}
@@ -270,8 +274,8 @@ func (receiver *Panel) Handle(ctx console.Context) error {
color.Green().Printfln("★ " + translate.Get("commands.panel.backup.start") + " [" + carbon.Now().ToDateTimeString() + "]")
color.Green().Printfln(hr)
if !tools.Exists(path) {
if err := tools.Mkdir(path, 0644); err != nil {
if !io.Exists(path) {
if err := io.Mkdir(path, 0644); err != nil {
color.Red().Printfln("|-" + translate.Get("commands.panel.backup.backupDirFail") + ": " + err.Error())
return nil
}
@@ -288,7 +292,7 @@ func (receiver *Panel) Handle(ctx console.Context) error {
}
backupFile := path + "/" + website.Name + "_" + carbon.Now().ToShortDateTimeString() + ".zip"
if _, err := tools.Exec(`cd '` + website.Path + `' && zip -r '` + backupFile + `' .`); err != nil {
if _, err := shell.Execf(`cd '` + website.Path + `' && zip -r '` + backupFile + `' .`); err != nil {
color.Red().Printfln("|-" + translate.Get("commands.panel.backup.backupFail") + ": " + err.Error())
return nil
}
@@ -307,23 +311,23 @@ func (receiver *Panel) Handle(ctx console.Context) error {
color.Green().Printfln("|-" + translate.Get("commands.panel.backup.targetMysql") + ": " + name)
color.Green().Printfln("|-" + translate.Get("commands.panel.backup.startExport"))
if _, err = tools.Exec(`mysqldump -uroot ` + name + ` > /tmp/` + backupFile + ` 2>&1`); err != nil {
if _, err = shell.Execf(`mysqldump -uroot ` + name + ` > /tmp/` + backupFile + ` 2>&1`); err != nil {
color.Red().Printfln("|-" + translate.Get("commands.panel.backup.exportFail") + ": " + err.Error())
return nil
}
color.Green().Printfln("|-" + translate.Get("commands.panel.backup.exportSuccess"))
color.Green().Printfln("|-" + translate.Get("commands.panel.backup.startCompress"))
if _, err = tools.Exec("cd /tmp && zip -r " + backupFile + ".zip " + backupFile); err != nil {
if _, err = shell.Execf("cd /tmp && zip -r " + backupFile + ".zip " + backupFile); err != nil {
color.Red().Printfln("|-" + translate.Get("commands.panel.backup.compressFail") + ": " + err.Error())
return nil
}
if err := tools.Remove("/tmp/" + backupFile); err != nil {
if err := io.Remove("/tmp/" + backupFile); err != nil {
color.Red().Printfln("|-" + translate.Get("commands.panel.backup.deleteFail") + ": " + err.Error())
return nil
}
color.Green().Printfln("|-" + translate.Get("commands.panel.backup.compressSuccess"))
color.Green().Printfln("|-" + translate.Get("commands.panel.backup.startMove"))
if err := tools.Mv("/tmp/"+backupFile+".zip", path+"/"+backupFile+".zip"); err != nil {
if err := io.Mv("/tmp/"+backupFile+".zip", path+"/"+backupFile+".zip"); err != nil {
color.Red().Printfln("|-" + translate.Get("commands.panel.backup.moveFail") + ": " + err.Error())
return nil
}
@@ -333,7 +337,7 @@ func (receiver *Panel) Handle(ctx console.Context) error {
case "postgresql":
backupFile := name + "_" + carbon.Now().ToShortDateTimeString() + ".sql"
check, err := tools.Exec(`su - postgres -c "psql -l" 2>&1`)
check, err := shell.Execf(`su - postgres -c "psql -l" 2>&1`)
if err != nil {
color.Red().Printfln("|-" + translate.Get("commands.panel.backup.databaseGetFail") + ": " + err.Error())
color.Green().Printfln(hr)
@@ -347,23 +351,23 @@ func (receiver *Panel) Handle(ctx console.Context) error {
color.Green().Printfln("|-" + translate.Get("commands.panel.backup.targetPostgres") + ": " + name)
color.Green().Printfln("|-" + translate.Get("commands.panel.backup.startExport"))
if _, err = tools.Exec(`su - postgres -c "pg_dump '` + name + `'" > /tmp/` + backupFile + ` 2>&1`); err != nil {
if _, err = shell.Execf(`su - postgres -c "pg_dump '` + name + `'" > /tmp/` + backupFile + ` 2>&1`); err != nil {
color.Red().Printfln("|-" + translate.Get("commands.panel.backup.exportFail") + ": " + err.Error())
return nil
}
color.Green().Printfln("|-" + translate.Get("commands.panel.backup.exportSuccess"))
color.Green().Printfln("|-" + translate.Get("commands.panel.backup.startCompress"))
if _, err = tools.Exec("cd /tmp && zip -r " + backupFile + ".zip " + backupFile); err != nil {
if _, err = shell.Execf("cd /tmp && zip -r " + backupFile + ".zip " + backupFile); err != nil {
color.Red().Printfln("|-" + translate.Get("commands.panel.backup.compressFail") + ": " + err.Error())
return nil
}
if err := tools.Remove("/tmp/" + backupFile); err != nil {
if err := io.Remove("/tmp/" + backupFile); err != nil {
color.Red().Printfln("|-" + translate.Get("commands.panel.backup.deleteFail") + ": " + err.Error())
return nil
}
color.Green().Printfln("|-" + translate.Get("commands.panel.backup.compressSuccess"))
color.Green().Printfln("|-" + translate.Get("commands.panel.backup.startMove"))
if err := tools.Mv("/tmp/"+backupFile+".zip", path+"/"+backupFile+".zip"); err != nil {
if err := io.Mv("/tmp/"+backupFile+".zip", path+"/"+backupFile+".zip"); err != nil {
color.Red().Printfln("|-" + translate.Get("commands.panel.backup.moveFail") + ": " + err.Error())
return nil
}
@@ -393,7 +397,7 @@ func (receiver *Panel) Handle(ctx console.Context) error {
for i := cast.ToInt(save); i < len(filteredFiles); i++ {
fileToDelete := filepath.Join(path, filteredFiles[i].Name())
color.Yellow().Printfln("|-" + translate.Get("commands.panel.backup.cleanBackup") + ": " + fileToDelete)
if err := tools.Remove(fileToDelete); err != nil {
if err := io.Remove(fileToDelete); err != nil {
color.Red().Printfln("|-" + translate.Get("commands.panel.backup.cleanupFail") + ": " + err.Error())
return nil
}
@@ -425,18 +429,18 @@ func (receiver *Panel) Handle(ctx console.Context) error {
}
logPath := "/www/wwwlogs/" + website.Name + ".log"
if !tools.Exists(logPath) {
if !io.Exists(logPath) {
color.Red().Printfln("|-" + translate.Get("commands.panel.cutoff.logNotExist"))
color.Green().Printfln(hr)
return nil
}
backupPath := "/www/wwwlogs/" + website.Name + "_" + carbon.Now().ToShortDateTimeString() + ".log.zip"
if _, err := tools.Exec(`cd /www/wwwlogs && zip -r ` + backupPath + ` ` + website.Name + ".log"); err != nil {
if _, err := shell.Execf(`cd /www/wwwlogs && zip -r ` + backupPath + ` ` + website.Name + ".log"); err != nil {
color.Red().Printfln("|-" + translate.Get("commands.panel.cutoff.backupFail") + ": " + err.Error())
return nil
}
if _, err := tools.Exec(`echo "" > ` + logPath); err != nil {
if _, err := shell.Execf(`echo "" > ` + logPath); err != nil {
color.Red().Printfln("|-" + translate.Get("commands.panel.cutoff.clearFail") + ": " + err.Error())
return nil
}
@@ -464,7 +468,7 @@ func (receiver *Panel) Handle(ctx console.Context) error {
for i := cast.ToInt(save); i < len(filteredFiles); i++ {
fileToDelete := filepath.Join("/www/wwwlogs", filteredFiles[i].Name())
color.Yellow().Printfln("|-" + translate.Get("commands.panel.cutoff.clearLog") + ": " + fileToDelete)
if err := tools.Remove(fileToDelete); err != nil {
if err := io.Remove(fileToDelete); err != nil {
color.Red().Printfln("|-" + translate.Get("commands.panel.cutoff.cleanupFail") + ": " + err.Error())
return nil
}
@@ -610,7 +614,7 @@ func (receiver *Panel) Handle(ctx console.Context) error {
return nil
}
_, err = website.Add(types.Website{
_, err = website.Add(types.WebsiteAdd{
Name: name,
Status: true,
Domains: domains,
+5 -4
View File
@@ -8,8 +8,9 @@ import (
"github.com/goravel/framework/facades"
"github.com/goravel/framework/support/carbon"
"github.com/TheTNB/panel/pkg/tools"
"github.com/TheTNB/panel/types"
"github.com/TheTNB/panel/pkg/io"
"github.com/TheTNB/panel/pkg/shell"
"github.com/TheTNB/panel/pkg/types"
)
// PanelTask 面板每日任务
@@ -48,7 +49,7 @@ func (receiver *PanelTask) Handle(console.Context) error {
}
// 备份面板
if err := tools.Archive([]string{"/www/panel"}, "/www/backup/panel/panel-"+carbon.Now().ToShortDateTimeString()+".zip"); err != nil {
if err := io.Archive([]string{"/www/panel"}, "/www/backup/panel/panel-"+carbon.Now().ToShortDateTimeString()+".zip"); err != nil {
types.Status = types.StatusFailed
facades.Log().Tags("面板", "每日任务").
With(map[string]any{
@@ -58,7 +59,7 @@ func (receiver *PanelTask) Handle(console.Context) error {
}
// 清理 7 天前的备份
if _, err := tools.Exec(`find /www/backup/panel -mtime +7 -name "*.zip" -exec rm -rf {} \;`); err != nil {
if _, err := shell.Execf(`find /www/backup/panel -mtime +7 -name "*.zip" -exec rm -rf {} \;`); err != nil {
types.Status = types.StatusFailed
facades.Log().Tags("面板", "每日任务").
With(map[string]any{
+2 -2
View File
@@ -9,7 +9,7 @@ import (
"github.com/TheTNB/panel/internal"
"github.com/TheTNB/panel/internal/services"
"github.com/TheTNB/panel/pkg/tools"
"github.com/TheTNB/panel/pkg/io"
)
type AssetController struct {
@@ -43,7 +43,7 @@ func (r *AssetController) Index(ctx http.Context) http.Response {
path = "/index.html"
}
if !tools.Exists("public" + path) {
if !io.Exists("public" + path) {
return Error(ctx, http.StatusNotFound, http.StatusText(http.StatusNotFound))
}
+3 -3
View File
@@ -5,7 +5,6 @@ import (
"strconv"
"strings"
"github.com/TheTNB/panel/pkg/tools"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/network"
"github.com/docker/go-connections/nat"
@@ -14,6 +13,7 @@ import (
requests "github.com/TheTNB/panel/app/http/requests/container"
"github.com/TheTNB/panel/internal/services"
"github.com/TheTNB/panel/pkg/str"
)
type ContainerController struct {
@@ -725,7 +725,7 @@ func (r *ContainerController) ImageList(ctx http.Context) http.Response {
"id": item.ID,
"created": carbon.FromTimestamp(item.Created).ToDateTimeString(),
"containers": item.Containers,
"size": tools.FormatBytes(float64(item.Size)),
"size": str.FormatBytes(float64(item.Size)),
"labels": item.Labels,
"repo_tags": item.RepoTags,
"repo_digests": item.RepoDigests,
@@ -874,7 +874,7 @@ func (r *ContainerController) VolumeList(ctx http.Context) http.Response {
if item.UsageData != nil {
usage = map[string]any{
"ref_count": item.UsageData.RefCount,
"size": tools.FormatBytes(float64(item.UsageData.Size)),
"size": str.FormatBytes(float64(item.UsageData.Size)),
}
}
items = append(items, map[string]any{
+2 -1
View File
@@ -1,9 +1,10 @@
package controllers
import (
commonrequests "github.com/TheTNB/panel/app/http/requests/common"
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/facades"
commonrequests "github.com/TheTNB/panel/app/http/requests/common"
)
// SuccessResponse 通用成功响应
+18 -16
View File
@@ -12,7 +12,9 @@ import (
"github.com/TheTNB/panel/app/models"
"github.com/TheTNB/panel/internal"
"github.com/TheTNB/panel/internal/services"
"github.com/TheTNB/panel/pkg/tools"
"github.com/TheTNB/panel/pkg/io"
"github.com/TheTNB/panel/pkg/shell"
"github.com/TheTNB/panel/pkg/str"
)
type CronController struct {
@@ -65,7 +67,7 @@ func (r *CronController) Add(ctx http.Context) http.Response {
return Error(ctx, http.StatusUnprocessableEntity, "时间格式错误")
}
shell := ctx.Request().Input("script")
script := ctx.Request().Input("script")
cronType := ctx.Request().Input("type")
if cronType == "backup" {
backupType := ctx.Request().Input("backup_type")
@@ -78,7 +80,7 @@ func (r *CronController) Add(ctx http.Context) http.Response {
backupPath = r.setting.Get(models.SettingKeyBackupPath) + "/" + backupType
}
backupSave := ctx.Request().InputInt("save", 10)
shell = `#!/bin/bash
script = `#!/bin/bash
export PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:$PATH
# 耗子面板 - 数据备份脚本
@@ -95,7 +97,7 @@ panel backup ${type} ${name} ${path} ${save} 2>&1
if cronType == "cutoff" {
website := ctx.Request().Input("website")
save := ctx.Request().InputInt("save", 180)
shell = `#!/bin/bash
script = `#!/bin/bash
export PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:$PATH
# 耗子面板 - 日志切割脚本
@@ -110,17 +112,17 @@ panel cutoff ${name} ${save} 2>&1
shellDir := "/www/server/cron/"
shellLogDir := "/www/server/cron/logs/"
if !tools.Exists(shellDir) {
if !io.Exists(shellDir) {
return Error(ctx, http.StatusInternalServerError, "计划任务目录不存在")
}
if !tools.Exists(shellLogDir) {
if !io.Exists(shellLogDir) {
return Error(ctx, http.StatusInternalServerError, "计划任务日志目录不存在")
}
shellFile := strconv.Itoa(int(carbon.Now().Timestamp())) + tools.RandomString(16)
if err := tools.Write(shellDir+shellFile+".sh", shell, 0700); err != nil {
shellFile := strconv.Itoa(int(carbon.Now().Timestamp())) + str.RandomString(16)
if err := io.Write(shellDir+shellFile+".sh", script, 0700); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
if out, err := tools.Exec("dos2unix " + shellDir + shellFile + ".sh"); err != nil {
if out, err := shell.Execf("dos2unix " + shellDir + shellFile + ".sh"); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
@@ -156,12 +158,12 @@ func (r *CronController) Script(ctx http.Context) http.Response {
return Error(ctx, http.StatusUnprocessableEntity, "计划任务不存在")
}
shell, err := tools.Read(cron.Shell)
script, err := io.Read(cron.Shell)
if err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
return Success(ctx, shell)
return Success(ctx, script)
}
// Update 更新计划任务
@@ -197,10 +199,10 @@ func (r *CronController) Update(ctx http.Context) http.Response {
return ErrorSystem(ctx)
}
if err := tools.Write(cron.Shell, ctx.Request().Input("script"), 0644); err != nil {
if err := io.Write(cron.Shell, ctx.Request().Input("script"), 0644); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
if out, err := tools.Exec("dos2unix " + cron.Shell); err != nil {
if out, err := shell.Execf("dos2unix " + cron.Shell); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
@@ -226,7 +228,7 @@ func (r *CronController) Delete(ctx http.Context) http.Response {
if err := r.cron.DeleteFromSystem(cron); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
if err := tools.Remove(cron.Shell); err != nil {
if err := io.Remove(cron.Shell); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -280,11 +282,11 @@ func (r *CronController) Log(ctx http.Context) http.Response {
return Error(ctx, http.StatusUnprocessableEntity, "计划任务不存在")
}
if !tools.Exists(cron.Log) {
if !io.Exists(cron.Log) {
return Error(ctx, http.StatusUnprocessableEntity, "日志文件不存在")
}
log, err := tools.Exec("tail -n 1000 " + cron.Log)
log, err := shell.Execf("tail -n 1000 " + cron.Log)
if err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
+46 -36
View File
@@ -2,9 +2,10 @@ package controllers
import (
"fmt"
"io"
"os"
stdio "io"
stdos "os"
"path/filepath"
"strconv"
"strings"
"syscall"
@@ -12,7 +13,10 @@ import (
"github.com/goravel/framework/support/carbon"
requests "github.com/TheTNB/panel/app/http/requests/file"
"github.com/TheTNB/panel/pkg/tools"
"github.com/TheTNB/panel/pkg/io"
"github.com/TheTNB/panel/pkg/os"
"github.com/TheTNB/panel/pkg/shell"
"github.com/TheTNB/panel/pkg/str"
)
type FileController struct {
@@ -42,11 +46,11 @@ func (r *FileController) Create(ctx http.Context) http.Response {
isDir := ctx.Request().InputBool("dir")
if !isDir {
if out, err := tools.Exec("touch " + request.Path); err != nil {
if out, err := shell.Execf("touch " + request.Path); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
} else {
if err := tools.Mkdir(request.Path, 0755); err != nil {
if err := io.Mkdir(request.Path, 0755); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
}
@@ -73,7 +77,7 @@ func (r *FileController) Content(ctx http.Context) http.Response {
return sanitize
}
fileInfo, err := tools.FileInfo(request.Path)
fileInfo, err := io.FileInfo(request.Path)
if err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -84,7 +88,7 @@ func (r *FileController) Content(ctx http.Context) http.Response {
return Error(ctx, http.StatusInternalServerError, "文件大小超过 10 M,不支持在线编辑")
}
content, err := tools.Read(request.Path)
content, err := io.Read(request.Path)
if err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -110,11 +114,11 @@ func (r *FileController) Save(ctx http.Context) http.Response {
return sanitize
}
fileInfo, err := tools.FileInfo(request.Path)
fileInfo, err := io.FileInfo(request.Path)
if err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
if err = tools.Write(request.Path, request.Content, fileInfo.Mode()); err != nil {
if err = io.Write(request.Path, request.Content, fileInfo.Mode()); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -140,7 +144,7 @@ func (r *FileController) Delete(ctx http.Context) http.Response {
return sanitize
}
if err := tools.Remove(request.Path); err != nil {
if err := io.Remove(request.Path); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -172,16 +176,16 @@ func (r *FileController) Upload(ctx http.Context) http.Response {
}
defer src.Close()
if tools.Exists(request.Path) && !ctx.Request().InputBool("force") {
if io.Exists(request.Path) && !ctx.Request().InputBool("force") {
return Error(ctx, http.StatusForbidden, "目标路径已存在,是否覆盖?")
}
data, err := io.ReadAll(src)
data, err := stdio.ReadAll(src)
if err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
if err = tools.Write(request.Path, string(data), 0755); err != nil {
if err = io.Write(request.Path, string(data), 0755); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -207,11 +211,11 @@ func (r *FileController) Move(ctx http.Context) http.Response {
return sanitize
}
if tools.Exists(request.Target) && !ctx.Request().InputBool("force") {
if io.Exists(request.Target) && !ctx.Request().InputBool("force") {
return Error(ctx, http.StatusForbidden, "目标路径"+request.Target+"已存在")
}
if err := tools.Mv(request.Source, request.Target); err != nil {
if err := io.Mv(request.Source, request.Target); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -237,11 +241,11 @@ func (r *FileController) Copy(ctx http.Context) http.Response {
return sanitize
}
if tools.Exists(request.Target) && !ctx.Request().InputBool("force") {
if io.Exists(request.Target) && !ctx.Request().InputBool("force") {
return Error(ctx, http.StatusForbidden, "目标路径"+request.Target+"已存在")
}
if err := tools.Cp(request.Source, request.Target); err != nil {
if err := io.Cp(request.Source, request.Target); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -267,7 +271,7 @@ func (r *FileController) Download(ctx http.Context) http.Response {
return sanitize
}
info, err := tools.FileInfo(request.Path)
info, err := io.FileInfo(request.Path)
if err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -318,14 +322,14 @@ func (r *FileController) Info(ctx http.Context) http.Response {
return sanitize
}
fileInfo, err := tools.FileInfo(request.Path)
fileInfo, err := io.FileInfo(request.Path)
if err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
return Success(ctx, http.Json{
"name": fileInfo.Name(),
"size": tools.FormatBytes(float64(fileInfo.Size())),
"size": str.FormatBytes(float64(fileInfo.Size())),
"mode_str": fileInfo.Mode().String(),
"mode": fmt.Sprintf("%04o", fileInfo.Mode().Perm()),
"dir": fileInfo.IsDir(),
@@ -351,10 +355,16 @@ func (r *FileController) Permission(ctx http.Context) http.Response {
return sanitize
}
if err := tools.Chmod(request.Path, os.FileMode(request.Mode)); err != nil {
// 解析成8进制
mode, err := strconv.ParseUint(request.Mode, 8, 64)
if err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
if err := tools.Chown(request.Path, request.Owner, request.Group); err != nil {
if err = io.Chmod(request.Path, stdos.FileMode(mode)); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
if err = io.Chown(request.Path, request.Owner, request.Group); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -379,7 +389,7 @@ func (r *FileController) Archive(ctx http.Context) http.Response {
return sanitize
}
if err := tools.Archive(request.Paths, request.File); err != nil {
if err := io.Archive(request.Paths, request.File); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -405,7 +415,7 @@ func (r *FileController) UnArchive(ctx http.Context) http.Response {
return sanitize
}
if err := tools.UnArchive(request.File, request.Path); err != nil {
if err := io.UnArchive(request.File, request.Path); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -430,8 +440,8 @@ func (r *FileController) Search(ctx http.Context) http.Response {
return sanitize
}
paths := make(map[string]os.FileInfo)
err := filepath.Walk(request.Path, func(path string, info os.FileInfo, err error) error {
paths := make(map[string]stdos.FileInfo)
err := filepath.Walk(request.Path, func(path string, info stdos.FileInfo, err error) error {
if err != nil {
return err
}
@@ -465,7 +475,7 @@ func (r *FileController) List(ctx http.Context) http.Response {
return sanitize
}
fileInfoList, err := os.ReadDir(request.Path)
fileInfoList, err := io.ReadDir(request.Path)
if err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -478,16 +488,16 @@ func (r *FileController) List(ctx http.Context) http.Response {
paths = append(paths, map[string]any{
"name": info.Name(),
"full": filepath.Join(request.Path, info.Name()),
"size": tools.FormatBytes(float64(info.Size())),
"size": str.FormatBytes(float64(info.Size())),
"mode_str": info.Mode().String(),
"mode": fmt.Sprintf("%04o", info.Mode().Perm()),
"owner": tools.GetUser(stat.Uid),
"group": tools.GetGroup(stat.Gid),
"owner": os.GetUser(stat.Uid),
"group": os.GetGroup(stat.Gid),
"uid": stat.Uid,
"gid": stat.Gid,
"hidden": tools.IsHidden(info.Name()),
"symlink": tools.IsSymlink(info.Mode()),
"link": tools.GetSymlink(filepath.Join(request.Path, info.Name())),
"hidden": io.IsHidden(info.Name()),
"symlink": io.IsSymlink(info.Mode()),
"link": io.GetSymlink(filepath.Join(request.Path, info.Name())),
"dir": info.IsDir(),
"modify": carbon.FromStdTime(info.ModTime()).ToDateTimeString(),
})
@@ -502,7 +512,7 @@ func (r *FileController) List(ctx http.Context) http.Response {
}
// setPermission
func (r *FileController) setPermission(path string, mode uint, owner, group string) {
_ = tools.Chmod(path, os.FileMode(mode))
_ = tools.Chown(path, owner, group)
func (r *FileController) setPermission(path string, mode stdos.FileMode, owner, group string) {
_ = io.Chmod(path, mode)
_ = io.Chown(path, owner, group)
}
+7 -5
View File
@@ -12,8 +12,10 @@ import (
"github.com/TheTNB/panel/app/models"
"github.com/TheTNB/panel/internal"
"github.com/TheTNB/panel/internal/services"
"github.com/TheTNB/panel/pkg/shell"
"github.com/TheTNB/panel/pkg/systemctl"
"github.com/TheTNB/panel/pkg/tools"
"github.com/TheTNB/panel/types"
"github.com/TheTNB/panel/pkg/types"
)
type MenuItem struct {
@@ -108,7 +110,7 @@ func (r *InfoController) CountInfo(ctx http.Context) http.Response {
}
var databaseCount int64
if mysqlInstalled {
status, err := tools.ServiceStatus("mysqld")
status, err := systemctl.Status("mysqld")
if status && err == nil {
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
type database struct {
@@ -150,9 +152,9 @@ func (r *InfoController) CountInfo(ctx http.Context) http.Response {
}
}
if postgresqlInstalled {
status, err := tools.ServiceStatus("postgresql")
status, err := systemctl.Status("postgresql")
if status && err == nil {
raw, err := tools.Exec(`echo "\l" | su - postgres -c "psql"`)
raw, err := shell.Execf(`echo "\l" | su - postgres -c "psql"`)
if err == nil {
databases := strings.Split(raw, "\n")
if len(databases) >= 4 {
@@ -173,7 +175,7 @@ func (r *InfoController) CountInfo(ctx http.Context) http.Response {
var ftpCount int64
var ftpPlugin = r.plugin.GetInstalledBySlug("pureftpd")
if ftpPlugin.ID != 0 {
listRaw, err := tools.Exec("pure-pw list")
listRaw, err := shell.Execf("pure-pw list")
if len(listRaw) != 0 && err == nil {
listArr := strings.Split(listRaw, "\n")
ftpCount = int64(len(listArr))
@@ -12,8 +12,11 @@ import (
"github.com/TheTNB/panel/app/models"
"github.com/TheTNB/panel/internal"
"github.com/TheTNB/panel/internal/services"
"github.com/TheTNB/panel/pkg/tools"
"github.com/TheTNB/panel/types"
"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 Fail2banController struct {
@@ -28,7 +31,7 @@ func NewFail2banController() *Fail2banController {
// List 所有 Fail2ban 规则
func (r *Fail2banController) List(ctx http.Context) http.Response {
raw, err := tools.Read("/etc/fail2ban/jail.local")
raw, err := io.Read("/etc/fail2ban/jail.local")
if err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, err.Error())
}
@@ -45,7 +48,7 @@ func (r *Fail2banController) List(ctx http.Context) http.Response {
}
jailName := jail[1]
jailRaw := tools.Cut(raw, "# "+jailName+"-START", "# "+jailName+"-END")
jailRaw := str.Cut(raw, "# "+jailName+"-START", "# "+jailName+"-END")
if len(jailRaw) == 0 {
continue
}
@@ -97,7 +100,7 @@ func (r *Fail2banController) Add(ctx http.Context) http.Response {
jailWebsiteMode := ctx.Request().Input("website_mode")
jailWebsitePath := ctx.Request().Input("website_path")
raw, err := tools.Read("/etc/fail2ban/jail.local")
raw, err := io.Read("/etc/fail2ban/jail.local")
if err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, err.Error())
}
@@ -136,7 +139,7 @@ logpath = /www/wwwlogs/` + website.Name + `.log
# ` + jailWebsiteName + `-` + jailWebsiteMode + `-END
`
raw += rule
if err = tools.Write("/etc/fail2ban/jail.local", raw, 0644); err != nil {
if err = io.Write("/etc/fail2ban/jail.local", raw, 0644); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "写入Fail2ban规则失败")
}
@@ -154,7 +157,7 @@ failregex = ^<HOST>\s-.*\s` + jailWebsitePath + `.*HTTP/.*$
ignoreregex =
`
}
if err = tools.Write("/etc/fail2ban/filter.d/haozi-"+jailWebsiteName+"-"+jailWebsiteMode+".conf", filter, 0644); err != nil {
if err = io.Write("/etc/fail2ban/filter.d/haozi-"+jailWebsiteName+"-"+jailWebsiteMode+".conf", filter, 0644); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "写入Fail2ban规则失败")
}
@@ -165,21 +168,21 @@ ignoreregex =
var err error
switch jailName {
case "ssh":
if tools.IsDebian() {
if os.IsDebian() {
logPath = "/var/log/auth.log"
} else {
logPath = "/var/log/secure"
}
filter = "sshd"
port, err = tools.Exec("cat /etc/ssh/sshd_config | grep 'Port ' | awk '{print $2}'")
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 = tools.Exec("cat /www/server/mysql/conf/my.cnf | grep 'port' | head -n 1 | awk '{print $3}'")
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 = tools.Exec(`cat /www/server/pure-ftpd/etc/pure-ftpd.conf | grep "Bind" | awk '{print $2}' | awk -F "," '{print $2}'`)
port, err = shell.Execf(`cat /www/server/pure-ftpd/etc/pure-ftpd.conf | grep "Bind" | awk '{print $2}' | awk -F "," '{print $2}'`)
default:
return controllers.Error(ctx, http.StatusUnprocessableEntity, "未知服务")
}
@@ -201,12 +204,12 @@ logpath = ` + logPath + `
# ` + jailName + `-END
`
raw += rule
if err := tools.Write("/etc/fail2ban/jail.local", raw, 0644); err != nil {
if err := io.Write("/etc/fail2ban/jail.local", raw, 0644); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "写入Fail2ban规则失败")
}
}
if _, err := tools.Exec("fail2ban-client reload"); err != nil {
if _, err := shell.Execf("fail2ban-client reload"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "重载配置失败")
}
@@ -216,7 +219,7 @@ logpath = ` + logPath + `
// Delete 删除规则
func (r *Fail2banController) Delete(ctx http.Context) http.Response {
jailName := ctx.Request().Input("name")
raw, err := tools.Read("/etc/fail2ban/jail.local")
raw, err := io.Read("/etc/fail2ban/jail.local")
if err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, err.Error())
}
@@ -224,14 +227,14 @@ func (r *Fail2banController) Delete(ctx http.Context) http.Response {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "规则不存在")
}
rule := tools.Cut(raw, "# "+jailName+"-START", "# "+jailName+"-END")
rule := str.Cut(raw, "# "+jailName+"-START", "# "+jailName+"-END")
raw = strings.Replace(raw, "\n# "+jailName+"-START"+rule+"# "+jailName+"-END", "", -1)
raw = strings.TrimSpace(raw)
if err := tools.Write("/etc/fail2ban/jail.local", raw, 0644); err != nil {
if err := io.Write("/etc/fail2ban/jail.local", raw, 0644); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "写入Fail2ban规则失败")
}
if _, err := tools.Exec("fail2ban-client reload"); err != nil {
if _, err := shell.Execf("fail2ban-client reload"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "重载配置失败")
}
@@ -245,15 +248,15 @@ func (r *Fail2banController) BanList(ctx http.Context) http.Response {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "缺少参数")
}
currentlyBan, err := tools.Exec(`fail2ban-client status ` + name + ` | grep "Currently banned" | awk '{print $4}'`)
currentlyBan, err := shell.Execf(`fail2ban-client status %s | grep "Currently banned" | awk '{print $4}'`, name)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取封禁列表失败")
}
totalBan, err := tools.Exec(`fail2ban-client status ` + name + ` | grep "Total banned" | awk '{print $4}'`)
totalBan, err := shell.Execf(`fail2ban-client status %s | grep "Total banned" | awk '{print $4}'`, name)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取封禁列表失败")
}
bannedIp, err := tools.Exec(`fail2ban-client status ` + name + ` | grep "Banned IP list" | awk -F ":" '{print $2}'`)
bannedIp, err := shell.Execf(`fail2ban-client status %s | grep "Banned IP list" | awk -F ":" '{print $2}'`, name)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取封禁列表失败")
}
@@ -287,7 +290,7 @@ func (r *Fail2banController) Unban(ctx http.Context) http.Response {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "缺少参数")
}
if _, err := tools.Exec("fail2ban-client set " + name + " unbanip " + ip); err != nil {
if _, err := shell.Execf("fail2ban-client set %s unbanip %s", name, ip); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "解封失败")
}
@@ -301,7 +304,7 @@ func (r *Fail2banController) SetWhiteList(ctx http.Context) http.Response {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "缺少参数")
}
raw, err := tools.Read("/etc/fail2ban/jail.local")
raw, err := io.Read("/etc/fail2ban/jail.local")
if err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, err.Error())
}
@@ -313,11 +316,11 @@ func (r *Fail2banController) SetWhiteList(ctx http.Context) http.Response {
return controllers.Error(ctx, http.StatusInternalServerError, "解析Fail2ban规则失败,Fail2ban可能已损坏")
}
if err := tools.Write("/etc/fail2ban/jail.local", raw, 0644); err != nil {
if err := io.Write("/etc/fail2ban/jail.local", raw, 0644); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "写入Fail2ban规则失败")
}
if _, err := tools.Exec("fail2ban-client reload"); err != nil {
if _, err := shell.Execf("fail2ban-client reload"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "重载配置失败")
}
return controllers.Success(ctx, nil)
@@ -325,7 +328,7 @@ func (r *Fail2banController) SetWhiteList(ctx http.Context) http.Response {
// GetWhiteList 获取白名单
func (r *Fail2banController) GetWhiteList(ctx http.Context) http.Response {
raw, err := tools.Read("/etc/fail2ban/jail.local")
raw, err := io.Read("/etc/fail2ban/jail.local")
if err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, err.Error())
}
@@ -7,7 +7,8 @@ import (
"github.com/TheTNB/panel/app/http/controllers"
requests "github.com/TheTNB/panel/app/http/requests/plugins/frp"
"github.com/TheTNB/panel/pkg/tools"
"github.com/TheTNB/panel/pkg/io"
"github.com/TheTNB/panel/pkg/systemctl"
)
type FrpController struct {
@@ -34,7 +35,7 @@ func (r *FrpController) GetConfig(ctx http.Context) http.Response {
return sanitize
}
config, err := tools.Read(fmt.Sprintf("/www/server/frp/%s.toml", serviceRequest.Service))
config, err := io.Read(fmt.Sprintf("/www/server/frp/%s.toml", serviceRequest.Service))
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -59,11 +60,11 @@ func (r *FrpController) UpdateConfig(ctx http.Context) http.Response {
return sanitize
}
if err := tools.Write(fmt.Sprintf("/www/server/frp/%s.toml", updateRequest.Service), updateRequest.Config, 0644); err != nil {
if err := io.Write(fmt.Sprintf("/www/server/frp/%s.toml", updateRequest.Service), updateRequest.Config, 0644); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
if err := tools.ServiceRestart(updateRequest.Service); err != nil {
if err := systemctl.Restart(updateRequest.Service); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -5,7 +5,8 @@ import (
"github.com/TheTNB/panel/app/http/controllers"
requests "github.com/TheTNB/panel/app/http/requests/plugins/gitea"
"github.com/TheTNB/panel/pkg/tools"
"github.com/TheTNB/panel/pkg/io"
"github.com/TheTNB/panel/pkg/systemctl"
)
type GiteaController struct {
@@ -25,7 +26,7 @@ func NewGiteaController() *GiteaController {
// @Success 200 {object} controllers.SuccessResponse
// @Router /plugins/gitea/config [get]
func (r *GiteaController) GetConfig(ctx http.Context) http.Response {
config, err := tools.Read("/www/server/gitea/app.ini")
config, err := io.Read("/www/server/gitea/app.ini")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -50,11 +51,11 @@ func (r *GiteaController) UpdateConfig(ctx http.Context) http.Response {
return sanitize
}
if err := tools.Write("/www/server/gitea/app.ini", updateRequest.Config, 0644); err != nil {
if err := io.Write("/www/server/gitea/app.ini", updateRequest.Config, 0644); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
if err := tools.ServiceRestart("gitea"); err != nil {
if err := systemctl.Restart("gitea"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
+102 -151
View File
@@ -1,7 +1,6 @@
package plugins
import (
"database/sql"
"fmt"
"regexp"
@@ -12,8 +11,12 @@ import (
"github.com/TheTNB/panel/app/models"
"github.com/TheTNB/panel/internal"
"github.com/TheTNB/panel/internal/services"
"github.com/TheTNB/panel/pkg/tools"
"github.com/TheTNB/panel/types"
"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 MySQLController struct {
@@ -30,7 +33,7 @@ func NewMySQLController() *MySQLController {
// GetConfig 获取配置
func (r *MySQLController) GetConfig(ctx http.Context) http.Response {
config, err := tools.Read("/www/server/mysql/conf/my.cnf")
config, err := io.Read("/www/server/mysql/conf/my.cnf")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取MySQL配置失败")
}
@@ -45,11 +48,11 @@ func (r *MySQLController) SaveConfig(ctx http.Context) http.Response {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "配置不能为空")
}
if err := tools.Write("/www/server/mysql/conf/my.cnf", config, 0644); err != nil {
if err := io.Write("/www/server/mysql/conf/my.cnf", config, 0644); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "写入MySQL配置失败")
}
if err := tools.ServiceReload("mysqld"); err != nil {
if err := systemctl.Reload("mysqld"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "重载MySQL失败")
}
@@ -63,12 +66,12 @@ func (r *MySQLController) Load(ctx http.Context) http.Response {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "MySQL root密码为空")
}
status, _ := tools.ServiceStatus("mysqld")
status, _ := systemctl.Status("mysqld")
if !status {
return controllers.Success(ctx, []types.NV{})
}
raw, err := tools.Exec("/www/server/mysql/bin/mysqladmin -uroot -p" + rootPassword + " extended-status 2>&1")
raw, err := shell.Execf("mysqladmin -uroot -p" + rootPassword + " extended-status 2>&1")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取MySQL负载失败")
}
@@ -104,7 +107,7 @@ func (r *MySQLController) Load(ctx http.Context) http.Response {
if len(matches) > 1 {
d := map[string]string{"name": expression.name, "value": matches[1]}
if expression.name == "发送" || expression.name == "接收" {
d["value"] = tools.FormatBytes(cast.ToFloat64(matches[1]))
d["value"] = str.FormatBytes(cast.ToFloat64(matches[1]))
}
data = append(data, d)
@@ -125,7 +128,7 @@ func (r *MySQLController) Load(ctx http.Context) http.Response {
// ErrorLog 获取错误日志
func (r *MySQLController) ErrorLog(ctx http.Context) http.Response {
log, err := tools.Exec("tail -n 100 /www/server/mysql/mysql-error.log")
log, err := shell.Execf("tail -n 100 /www/server/mysql/mysql-error.log")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, log)
}
@@ -135,7 +138,7 @@ func (r *MySQLController) ErrorLog(ctx http.Context) http.Response {
// ClearErrorLog 清空错误日志
func (r *MySQLController) ClearErrorLog(ctx http.Context) http.Response {
if out, err := tools.Exec("echo '' > /www/server/mysql/mysql-error.log"); err != nil {
if out, err := shell.Execf("echo '' > /www/server/mysql/mysql-error.log"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
@@ -144,7 +147,7 @@ func (r *MySQLController) ClearErrorLog(ctx http.Context) http.Response {
// SlowLog 获取慢查询日志
func (r *MySQLController) SlowLog(ctx http.Context) http.Response {
log, err := tools.Exec("tail -n 100 /www/server/mysql/mysql-slow.log")
log, err := shell.Execf("tail -n 100 /www/server/mysql/mysql-slow.log")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, log)
}
@@ -154,7 +157,7 @@ func (r *MySQLController) SlowLog(ctx http.Context) http.Response {
// ClearSlowLog 清空慢查询日志
func (r *MySQLController) ClearSlowLog(ctx http.Context) http.Response {
if out, err := tools.Exec("echo '' > /www/server/mysql/mysql-slow.log"); err != nil {
if out, err := shell.Execf("echo '' > /www/server/mysql/mysql-slow.log"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
return controllers.Success(ctx, nil)
@@ -172,80 +175,45 @@ func (r *MySQLController) GetRootPassword(ctx http.Context) http.Response {
// SetRootPassword 设置root密码
func (r *MySQLController) SetRootPassword(ctx http.Context) http.Response {
status, err := tools.ServiceStatus("mysqld")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取MySQL状态失败")
}
if !status {
return controllers.Error(ctx, http.StatusInternalServerError, "MySQL 未运行")
}
rootPassword := ctx.Request().Input("password")
if len(rootPassword) == 0 {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "MySQL root密码不能为空")
}
oldRootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
if oldRootPassword != rootPassword {
if _, err = tools.Exec(fmt.Sprintf(`/www/server/mysql/bin/mysql -uroot -p%s -e "ALTER USER 'root'@'localhost' IDENTIFIED BY '%s';"`, oldRootPassword, rootPassword)); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, fmt.Sprintf("设置root密码失败: %v", err))
mysql, err := db.NewMySQL("root", oldRootPassword, r.getSock(), "unix")
if err != nil {
// 尝试安全模式直接改密
if err = db.MySQLResetRootPassword(rootPassword); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
if _, err = tools.Exec(fmt.Sprintf(`/www/server/mysql/bin/mysql -uroot -p%s -e "FLUSH PRIVILEGES;"`, rootPassword)); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "设置root密码失败")
}
if err = r.setting.Set(models.SettingKeyMysqlRootPassword, rootPassword); err != nil {
_, _ = tools.Exec(fmt.Sprintf(`/www/server/mysql/bin/mysql -uroot -p%s -e "ALTER USER 'root'@'localhost' IDENTIFIED BY '%s';"`, rootPassword, oldRootPassword))
_, _ = tools.Exec(fmt.Sprintf(`/www/server/mysql/bin/mysql -uroot -p%s -e "FLUSH PRIVILEGES;"`, oldRootPassword))
return controllers.Error(ctx, http.StatusInternalServerError, fmt.Sprintf("设置保存失败: %v", err))
} else {
if err = mysql.UserPassword("root", rootPassword); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
}
if err = r.setting.Set(models.SettingKeyMysqlRootPassword, rootPassword); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, fmt.Sprintf("设置保存失败: %v", err))
}
return controllers.Success(ctx, nil)
}
// DatabaseList 获取数据库列表
func (r *MySQLController) DatabaseList(ctx http.Context) http.Response {
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
type database struct {
Name string `json:"name"`
}
db, err := sql.Open("mysql", "root:"+rootPassword+"@unix(/tmp/mysql.sock)/")
password := r.setting.Get(models.SettingKeyMysqlRootPassword)
mysql, err := db.NewMySQL("root", password, r.getSock(), "unix")
if err != nil {
return controllers.Success(ctx, http.Json{
"total": 0,
"items": []database{},
})
}
defer db.Close()
if err = db.Ping(); err != nil {
return controllers.Success(ctx, http.Json{
"total": 0,
"items": []database{},
"items": []types.MySQLDatabase{},
})
}
rows, err := db.Query("SHOW DATABASES")
databases, err := mysql.Databases()
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
defer rows.Close()
var databases []database
for rows.Next() {
var d database
if err = rows.Scan(&d.Name); err != nil {
continue
}
databases = append(databases, d)
}
if err = rows.Err(); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取数据库列表失败")
}
paged, total := controllers.Paginate(ctx, databases)
return controllers.Success(ctx, http.Json{
@@ -269,17 +237,18 @@ func (r *MySQLController) AddDatabase(ctx http.Context) http.Response {
user := ctx.Request().Input("user")
password := ctx.Request().Input("password")
if out, err := tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"CREATE DATABASE IF NOT EXISTS " + database + " DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_general_ci;\""); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
mysql, err := db.NewMySQL("root", rootPassword, r.getSock(), "unix")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
if out, err := tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"CREATE USER '" + user + "'@'localhost' IDENTIFIED BY '" + password + "';\""); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
if err = mysql.DatabaseCreate(database); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
if out, err := tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"GRANT ALL PRIVILEGES ON " + database + ".* TO '" + user + "'@'localhost';\""); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
if err = mysql.UserCreate(user, password); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
if out, err := tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"FLUSH PRIVILEGES;\""); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
if err = mysql.PrivilegesGrant(user, database); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
return controllers.Success(ctx, nil)
@@ -295,8 +264,12 @@ func (r *MySQLController) DeleteDatabase(ctx http.Context) http.Response {
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
database := ctx.Request().Input("database")
if out, err := tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"DROP DATABASE IF EXISTS " + database + ";\""); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
mysql, err := db.NewMySQL("root", rootPassword, r.getSock(), "unix")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
if err = mysql.DatabaseDrop(database); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
return controllers.Success(ctx, nil)
@@ -325,8 +298,8 @@ func (r *MySQLController) UploadBackup(ctx http.Context) http.Response {
}
backupPath := r.setting.Get(models.SettingKeyBackupPath) + "/mysql"
if !tools.Exists(backupPath) {
if err = tools.Mkdir(backupPath, 0644); err != nil {
if !io.Exists(backupPath) {
if err = io.Mkdir(backupPath, 0644); err != nil {
return nil
}
}
@@ -366,7 +339,7 @@ func (r *MySQLController) DeleteBackup(ctx http.Context) http.Response {
backupPath := r.setting.Get(models.SettingKeyBackupPath) + "/mysql"
fileName := ctx.Request().Input("name")
if err := tools.Remove(backupPath + "/" + fileName); err != nil {
if err := io.Remove(backupPath + "/" + fileName); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -391,71 +364,20 @@ func (r *MySQLController) RestoreBackup(ctx http.Context) http.Response {
// UserList 用户列表
func (r *MySQLController) UserList(ctx http.Context) http.Response {
type user struct {
User string `json:"user"`
Host string `json:"host"`
Grants []string `json:"grants"`
}
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
db, err := sql.Open("mysql", "root:"+rootPassword+"@unix(/tmp/mysql.sock)/")
password := r.setting.Get(models.SettingKeyMysqlRootPassword)
mysql, err := db.NewMySQL("root", password, r.getSock(), "unix")
if err != nil {
return controllers.Success(ctx, http.Json{
"total": 0,
"items": []user{},
})
}
defer db.Close()
if err = db.Ping(); err != nil {
return controllers.Success(ctx, http.Json{
"total": 0,
"items": []user{},
"items": []types.MySQLUser{},
})
}
rows, err := db.Query("SELECT user, host FROM mysql.user")
users, err := mysql.Users()
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
defer rows.Close()
var userGrants []user
for rows.Next() {
var u user
if err = rows.Scan(&u.User, &u.Host); err != nil {
continue
}
// 查询用户权限
grantsRows, err := db.Query(fmt.Sprintf("SHOW GRANTS FOR '%s'@'%s'", u.User, u.Host))
if err != nil {
continue
}
defer grantsRows.Close()
for grantsRows.Next() {
var grant string
if err = grantsRows.Scan(&grant); err != nil {
continue
}
u.Grants = append(u.Grants, grant)
}
if err = grantsRows.Err(); err != nil {
continue
}
userGrants = append(userGrants, u)
}
if err = rows.Err(); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取用户列表失败")
}
paged, total := controllers.Paginate(ctx, userGrants)
paged, total := controllers.Paginate(ctx, users)
return controllers.Success(ctx, http.Json{
"total": total,
@@ -477,14 +399,15 @@ func (r *MySQLController) AddUser(ctx http.Context) http.Response {
user := ctx.Request().Input("user")
password := ctx.Request().Input("password")
database := ctx.Request().Input("database")
if out, err := tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"CREATE USER '" + user + "'@'localhost' IDENTIFIED BY '" + password + ";'\""); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
mysql, err := db.NewMySQL("root", rootPassword, r.getSock(), "unix")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
if out, err := tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"GRANT ALL PRIVILEGES ON " + database + ".* TO '" + user + "'@'localhost';\""); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
if err = mysql.UserCreate(user, password); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
if out, err := tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"FLUSH PRIVILEGES;\""); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
if err = mysql.PrivilegesGrant(user, database); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
return controllers.Success(ctx, nil)
@@ -500,8 +423,12 @@ func (r *MySQLController) DeleteUser(ctx http.Context) http.Response {
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
user := ctx.Request().Input("user")
if out, err := tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"DROP USER '" + user + "'@'localhost';\""); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
mysql, err := db.NewMySQL("root", rootPassword, r.getSock(), "unix")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
if err = mysql.UserDrop(user); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
return controllers.Success(ctx, nil)
@@ -519,11 +446,12 @@ func (r *MySQLController) SetUserPassword(ctx http.Context) http.Response {
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
user := ctx.Request().Input("user")
password := ctx.Request().Input("password")
if out, err := tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"ALTER USER '" + user + "'@'localhost' IDENTIFIED BY '" + password + "';\""); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
mysql, err := db.NewMySQL("root", rootPassword, r.getSock(), "unix")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
if out, err := tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"FLUSH PRIVILEGES;\""); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
if err = mysql.UserPassword(user, password); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
return controllers.Success(ctx, nil)
@@ -541,15 +469,38 @@ func (r *MySQLController) SetUserPrivileges(ctx http.Context) http.Response {
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
user := ctx.Request().Input("user")
database := ctx.Request().Input("database")
if out, err := tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"REVOKE ALL PRIVILEGES ON *.* FROM '" + user + "'@'localhost';\""); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
mysql, err := db.NewMySQL("root", rootPassword, r.getSock(), "unix")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
if out, err := tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"GRANT ALL PRIVILEGES ON " + database + ".* TO '" + user + "'@'localhost';\""); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"FLUSH PRIVILEGES;\""); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
if err = mysql.PrivilegesGrant(user, database); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
return controllers.Success(ctx, nil)
}
// getSock 获取sock文件位置
func (r *MySQLController) getSock() string {
if io.Exists("/tmp/mysql.sock") {
return "/tmp/mysql.sock"
}
if io.Exists("/www/server/mysql/config/my.cnf") {
config, _ := io.Read("/www/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"
}
@@ -9,8 +9,11 @@ import (
"github.com/spf13/cast"
"github.com/TheTNB/panel/app/http/controllers"
"github.com/TheTNB/panel/pkg/tools"
"github.com/TheTNB/panel/types"
"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 OpenRestyController struct {
@@ -30,7 +33,7 @@ func NewOpenrestyController() *OpenRestyController {
// @Success 200 {object} controllers.SuccessResponse
// @Router /plugins/openresty/config [get]
func (r *OpenRestyController) GetConfig(ctx http.Context) http.Response {
config, err := tools.Read("/www/server/openresty/conf/nginx.conf")
config, err := io.Read("/www/server/openresty/conf/nginx.conf")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取配置失败")
}
@@ -53,11 +56,11 @@ func (r *OpenRestyController) SaveConfig(ctx http.Context) http.Response {
return controllers.Error(ctx, http.StatusInternalServerError, "配置不能为空")
}
if err := tools.Write("/www/server/openresty/conf/nginx.conf", config, 0644); err != nil {
if err := io.Write("/www/server/openresty/conf/nginx.conf", config, 0644); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "保存配置失败")
}
if err := tools.ServiceReload("openresty"); err != nil {
if err := systemctl.Reload("openresty"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "重载服务失败")
}
@@ -73,11 +76,11 @@ func (r *OpenRestyController) SaveConfig(ctx http.Context) http.Response {
// @Success 200 {object} controllers.SuccessResponse
// @Router /plugins/openresty/errorLog [get]
func (r *OpenRestyController) ErrorLog(ctx http.Context) http.Response {
if !tools.Exists("/www/wwwlogs/nginx_error.log") {
if !io.Exists("/www/wwwlogs/nginx_error.log") {
return controllers.Success(ctx, "")
}
out, err := tools.Exec("tail -n 100 /www/wwwlogs/openresty_error.log")
out, err := shell.Execf("tail -n 100 /www/wwwlogs/openresty_error.log")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
@@ -94,7 +97,7 @@ func (r *OpenRestyController) ErrorLog(ctx http.Context) http.Response {
// @Success 200 {object} controllers.SuccessResponse
// @Router /plugins/openresty/clearErrorLog [post]
func (r *OpenRestyController) ClearErrorLog(ctx http.Context) http.Response {
if out, err := tools.Exec("echo '' > /www/wwwlogs/openresty_error.log"); err != nil {
if out, err := shell.Execf("echo '' > /www/wwwlogs/openresty_error.log"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
@@ -119,7 +122,7 @@ func (r *OpenRestyController) Load(ctx http.Context) http.Response {
raw := resp.String()
var data []types.NV
workers, err := tools.Exec("ps aux | grep nginx | grep 'worker process' | wc -l")
workers, err := shell.Execf("ps aux | grep nginx | grep 'worker process' | wc -l")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取负载失败")
}
@@ -128,11 +131,11 @@ func (r *OpenRestyController) Load(ctx http.Context) http.Response {
Value: workers,
})
out, err := tools.Exec("ps aux | grep nginx | grep 'worker process' | awk '{memsum+=$6};END {print memsum}'")
out, err := shell.Execf("ps aux | grep nginx | grep 'worker process' | awk '{memsum+=$6};END {print memsum}'")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取负载失败")
}
mem := tools.FormatBytes(cast.ToFloat64(out))
mem := str.FormatBytes(cast.ToFloat64(out))
data = append(data, types.NV{
Name: "内存占用",
Value: mem,
@@ -1,9 +1,10 @@
package plugins
import (
"github.com/goravel/framework/contracts/http"
"github.com/TheTNB/panel/app/http/controllers"
"github.com/TheTNB/panel/internal/services"
"github.com/goravel/framework/contracts/http"
)
type PHPController struct{}
@@ -1,7 +1,6 @@
package plugins
import (
"os"
"regexp"
"strings"
@@ -10,7 +9,10 @@ import (
"github.com/spf13/cast"
"github.com/TheTNB/panel/app/http/controllers"
"github.com/TheTNB/panel/pkg/tools"
"github.com/TheTNB/panel/pkg/io"
"github.com/TheTNB/panel/pkg/os"
"github.com/TheTNB/panel/pkg/shell"
"github.com/TheTNB/panel/pkg/systemctl"
)
type PhpMyAdminController struct {
@@ -21,7 +23,7 @@ func NewPhpMyAdminController() *PhpMyAdminController {
}
func (r *PhpMyAdminController) Info(ctx http.Context) http.Response {
files, err := os.ReadDir("/www/server/phpmyadmin")
files, err := io.ReadDir("/www/server/phpmyadmin")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "找不到 phpMyAdmin 目录")
}
@@ -36,7 +38,7 @@ func (r *PhpMyAdminController) Info(ctx http.Context) http.Response {
return controllers.Error(ctx, http.StatusInternalServerError, "找不到 phpMyAdmin 目录")
}
conf, err := tools.Read("/www/server/vhost/phpmyadmin.conf")
conf, err := io.Read("/www/server/vhost/phpmyadmin.conf")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -52,40 +54,40 @@ func (r *PhpMyAdminController) Info(ctx http.Context) http.Response {
}
func (r *PhpMyAdminController) SetPort(ctx http.Context) http.Response {
port := ctx.Request().Input("port")
if len(port) == 0 {
port := ctx.Request().InputInt("port")
if port == 0 {
return controllers.Error(ctx, http.StatusInternalServerError, "端口不能为空")
}
conf, err := tools.Read("/www/server/vhost/phpmyadmin.conf")
conf, err := io.Read("/www/server/vhost/phpmyadmin.conf")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
conf = regexp.MustCompile(`listen\s+(\d+);`).ReplaceAllString(conf, "listen "+port+";")
if err := tools.Write("/www/server/vhost/phpmyadmin.conf", conf, 0644); err != nil {
conf = regexp.MustCompile(`listen\s+(\d+);`).ReplaceAllString(conf, "listen "+cast.ToString(port)+";")
if err := io.Write("/www/server/vhost/phpmyadmin.conf", conf, 0644); err != nil {
facades.Log().Request(ctx.Request()).Tags("插件", "phpMyAdmin").With(map[string]any{
"error": err.Error(),
}).Info("修改 phpMyAdmin 端口失败")
return controllers.ErrorSystem(ctx)
}
if tools.IsRHEL() {
if out, err := tools.Exec("firewall-cmd --zone=public --add-port=" + port + "/tcp --permanent"); err != nil {
if os.IsRHEL() {
if out, err := shell.Execf("firewall-cmd --zone=public --add-port=%d/tcp --permanent", port); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec("firewall-cmd --reload"); err != nil {
if out, err := shell.Execf("firewall-cmd --reload"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
} else {
if out, err := tools.Exec("ufw allow " + port + "/tcp"); err != nil {
if out, err := shell.Execf("ufw allow %d/tcp", port); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec("ufw reload"); err != nil {
if out, err := shell.Execf("ufw reload"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
}
if err := tools.ServiceReload("openresty"); err != nil {
if err := systemctl.Reload("openresty"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "重载OpenResty失败")
}
@@ -5,7 +5,8 @@ import (
"github.com/TheTNB/panel/app/http/controllers"
requests "github.com/TheTNB/panel/app/http/requests/plugins/podman"
"github.com/TheTNB/panel/pkg/tools"
"github.com/TheTNB/panel/pkg/io"
"github.com/TheTNB/panel/pkg/systemctl"
)
type PodmanController struct {
@@ -25,7 +26,7 @@ func NewPodmanController() *PodmanController {
// @Success 200 {object} controllers.SuccessResponse
// @Router /plugins/podman/registryConfig [get]
func (r *PodmanController) GetRegistryConfig(ctx http.Context) http.Response {
config, err := tools.Read("/etc/containers/registries.conf")
config, err := io.Read("/etc/containers/registries.conf")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -50,11 +51,11 @@ func (r *PodmanController) UpdateRegistryConfig(ctx http.Context) http.Response
return sanitize
}
if err := tools.Write("/etc/containers/registries.conf", updateRequest.Config, 0644); err != nil {
if err := io.Write("/etc/containers/registries.conf", updateRequest.Config, 0644); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
if err := tools.ServiceRestart("podman"); err != nil {
if err := systemctl.Restart("podman"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -71,7 +72,7 @@ func (r *PodmanController) UpdateRegistryConfig(ctx http.Context) http.Response
// @Success 200 {object} controllers.SuccessResponse
// @Router /plugins/podman/storageConfig [get]
func (r *PodmanController) GetStorageConfig(ctx http.Context) http.Response {
config, err := tools.Read("/etc/containers/storage.conf")
config, err := io.Read("/etc/containers/storage.conf")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -96,11 +97,11 @@ func (r *PodmanController) UpdateStorageConfig(ctx http.Context) http.Response {
return sanitize
}
if err := tools.Write("/etc/containers/storage.conf", updateRequest.Config, 0644); err != nil {
if err := io.Write("/etc/containers/storage.conf", updateRequest.Config, 0644); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
if err := tools.ServiceRestart("podman"); err != nil {
if err := systemctl.Restart("podman"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -11,8 +11,10 @@ import (
"github.com/TheTNB/panel/app/models"
"github.com/TheTNB/panel/internal"
"github.com/TheTNB/panel/internal/services"
"github.com/TheTNB/panel/pkg/tools"
"github.com/TheTNB/panel/types"
"github.com/TheTNB/panel/pkg/io"
"github.com/TheTNB/panel/pkg/shell"
"github.com/TheTNB/panel/pkg/systemctl"
"github.com/TheTNB/panel/pkg/types"
)
type PostgreSQLController struct {
@@ -30,7 +32,7 @@ func NewPostgreSQLController() *PostgreSQLController {
// GetConfig 获取配置
func (r *PostgreSQLController) GetConfig(ctx http.Context) http.Response {
// 获取配置
config, err := tools.Read("/www/server/postgresql/data/postgresql.conf")
config, err := io.Read("/www/server/postgresql/data/postgresql.conf")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL配置失败")
}
@@ -41,7 +43,7 @@ func (r *PostgreSQLController) GetConfig(ctx http.Context) http.Response {
// GetUserConfig 获取用户配置
func (r *PostgreSQLController) GetUserConfig(ctx http.Context) http.Response {
// 获取配置
config, err := tools.Read("/www/server/postgresql/data/pg_hba.conf")
config, err := io.Read("/www/server/postgresql/data/pg_hba.conf")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL配置失败")
}
@@ -56,11 +58,11 @@ func (r *PostgreSQLController) SaveConfig(ctx http.Context) http.Response {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "配置不能为空")
}
if err := tools.Write("/www/server/postgresql/data/postgresql.conf", config, 0644); err != nil {
if err := io.Write("/www/server/postgresql/data/postgresql.conf", config, 0644); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "写入PostgreSQL配置失败")
}
if err := tools.ServiceReload("postgresql"); err != nil {
if err := systemctl.Reload("postgresql"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "重载服务失败")
}
@@ -74,11 +76,11 @@ func (r *PostgreSQLController) SaveUserConfig(ctx http.Context) http.Response {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "配置不能为空")
}
if err := tools.Write("/www/server/postgresql/data/pg_hba.conf", config, 0644); err != nil {
if err := io.Write("/www/server/postgresql/data/pg_hba.conf", config, 0644); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "写入PostgreSQL配置失败")
}
if err := tools.ServiceReload("postgresql"); err != nil {
if err := systemctl.Reload("postgresql"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "重载服务失败")
}
@@ -87,28 +89,28 @@ func (r *PostgreSQLController) SaveUserConfig(ctx http.Context) http.Response {
// Load 获取负载
func (r *PostgreSQLController) Load(ctx http.Context) http.Response {
status, _ := tools.ServiceStatus("postgresql")
status, _ := systemctl.Status("postgresql")
if !status {
return controllers.Success(ctx, []types.NV{})
}
time, err := tools.Exec(`echo "select pg_postmaster_start_time();" | su - postgres -c "psql" | sed -n 3p | cut -d'.' -f1`)
time, err := shell.Execf(`echo "select pg_postmaster_start_time();" | su - postgres -c "psql" | sed -n 3p | cut -d'.' -f1`)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL启动时间失败")
}
pid, err := tools.Exec(`echo "select pg_backend_pid();" | su - postgres -c "psql" | sed -n 3p`)
pid, err := shell.Execf(`echo "select pg_backend_pid();" | su - postgres -c "psql" | sed -n 3p`)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL进程PID失败")
}
process, err := tools.Exec(`ps aux | grep postgres | grep -v grep | wc -l`)
process, err := shell.Execf(`ps aux | grep postgres | grep -v grep | wc -l`)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL进程数失败")
}
connections, err := tools.Exec(`echo "SELECT count(*) FROM pg_stat_activity WHERE NOT pid=pg_backend_pid();" | su - postgres -c "psql" | sed -n 3p`)
connections, err := shell.Execf(`echo "SELECT count(*) FROM pg_stat_activity WHERE NOT pid=pg_backend_pid();" | su - postgres -c "psql" | sed -n 3p`)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL连接数失败")
}
storage, err := tools.Exec(`echo "select pg_size_pretty(pg_database_size('postgres'));" | su - postgres -c "psql" | sed -n 3p`)
storage, err := shell.Execf(`echo "select pg_size_pretty(pg_database_size('postgres'));" | su - postgres -c "psql" | sed -n 3p`)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL空间占用失败")
}
@@ -126,7 +128,7 @@ func (r *PostgreSQLController) Load(ctx http.Context) http.Response {
// Log 获取日志
func (r *PostgreSQLController) Log(ctx http.Context) http.Response {
log, err := tools.Exec("tail -n 100 /www/server/postgresql/logs/postgresql-" + carbon.Now().ToDateString() + ".log")
log, err := shell.Execf("tail -n 100 /www/server/postgresql/logs/postgresql-" + carbon.Now().ToDateString() + ".log")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, log)
}
@@ -136,7 +138,7 @@ func (r *PostgreSQLController) Log(ctx http.Context) http.Response {
// ClearLog 清空日志
func (r *PostgreSQLController) ClearLog(ctx http.Context) http.Response {
if out, err := tools.Exec("echo '' > /www/server/postgresql/logs/postgresql-" + carbon.Now().ToDateString() + ".log"); err != nil {
if out, err := shell.Execf("echo '' > /www/server/postgresql/logs/postgresql-" + carbon.Now().ToDateString() + ".log"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
@@ -211,25 +213,25 @@ func (r *PostgreSQLController) AddDatabase(ctx http.Context) http.Response {
user := ctx.Request().Input("user")
password := ctx.Request().Input("password")
if out, err := tools.Exec(`echo "CREATE DATABASE ` + database + `;" | su - postgres -c "psql"`); err != nil {
if out, err := shell.Execf(`echo "CREATE DATABASE ` + database + `;" | su - postgres -c "psql"`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec(`echo "CREATE USER ` + user + ` WITH PASSWORD '` + password + `';" | su - postgres -c "psql"`); err != nil {
if out, err := shell.Execf(`echo "CREATE USER ` + user + ` WITH PASSWORD '` + password + `';" | su - postgres -c "psql"`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec(`echo "ALTER DATABASE ` + database + ` OWNER TO ` + user + `;" | su - postgres -c "psql"`); err != nil {
if out, err := shell.Execf(`echo "ALTER DATABASE ` + database + ` OWNER TO ` + user + `;" | su - postgres -c "psql"`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec(`echo "GRANT ALL PRIVILEGES ON DATABASE ` + database + ` TO ` + user + `;" | su - postgres -c "psql"`); err != nil {
if out, err := shell.Execf(`echo "GRANT ALL PRIVILEGES ON DATABASE ` + database + ` TO ` + user + `;" | su - postgres -c "psql"`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
userConfig := "host " + database + " " + user + " 127.0.0.1/32 scram-sha-256"
if out, err := tools.Exec(`echo "` + userConfig + `" >> /www/server/postgresql/data/pg_hba.conf`); err != nil {
if out, err := shell.Execf(`echo "` + userConfig + `" >> /www/server/postgresql/data/pg_hba.conf`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if err := tools.ServiceReload("postgresql"); err != nil {
if err := systemctl.Reload("postgresql"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "重载服务失败")
}
@@ -245,7 +247,7 @@ func (r *PostgreSQLController) DeleteDatabase(ctx http.Context) http.Response {
}
database := ctx.Request().Input("database")
if out, err := tools.Exec(`echo "DROP DATABASE ` + database + `;" | su - postgres -c "psql"`); err != nil {
if out, err := shell.Execf(`echo "DROP DATABASE ` + database + `;" | su - postgres -c "psql"`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
@@ -275,8 +277,8 @@ func (r *PostgreSQLController) UploadBackup(ctx http.Context) http.Response {
}
backupPath := r.setting.Get(models.SettingKeyBackupPath) + "/postgresql"
if !tools.Exists(backupPath) {
if err = tools.Mkdir(backupPath, 0644); err != nil {
if !io.Exists(backupPath) {
if err = io.Mkdir(backupPath, 0644); err != nil {
return nil
}
}
@@ -316,7 +318,7 @@ func (r *PostgreSQLController) DeleteBackup(ctx http.Context) http.Response {
backupPath := r.setting.Get(models.SettingKeyBackupPath) + "/postgresql"
fileName := ctx.Request().Input("name")
if err := tools.Remove(backupPath + "/" + fileName); err != nil {
if err := io.Remove(backupPath + "/" + fileName); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -428,19 +430,19 @@ func (r *PostgreSQLController) AddRole(ctx http.Context) http.Response {
user := ctx.Request().Input("user")
password := ctx.Request().Input("password")
database := ctx.Request().Input("database")
if out, err := tools.Exec(`echo "CREATE USER ` + user + ` WITH PASSWORD '` + password + `';" | su - postgres -c "psql"`); err != nil {
if out, err := shell.Execf(`echo "CREATE USER ` + user + ` WITH PASSWORD '` + password + `';" | su - postgres -c "psql"`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec(`echo "GRANT ALL PRIVILEGES ON DATABASE ` + database + ` TO ` + user + `;" | su - postgres -c "psql"`); err != nil {
if out, err := shell.Execf(`echo "GRANT ALL PRIVILEGES ON DATABASE ` + database + ` TO ` + user + `;" | su - postgres -c "psql"`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
userConfig := "host " + database + " " + user + " 127.0.0.1/32 scram-sha-256"
if out, err := tools.Exec(`echo "` + userConfig + `" >> /www/server/postgresql/data/pg_hba.conf`); err != nil {
if out, err := shell.Execf(`echo "` + userConfig + `" >> /www/server/postgresql/data/pg_hba.conf`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if err := tools.ServiceReload("postgresql"); err != nil {
if err := systemctl.Reload("postgresql"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "重载服务失败")
}
@@ -456,14 +458,14 @@ func (r *PostgreSQLController) DeleteRole(ctx http.Context) http.Response {
}
user := ctx.Request().Input("user")
if out, err := tools.Exec(`echo "DROP USER ` + user + `;" | su - postgres -c "psql"`); err != nil {
if out, err := shell.Execf(`echo "DROP USER ` + user + `;" | su - postgres -c "psql"`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec(`sed -i '/` + user + `/d' /www/server/postgresql/data/pg_hba.conf`); err != nil {
if out, err := shell.Execf(`sed -i '/` + user + `/d' /www/server/postgresql/data/pg_hba.conf`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if err := tools.ServiceReload("postgresql"); err != nil {
if err := systemctl.Reload("postgresql"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "重载服务失败")
}
@@ -481,7 +483,7 @@ func (r *PostgreSQLController) SetRolePassword(ctx http.Context) http.Response {
user := ctx.Request().Input("user")
password := ctx.Request().Input("password")
if out, err := tools.Exec(`echo "ALTER USER ` + user + ` WITH PASSWORD '` + password + `';" | su - postgres -c "psql"`); err != nil {
if out, err := shell.Execf(`echo "ALTER USER ` + user + ` WITH PASSWORD '` + password + `';" | su - postgres -c "psql"`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
@@ -8,8 +8,11 @@ import (
"github.com/spf13/cast"
"github.com/TheTNB/panel/app/http/controllers"
"github.com/TheTNB/panel/pkg/tools"
"github.com/TheTNB/panel/types"
"github.com/TheTNB/panel/pkg/io"
"github.com/TheTNB/panel/pkg/os"
"github.com/TheTNB/panel/pkg/shell"
"github.com/TheTNB/panel/pkg/systemctl"
"github.com/TheTNB/panel/pkg/types"
)
type PureFtpdController struct {
@@ -21,7 +24,7 @@ func NewPureFtpdController() *PureFtpdController {
// List 获取用户列表
func (r *PureFtpdController) List(ctx http.Context) http.Response {
listRaw, err := tools.Exec("pure-pw list")
listRaw, err := shell.Execf("pure-pw list")
if err != nil {
return controllers.Success(ctx, http.Json{
"total": 0,
@@ -68,20 +71,20 @@ func (r *PureFtpdController) Add(ctx http.Context) http.Response {
if !strings.HasPrefix(path, "/") {
path = "/" + path
}
if !tools.Exists(path) {
if !io.Exists(path) {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "目录不存在")
}
if err := tools.Chmod(path, 0755); err != nil {
if err := io.Chmod(path, 0755); err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "修改目录权限失败")
}
if err := tools.Chown(path, "www", "www"); err != nil {
if err := io.Chown(path, "www", "www"); err != nil {
return nil
}
if out, err := tools.Exec(`yes '` + password + `' | pure-pw useradd ` + username + ` -u www -g www -d ` + path); err != nil {
if out, err := shell.Execf(`yes '` + password + `' | pure-pw useradd ` + username + ` -u www -g www -d ` + path); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec("pure-pw mkdb"); err != nil {
if out, err := shell.Execf("pure-pw mkdb"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
@@ -98,10 +101,10 @@ func (r *PureFtpdController) Delete(ctx http.Context) http.Response {
username := ctx.Request().Input("username")
if out, err := tools.Exec("pure-pw userdel " + username + " -m"); err != nil {
if out, err := shell.Execf("pure-pw userdel " + username + " -m"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec("pure-pw mkdb"); err != nil {
if out, err := shell.Execf("pure-pw mkdb"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
@@ -120,10 +123,10 @@ func (r *PureFtpdController) ChangePassword(ctx http.Context) http.Response {
username := ctx.Request().Input("username")
password := ctx.Request().Input("password")
if out, err := tools.Exec(`yes '` + password + `' | pure-pw passwd ` + username + ` -m`); err != nil {
if out, err := shell.Execf(`yes '` + password + `' | pure-pw passwd ` + username + ` -m`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec("pure-pw mkdb"); err != nil {
if out, err := shell.Execf("pure-pw mkdb"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
@@ -132,7 +135,7 @@ func (r *PureFtpdController) ChangePassword(ctx http.Context) http.Response {
// GetPort 获取端口
func (r *PureFtpdController) GetPort(ctx http.Context) http.Response {
port, err := tools.Exec(`cat /www/server/pure-ftpd/etc/pure-ftpd.conf | grep "Bind" | awk '{print $2}' | awk -F "," '{print $2}'`)
port, err := shell.Execf(`cat /www/server/pure-ftpd/etc/pure-ftpd.conf | grep "Bind" | awk '{print $2}' | awk -F "," '{print $2}'`)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PureFtpd端口失败")
}
@@ -149,26 +152,26 @@ func (r *PureFtpdController) SetPort(ctx http.Context) http.Response {
}
port := ctx.Request().Input("port")
if out, err := tools.Exec(`sed -i "s/Bind.*/Bind 0.0.0.0,` + port + `/g" /www/server/pure-ftpd/etc/pure-ftpd.conf`); err != nil {
if out, err := shell.Execf(`sed -i "s/Bind.*/Bind 0.0.0.0,%s/g" /www/server/pure-ftpd/etc/pure-ftpd.conf`, port); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if tools.IsRHEL() {
if out, err := tools.Exec("firewall-cmd --zone=public --add-port=" + port + "/tcp --permanent"); err != nil {
if os.IsRHEL() {
if out, err := shell.Execf("firewall-cmd --zone=public --add-port=%s/tcp --permanent", port); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec("firewall-cmd --reload"); err != nil {
if out, err := shell.Execf("firewall-cmd --reload"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
} else {
if out, err := tools.Exec("ufw allow " + port + "/tcp"); err != nil {
if out, err := shell.Execf("ufw allow %s/tcp", port); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec("ufw reload"); err != nil {
if out, err := shell.Execf("ufw reload"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
}
if err := tools.ServiceRestart("pure-ftpd"); err != nil {
if err := systemctl.Restart("pure-ftpd"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -6,8 +6,10 @@ import (
"github.com/goravel/framework/contracts/http"
"github.com/TheTNB/panel/app/http/controllers"
"github.com/TheTNB/panel/pkg/tools"
"github.com/TheTNB/panel/types"
"github.com/TheTNB/panel/pkg/io"
"github.com/TheTNB/panel/pkg/shell"
"github.com/TheTNB/panel/pkg/systemctl"
"github.com/TheTNB/panel/pkg/types"
)
type RedisController struct {
@@ -20,7 +22,7 @@ func NewRedisController() *RedisController {
// GetConfig 获取配置
func (r *RedisController) GetConfig(ctx http.Context) http.Response {
// 获取配置
config, err := tools.Read("/www/server/redis/redis.conf")
config, err := io.Read("/www/server/redis/redis.conf")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取Redis配置失败")
}
@@ -35,11 +37,11 @@ func (r *RedisController) SaveConfig(ctx http.Context) http.Response {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "配置不能为空")
}
if err := tools.Write("/www/server/redis/redis.conf", config, 0644); err != nil {
if err := io.Write("/www/server/redis/redis.conf", config, 0644); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "写入Redis配置失败")
}
if err := tools.ServiceRestart("redis"); err != nil {
if err := systemctl.Restart("redis"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "重启Redis失败")
}
@@ -48,7 +50,7 @@ func (r *RedisController) SaveConfig(ctx http.Context) http.Response {
// Load 获取负载
func (r *RedisController) Load(ctx http.Context) http.Response {
status, err := tools.ServiceStatus("redis")
status, err := systemctl.Status("redis")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取Redis状态失败")
}
@@ -56,7 +58,7 @@ func (r *RedisController) Load(ctx http.Context) http.Response {
return controllers.Error(ctx, http.StatusInternalServerError, "Redis已停止运行")
}
raw, err := tools.Exec("redis-cli info")
raw, err := shell.Execf("redis-cli info")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取Redis负载失败")
}
@@ -8,8 +8,11 @@ import (
"github.com/TheTNB/panel/app/http/controllers"
requests "github.com/TheTNB/panel/app/http/requests/plugins/rsync"
"github.com/TheTNB/panel/pkg/tools"
"github.com/TheTNB/panel/types"
"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 RsyncController struct {
@@ -30,7 +33,7 @@ func NewRsyncController() *RsyncController {
// @Success 200 {object} controllers.SuccessResponse
// @Router /plugins/rsync/modules [get]
func (r *RsyncController) List(ctx http.Context) http.Response {
config, err := tools.Read("/etc/rsyncd.conf")
config, err := io.Read("/etc/rsyncd.conf")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -69,7 +72,7 @@ func (r *RsyncController) List(ctx http.Context) http.Response {
currentModule.ReadOnly = value == "yes" || value == "true"
case "auth users":
currentModule.AuthUser = value
currentModule.Secret, err = tools.Exec("grep -E '^" + currentModule.AuthUser + ":.*$' /etc/rsyncd.secrets | awk -F ':' '{print $2}'")
currentModule.Secret, err = shell.Execf("grep -E '^" + currentModule.AuthUser + ":.*$' /etc/rsyncd.secrets | awk -F ':' '{print $2}'")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取模块"+currentModule.AuthUser+"的密钥失败")
}
@@ -109,7 +112,7 @@ func (r *RsyncController) Create(ctx http.Context) http.Response {
return sanitize
}
config, err := tools.Read("/etc/rsyncd.conf")
config, err := io.Read("/etc/rsyncd.conf")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -128,14 +131,14 @@ secrets file = /etc/rsyncd.secrets
# ` + createRequest.Name + `-END
`
if err := tools.WriteAppend("/etc/rsyncd.conf", conf); err != nil {
if err := io.WriteAppend("/etc/rsyncd.conf", conf); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
if out, err := tools.Exec("echo '" + createRequest.AuthUser + ":" + createRequest.Secret + "' >> /etc/rsyncd.secrets"); err != nil {
if out, err := shell.Execf("echo '" + createRequest.AuthUser + ":" + createRequest.Secret + "' >> /etc/rsyncd.secrets"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if err := tools.ServiceRestart("rsyncd"); err != nil {
if err := systemctl.Restart("rsyncd"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -158,7 +161,7 @@ func (r *RsyncController) Destroy(ctx http.Context) http.Response {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "name 不能为空")
}
config, err := tools.Read("/etc/rsyncd.conf")
config, err := io.Read("/etc/rsyncd.conf")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -166,22 +169,22 @@ func (r *RsyncController) Destroy(ctx http.Context) http.Response {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "模块 "+name+" 不存在")
}
module := tools.Cut(config, "# "+name+"-START", "# "+name+"-END")
module := str.Cut(config, "# "+name+"-START", "# "+name+"-END")
config = strings.Replace(config, "\n# "+name+"-START"+module+"# "+name+"-END", "", -1)
match := regexp.MustCompile(`auth users = ([^\n]+)`).FindStringSubmatch(module)
if len(match) == 2 {
authUser := match[1]
if out, err := tools.Exec("sed -i '/^" + authUser + ":.*$/d' /etc/rsyncd.secrets"); err != nil {
if out, err := shell.Execf("sed -i '/^" + authUser + ":.*$/d' /etc/rsyncd.secrets"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
}
if err = tools.Write("/etc/rsyncd.conf", config, 0644); err != nil {
if err = io.Write("/etc/rsyncd.conf", config, 0644); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
if err = tools.ServiceRestart("rsyncd"); err != nil {
if err = systemctl.Restart("rsyncd"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -206,7 +209,7 @@ func (r *RsyncController) Update(ctx http.Context) http.Response {
return sanitize
}
config, err := tools.Read("/etc/rsyncd.conf")
config, err := io.Read("/etc/rsyncd.conf")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -224,25 +227,25 @@ hosts allow = ` + updateRequest.HostsAllow + `
secrets file = /etc/rsyncd.secrets
# ` + updateRequest.Name + `-END`
module := tools.Cut(config, "# "+updateRequest.Name+"-START", "# "+updateRequest.Name+"-END")
module := str.Cut(config, "# "+updateRequest.Name+"-START", "# "+updateRequest.Name+"-END")
config = strings.Replace(config, "# "+updateRequest.Name+"-START"+module+"# "+updateRequest.Name+"-END", newConf, -1)
match := regexp.MustCompile(`auth users = ([^\n]+)`).FindStringSubmatch(module)
if len(match) == 2 {
authUser := match[1]
if out, err := tools.Exec("sed -i '/^" + authUser + ":.*$/d' /etc/rsyncd.secrets"); err != nil {
if out, err := shell.Execf("sed -i '/^" + authUser + ":.*$/d' /etc/rsyncd.secrets"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
}
if err = tools.Write("/etc/rsyncd.conf", config, 0644); err != nil {
if err = io.Write("/etc/rsyncd.conf", config, 0644); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
if out, err := tools.Exec("echo '" + updateRequest.AuthUser + ":" + updateRequest.Secret + "' >> /etc/rsyncd.secrets"); err != nil {
if out, err := shell.Execf("echo '" + updateRequest.AuthUser + ":" + updateRequest.Secret + "' >> /etc/rsyncd.secrets"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if err = tools.ServiceRestart("rsyncd"); err != nil {
if err = systemctl.Restart("rsyncd"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -259,7 +262,7 @@ secrets file = /etc/rsyncd.secrets
// @Success 200 {object} controllers.SuccessResponse
// @Router /plugins/rsync/config [get]
func (r *RsyncController) GetConfig(ctx http.Context) http.Response {
config, err := tools.Read("/etc/rsyncd.conf")
config, err := io.Read("/etc/rsyncd.conf")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -284,11 +287,11 @@ func (r *RsyncController) UpdateConfig(ctx http.Context) http.Response {
return sanitize
}
if err := tools.Write("/etc/rsyncd.conf", updateRequest.Config, 0644); err != nil {
if err := io.Write("/etc/rsyncd.conf", updateRequest.Config, 0644); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
if err := tools.ServiceRestart("rsyncd"); err != nil {
if err := systemctl.Restart("rsyncd"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
+17 -16
View File
@@ -11,8 +11,9 @@ import (
"github.com/TheTNB/panel/app/http/controllers"
"github.com/TheTNB/panel/internal"
"github.com/TheTNB/panel/internal/services"
"github.com/TheTNB/panel/pkg/tools"
"github.com/TheTNB/panel/types"
"github.com/TheTNB/panel/pkg/io"
"github.com/TheTNB/panel/pkg/shell"
"github.com/TheTNB/panel/pkg/types"
)
type S3fsController struct {
@@ -65,12 +66,12 @@ func (r *S3fsController) Add(ctx http.Context) http.Response {
}
// 检查挂载目录是否存在且为空
if !tools.Exists(path) {
if err := tools.Mkdir(path, 0755); err != nil {
if !io.Exists(path) {
if err := io.Mkdir(path, 0755); err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "挂载目录创建失败")
}
}
if !tools.Empty(path) {
if !io.Empty(path) {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "挂载目录必须为空")
}
@@ -87,19 +88,19 @@ func (r *S3fsController) Add(ctx http.Context) http.Response {
id := carbon.Now().TimestampMilli()
password := ak + ":" + sk
if err := tools.Write("/etc/passwd-s3fs-"+cast.ToString(id), password, 0600); err != nil {
if err := io.Write("/etc/passwd-s3fs-"+cast.ToString(id), password, 0600); err != nil {
return nil
}
out, err := tools.Exec(`echo 's3fs#` + bucket + ` ` + path + ` fuse _netdev,allow_other,nonempty,url=` + url + `,passwd_file=/etc/passwd-s3fs-` + cast.ToString(id) + ` 0 0' >> /etc/fstab`)
out, err := shell.Execf(`echo 's3fs#` + bucket + ` ` + path + ` fuse _netdev,allow_other,nonempty,url=` + url + `,passwd_file=/etc/passwd-s3fs-` + cast.ToString(id) + ` 0 0' >> /etc/fstab`)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if mountCheck, err := tools.Exec("mount -a 2>&1"); err != nil {
_, _ = tools.Exec(`sed -i 's@^s3fs#` + bucket + `\s` + path + `.*$@@g' /etc/fstab`)
if mountCheck, err := shell.Execf("mount -a 2>&1"); err != nil {
_, _ = shell.Execf(`sed -i 's@^s3fs#` + bucket + `\s` + path + `.*$@@g' /etc/fstab`)
return controllers.Error(ctx, http.StatusInternalServerError, "检测到/etc/fstab有误: "+mountCheck)
}
if _, err := tools.Exec("df -h | grep " + path + " 2>&1"); err != nil {
_, _ = tools.Exec(`sed -i 's@^s3fs#` + bucket + `\s` + path + `.*$@@g' /etc/fstab`)
if _, err := shell.Execf("df -h | grep " + path + " 2>&1"); err != nil {
_, _ = shell.Execf(`sed -i 's@^s3fs#` + bucket + `\s` + path + `.*$@@g' /etc/fstab`)
return controllers.Error(ctx, http.StatusInternalServerError, "挂载失败,请检查配置是否正确")
}
@@ -145,19 +146,19 @@ func (r *S3fsController) Delete(ctx http.Context) http.Response {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "挂载ID不存在")
}
if out, err := tools.Exec(`fusermount -u '` + mount.Path + `' 2>&1`); err != nil {
if out, err := shell.Execf(`fusermount -u '` + mount.Path + `' 2>&1`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec(`umount '` + mount.Path + `' 2>&1`); err != nil {
if out, err := shell.Execf(`umount '` + mount.Path + `' 2>&1`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec(`sed -i 's@^s3fs#` + mount.Bucket + `\s` + mount.Path + `.*$@@g' /etc/fstab`); err != nil {
if out, err := shell.Execf(`sed -i 's@^s3fs#` + mount.Bucket + `\s` + mount.Path + `.*$@@g' /etc/fstab`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if mountCheck, err := tools.Exec("mount -a 2>&1"); err != nil {
if mountCheck, err := shell.Execf("mount -a 2>&1"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "检测到/etc/fstab有误: "+mountCheck)
}
if err := tools.Remove("/etc/passwd-s3fs-" + cast.ToString(mount.ID)); err != nil {
if err := io.Remove("/etc/passwd-s3fs-" + cast.ToString(mount.ID)); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -5,10 +5,13 @@ import (
"strconv"
"strings"
"github.com/TheTNB/panel/pkg/tools"
"github.com/goravel/framework/contracts/http"
"github.com/TheTNB/panel/app/http/controllers"
"github.com/TheTNB/panel/pkg/io"
"github.com/TheTNB/panel/pkg/os"
"github.com/TheTNB/panel/pkg/shell"
"github.com/TheTNB/panel/pkg/systemctl"
)
type SupervisorController struct {
@@ -17,7 +20,7 @@ type SupervisorController struct {
func NewSupervisorController() *SupervisorController {
var service string
if tools.IsRHEL() {
if os.IsRHEL() {
service = "supervisord"
} else {
service = "supervisor"
@@ -35,7 +38,7 @@ func (r *SupervisorController) Service(ctx http.Context) http.Response {
// Log 日志
func (r *SupervisorController) Log(ctx http.Context) http.Response {
log, err := tools.Exec(`tail -n 200 /var/log/supervisor/supervisord.log`)
log, err := shell.Execf(`tail -n 200 /var/log/supervisor/supervisord.log`)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, log)
}
@@ -45,7 +48,7 @@ func (r *SupervisorController) Log(ctx http.Context) http.Response {
// ClearLog 清空日志
func (r *SupervisorController) ClearLog(ctx http.Context) http.Response {
if out, err := tools.Exec(`echo "" > /var/log/supervisor/supervisord.log`); err != nil {
if out, err := shell.Execf(`echo "" > /var/log/supervisor/supervisord.log`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
@@ -56,10 +59,10 @@ func (r *SupervisorController) ClearLog(ctx http.Context) http.Response {
func (r *SupervisorController) Config(ctx http.Context) http.Response {
var config string
var err error
if tools.IsRHEL() {
config, err = tools.Read(`/etc/supervisord.conf`)
if os.IsRHEL() {
config, err = io.Read(`/etc/supervisord.conf`)
} else {
config, err = tools.Read(`/etc/supervisor/supervisord.conf`)
config, err = io.Read(`/etc/supervisor/supervisord.conf`)
}
if err != nil {
@@ -73,17 +76,17 @@ func (r *SupervisorController) Config(ctx http.Context) http.Response {
func (r *SupervisorController) SaveConfig(ctx http.Context) http.Response {
config := ctx.Request().Input("config")
var err error
if tools.IsRHEL() {
err = tools.Write(`/etc/supervisord.conf`, config, 0644)
if os.IsRHEL() {
err = io.Write(`/etc/supervisord.conf`, config, 0644)
} else {
err = tools.Write(`/etc/supervisor/supervisord.conf`, config, 0644)
err = io.Write(`/etc/supervisor/supervisord.conf`, config, 0644)
}
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
if err = tools.ServiceRestart(r.service); err != nil {
if err = systemctl.Restart(r.service); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, fmt.Sprintf("重启 %s 服务失败", r.service))
}
@@ -99,7 +102,7 @@ func (r *SupervisorController) Processes(ctx http.Context) http.Response {
Uptime string `json:"uptime"`
}
out, err := tools.Exec(`supervisorctl status | awk '{print $1}'`)
out, err := shell.Execf(`supervisorctl status | awk '{print $1}'`)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
@@ -112,13 +115,13 @@ func (r *SupervisorController) Processes(ctx http.Context) http.Response {
var p process
p.Name = line
if status, err := tools.Exec(`supervisorctl status ` + line + ` | awk '{print $2}'`); err == nil {
if status, err := shell.Execf(`supervisorctl status ` + line + ` | awk '{print $2}'`); err == nil {
p.Status = status
}
if p.Status == "RUNNING" {
pid, _ := tools.Exec(`supervisorctl status ` + line + ` | awk '{print $4}'`)
pid, _ := shell.Execf(`supervisorctl status ` + line + ` | awk '{print $4}'`)
p.Pid = strings.ReplaceAll(pid, ",", "")
uptime, _ := tools.Exec(`supervisorctl status ` + line + ` | awk '{print $6}'`)
uptime, _ := shell.Execf(`supervisorctl status ` + line + ` | awk '{print $6}'`)
p.Uptime = uptime
} else {
p.Pid = "-"
@@ -138,7 +141,7 @@ func (r *SupervisorController) Processes(ctx http.Context) http.Response {
// StartProcess 启动进程
func (r *SupervisorController) StartProcess(ctx http.Context) http.Response {
process := ctx.Request().Input("process")
if out, err := tools.Exec(`supervisorctl start ` + process); err != nil {
if out, err := shell.Execf(`supervisorctl start %s`, process); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
@@ -148,7 +151,7 @@ func (r *SupervisorController) StartProcess(ctx http.Context) http.Response {
// StopProcess 停止进程
func (r *SupervisorController) StopProcess(ctx http.Context) http.Response {
process := ctx.Request().Input("process")
if out, err := tools.Exec(`supervisorctl stop ` + process); err != nil {
if out, err := shell.Execf(`supervisorctl stop %s`, process); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
@@ -158,7 +161,7 @@ func (r *SupervisorController) StopProcess(ctx http.Context) http.Response {
// RestartProcess 重启进程
func (r *SupervisorController) RestartProcess(ctx http.Context) http.Response {
process := ctx.Request().Input("process")
if out, err := tools.Exec(`supervisorctl restart ` + process); err != nil {
if out, err := shell.Execf(`supervisorctl restart %s`, process); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
@@ -170,17 +173,17 @@ func (r *SupervisorController) ProcessLog(ctx http.Context) http.Response {
process := ctx.Request().Input("process")
var logPath string
var err error
if tools.IsRHEL() {
logPath, err = tools.Exec(`cat '/etc/supervisord.d/` + process + `.conf' | grep stdout_logfile= | awk -F "=" '{print $2}'`)
if os.IsRHEL() {
logPath, err = shell.Execf(`cat '/etc/supervisord.d/%s.conf' | grep stdout_logfile= | awk -F "=" '{print $2}'`, process)
} else {
logPath, err = tools.Exec(`cat '/etc/supervisor/conf.d/` + process + `.conf' | grep stdout_logfile= | awk -F "=" '{print $2}'`)
logPath, err = shell.Execf(`cat '/etc/supervisor/conf.d/%s.conf' | grep stdout_logfile= | awk -F "=" '{print $2}'`, process)
}
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "无法从进程"+process+"的配置文件中获取日志路径")
}
log, err := tools.Exec(`tail -n 200 ` + logPath)
log, err := shell.Execf(`tail -n 200 ` + logPath)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, log)
}
@@ -193,17 +196,17 @@ func (r *SupervisorController) ClearProcessLog(ctx http.Context) http.Response {
process := ctx.Request().Input("process")
var logPath string
var err error
if tools.IsRHEL() {
logPath, err = tools.Exec(`cat '/etc/supervisord.d/` + process + `.conf' | grep stdout_logfile= | awk -F "=" '{print $2}'`)
if os.IsRHEL() {
logPath, err = shell.Execf(`cat '/etc/supervisord.d/%s.conf' | grep stdout_logfile= | awk -F "=" '{print $2}'`, process)
} else {
logPath, err = tools.Exec(`cat '/etc/supervisor/conf.d/` + process + `.conf' | grep stdout_logfile= | awk -F "=" '{print $2}'`)
logPath, err = shell.Execf(`cat '/etc/supervisor/conf.d/%s.conf' | grep stdout_logfile= | awk -F "=" '{print $2}'`, process)
}
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "无法从进程"+process+"的配置文件中获取日志路径")
return controllers.Error(ctx, http.StatusInternalServerError, fmt.Sprintf("无法从进程%s的配置文件中获取日志路径", process))
}
if out, err := tools.Exec(`echo "" > ` + logPath); err != nil {
if out, err := shell.Execf(`echo "" > ` + logPath); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
@@ -215,10 +218,10 @@ func (r *SupervisorController) ProcessConfig(ctx http.Context) http.Response {
process := ctx.Request().Query("process")
var config string
var err error
if tools.IsRHEL() {
config, err = tools.Read(`/etc/supervisord.d/` + process + `.conf`)
if os.IsRHEL() {
config, err = io.Read(`/etc/supervisord.d/` + process + `.conf`)
} else {
config, err = tools.Read(`/etc/supervisor/conf.d/` + process + `.conf`)
config, err = io.Read(`/etc/supervisor/conf.d/` + process + `.conf`)
}
if err != nil {
@@ -233,19 +236,19 @@ func (r *SupervisorController) SaveProcessConfig(ctx http.Context) http.Response
process := ctx.Request().Input("process")
config := ctx.Request().Input("config")
var err error
if tools.IsRHEL() {
err = tools.Write(`/etc/supervisord.d/`+process+`.conf`, config, 0644)
if os.IsRHEL() {
err = io.Write(`/etc/supervisord.d/`+process+`.conf`, config, 0644)
} else {
err = tools.Write(`/etc/supervisor/conf.d/`+process+`.conf`, config, 0644)
err = io.Write(`/etc/supervisor/conf.d/`+process+`.conf`, config, 0644)
}
if err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, err.Error())
}
_, _ = tools.Exec(`supervisorctl reread`)
_, _ = tools.Exec(`supervisorctl update`)
_, _ = tools.Exec(`supervisorctl restart ` + process)
_, _ = shell.Execf(`supervisorctl reread`)
_, _ = shell.Execf(`supervisorctl update`)
_, _ = shell.Execf(`supervisorctl restart %s`, process)
return controllers.Success(ctx, nil)
}
@@ -281,19 +284,19 @@ stdout_logfile_maxbytes=2MB
`
var err error
if tools.IsRHEL() {
err = tools.Write(`/etc/supervisord.d/`+name+`.conf`, config, 0644)
if os.IsRHEL() {
err = io.Write(`/etc/supervisord.d/`+name+`.conf`, config, 0644)
} else {
err = tools.Write(`/etc/supervisor/conf.d/`+name+`.conf`, config, 0644)
err = io.Write(`/etc/supervisor/conf.d/`+name+`.conf`, config, 0644)
}
if err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, err.Error())
}
_, _ = tools.Exec(`supervisorctl reread`)
_, _ = tools.Exec(`supervisorctl update`)
_, _ = tools.Exec(`supervisorctl start ` + name)
_, _ = shell.Execf(`supervisorctl reread`)
_, _ = shell.Execf(`supervisorctl update`)
_, _ = shell.Execf(`supervisorctl start %s`, name)
return controllers.Success(ctx, nil)
}
@@ -301,20 +304,20 @@ stdout_logfile_maxbytes=2MB
// DeleteProcess 删除进程
func (r *SupervisorController) DeleteProcess(ctx http.Context) http.Response {
process := ctx.Request().Input("process")
if out, err := tools.Exec(`supervisorctl stop ` + process); err != nil {
if out, err := shell.Execf(`supervisorctl stop %s`, process); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
var logPath string
var err error
if tools.IsRHEL() {
logPath, err = tools.Exec(`cat '/etc/supervisord.d/` + process + `.conf' | grep stdout_logfile= | awk -F "=" '{print $2}'`)
if err := tools.Remove(`/etc/supervisord.d/` + process + `.conf`); err != nil {
if os.IsRHEL() {
logPath, err = shell.Execf(`cat '/etc/supervisord.d/%s.conf' | grep stdout_logfile= | awk -F "=" '{print $2}'`, process)
if err := io.Remove(`/etc/supervisord.d/` + process + `.conf`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
} else {
logPath, err = tools.Exec(`cat '/etc/supervisor/conf.d/` + process + `.conf' | grep stdout_logfile= | awk -F "=" '{print $2}'`)
if err := tools.Remove(`/etc/supervisor/conf.d/` + process + `.conf`); err != nil {
logPath, err = shell.Execf(`cat '/etc/supervisor/conf.d/%s.conf' | grep stdout_logfile= | awk -F "=" '{print $2}'`, process)
if err := io.Remove(`/etc/supervisor/conf.d/` + process + `.conf`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
}
@@ -323,11 +326,11 @@ func (r *SupervisorController) DeleteProcess(ctx http.Context) http.Response {
return controllers.Error(ctx, http.StatusInternalServerError, "无法从进程"+process+"的配置文件中获取日志路径")
}
if err := tools.Remove(logPath); err != nil {
if err := io.Remove(logPath); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
_, _ = tools.Exec(`supervisorctl reread`)
_, _ = tools.Exec(`supervisorctl update`)
_, _ = shell.Execf(`supervisorctl reread`)
_, _ = shell.Execf(`supervisorctl update`)
return controllers.Success(ctx, nil)
}
@@ -8,7 +8,9 @@ import (
"github.com/spf13/cast"
"github.com/TheTNB/panel/app/http/controllers"
"github.com/TheTNB/panel/pkg/tools"
"github.com/TheTNB/panel/pkg/io"
"github.com/TheTNB/panel/pkg/shell"
"github.com/TheTNB/panel/pkg/str"
)
type ToolBoxController struct {
@@ -20,7 +22,7 @@ func NewToolBoxController() *ToolBoxController {
// GetDNS 获取 DNS 信息
func (r *ToolBoxController) GetDNS(ctx http.Context) http.Response {
raw, err := tools.Read("/etc/resolv.conf")
raw, err := io.Read("/etc/resolv.conf")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -50,7 +52,7 @@ func (r *ToolBoxController) SetDNS(ctx http.Context) http.Response {
dns += "nameserver " + dns1 + "\n"
dns += "nameserver " + dns2 + "\n"
if err := tools.Write("/etc/resolv.conf", dns, 0644); err != nil {
if err := io.Write("/etc/resolv.conf", dns, 0644); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "写入 DNS 信息失败")
}
@@ -61,28 +63,28 @@ func (r *ToolBoxController) SetDNS(ctx http.Context) http.Response {
func (r *ToolBoxController) GetSWAP(ctx http.Context) http.Response {
var total, used, free string
var size int64
if tools.Exists("/www/swap") {
file, err := tools.FileInfo("/www/swap")
if io.Exists("/www/swap") {
file, err := io.FileInfo("/www/swap")
if err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "获取 SWAP 信息失败")
}
size = file.Size() / 1024 / 1024
total = tools.FormatBytes(float64(file.Size()))
total = str.FormatBytes(float64(file.Size()))
} else {
size = 0
total = "0.00 B"
}
raw, err := tools.Exec("free | grep Swap")
raw, err := shell.Execf("free | grep Swap")
if err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "获取 SWAP 信息失败")
}
match := regexp.MustCompile(`Swap:\s+(\d+)\s+(\d+)\s+(\d+)`).FindStringSubmatch(raw)
if len(match) > 0 {
used = tools.FormatBytes(cast.ToFloat64(match[2]) * 1024)
free = tools.FormatBytes(cast.ToFloat64(match[3]) * 1024)
used = str.FormatBytes(cast.ToFloat64(match[2]) * 1024)
free = str.FormatBytes(cast.ToFloat64(match[3]) * 1024)
}
return controllers.Success(ctx, http.Json{
@@ -97,47 +99,47 @@ func (r *ToolBoxController) GetSWAP(ctx http.Context) http.Response {
func (r *ToolBoxController) SetSWAP(ctx http.Context) http.Response {
size := ctx.Request().InputInt("size")
if tools.Exists("/www/swap") {
if out, err := tools.Exec("swapoff /www/swap"); err != nil {
if io.Exists("/www/swap") {
if out, err := shell.Execf("swapoff /www/swap"); err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, out)
}
if out, err := tools.Exec("rm -f /www/swap"); err != nil {
if out, err := shell.Execf("rm -f /www/swap"); err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, out)
}
if out, err := tools.Exec("sed -i '/www\\/swap/d' /etc/fstab"); err != nil {
if out, err := shell.Execf("sed -i '/www\\/swap/d' /etc/fstab"); err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, out)
}
}
if size > 1 {
free, err := tools.Exec("df -k /www | awk '{print $4}' | tail -n 1")
free, err := shell.Execf("df -k /www | awk '{print $4}' | tail -n 1")
if err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "获取磁盘空间失败")
}
if cast.ToInt64(free)*1024 < int64(size)*1024*1024 {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "磁盘空间不足,当前剩余 "+tools.FormatBytes(cast.ToFloat64(free)))
return controllers.Error(ctx, http.StatusUnprocessableEntity, "磁盘空间不足,当前剩余 "+str.FormatBytes(cast.ToFloat64(free)))
}
btrfsCheck, _ := tools.Exec("df -T /www | awk '{print $2}' | tail -n 1")
btrfsCheck, _ := shell.Execf("df -T /www | awk '{print $2}' | tail -n 1")
if strings.Contains(btrfsCheck, "btrfs") {
if out, err := tools.Exec("btrfs filesystem mkswapfile --size " + cast.ToString(size) + "M --uuid clear /www/swap"); err != nil {
if out, err := shell.Execf("btrfs filesystem mkswapfile --size " + cast.ToString(size) + "M --uuid clear /www/swap"); err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, out)
}
} else {
if out, err := tools.Exec("dd if=/dev/zero of=/www/swap bs=1M count=" + cast.ToString(size)); err != nil {
if out, err := shell.Execf("dd if=/dev/zero of=/www/swap bs=1M count=" + cast.ToString(size)); err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, out)
}
if out, err := tools.Exec("mkswap -f /www/swap"); err != nil {
if out, err := shell.Execf("mkswap -f /www/swap"); err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, out)
}
if err := tools.Chmod("/www/swap", 0600); err != nil {
if err := io.Chmod("/www/swap", 0600); err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "设置 SWAP 权限失败")
}
}
if out, err := tools.Exec("swapon /www/swap"); err != nil {
if out, err := shell.Execf("swapon /www/swap"); err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, out)
}
if out, err := tools.Exec("echo '/www/swap swap swap defaults 0 0' >> /etc/fstab"); err != nil {
if out, err := shell.Execf("echo '/www/swap swap swap defaults 0 0' >> /etc/fstab"); err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, out)
}
}
@@ -147,7 +149,7 @@ func (r *ToolBoxController) SetSWAP(ctx http.Context) http.Response {
// GetTimezone 获取时区
func (r *ToolBoxController) GetTimezone(ctx http.Context) http.Response {
raw, err := tools.Exec("timedatectl | grep zone")
raw, err := shell.Execf("timedatectl | grep zone")
if err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "获取时区信息失败")
}
@@ -162,7 +164,7 @@ func (r *ToolBoxController) GetTimezone(ctx http.Context) http.Response {
Value string `json:"value"`
}
zonesRaw, err := tools.Exec("timedatectl list-timezones")
zonesRaw, err := shell.Execf("timedatectl list-timezones")
if err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "获取时区列表失败")
}
@@ -189,7 +191,7 @@ func (r *ToolBoxController) SetTimezone(ctx http.Context) http.Response {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "时区不能为空")
}
if out, err := tools.Exec("timedatectl set-timezone " + timezone); err != nil {
if out, err := shell.Execf("timedatectl set-timezone %s", timezone); err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, out)
}
@@ -198,7 +200,7 @@ func (r *ToolBoxController) SetTimezone(ctx http.Context) http.Response {
// GetHosts 获取 hosts 信息
func (r *ToolBoxController) GetHosts(ctx http.Context) http.Response {
hosts, err := tools.Read("/etc/hosts")
hosts, err := io.Read("/etc/hosts")
if err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, err.Error())
}
@@ -213,7 +215,7 @@ func (r *ToolBoxController) SetHosts(ctx http.Context) http.Response {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "hosts 信息不能为空")
}
if err := tools.Write("/etc/hosts", hosts, 0644); err != nil {
if err := io.Write("/etc/hosts", hosts, 0644); err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "写入 hosts 信息失败")
}
@@ -231,7 +233,7 @@ func (r *ToolBoxController) SetRootPassword(ctx http.Context) http.Response {
}
password = strings.ReplaceAll(password, `'`, `\'`)
if out, err := tools.Exec(`yes '` + password + `' | passwd root`); err != nil {
if out, err := shell.Execf(`yes '` + password + `' | passwd root`); err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, out)
}
+60 -57
View File
@@ -7,7 +7,10 @@ import (
"github.com/goravel/framework/contracts/http"
"github.com/spf13/cast"
"github.com/TheTNB/panel/pkg/tools"
"github.com/TheTNB/panel/pkg/io"
"github.com/TheTNB/panel/pkg/os"
"github.com/TheTNB/panel/pkg/shell"
"github.com/TheTNB/panel/pkg/systemctl"
)
type SafeController struct {
@@ -16,7 +19,7 @@ type SafeController struct {
func NewSafeController() *SafeController {
var ssh string
if tools.IsRHEL() {
if os.IsRHEL() {
ssh = "sshd"
} else {
ssh = "ssh"
@@ -36,33 +39,33 @@ func (r *SafeController) GetFirewallStatus(ctx http.Context) http.Response {
func (r *SafeController) SetFirewallStatus(ctx http.Context) http.Response {
var err error
if ctx.Request().InputBool("status") {
if tools.IsRHEL() {
err = tools.ServiceStart("firewalld")
if os.IsRHEL() {
err = systemctl.Start("firewalld")
if err == nil {
err = tools.ServiceEnable("firewalld")
err = systemctl.Enable("firewalld")
}
} else {
_, err = tools.Exec("echo y | ufw enable")
_, err = shell.Execf("echo y | ufw enable")
if err == nil {
err = tools.ServiceStart("ufw")
err = systemctl.Start("ufw")
}
if err == nil {
err = tools.ServiceEnable("ufw")
err = systemctl.Enable("ufw")
}
}
} else {
if tools.IsRHEL() {
err = tools.ServiceStop("firewalld")
if os.IsRHEL() {
err = systemctl.Stop("firewalld")
if err == nil {
err = tools.ServiceDisable("firewalld")
err = systemctl.Disable("firewalld")
}
} else {
_, err = tools.Exec("ufw disable")
_, err = shell.Execf("ufw disable")
if err == nil {
err = tools.ServiceStop("ufw")
err = systemctl.Stop("ufw")
}
if err == nil {
err = tools.ServiceDisable("ufw")
err = systemctl.Disable("ufw")
}
}
}
@@ -81,8 +84,8 @@ func (r *SafeController) GetFirewallRules(ctx http.Context) http.Response {
}
var rules []map[string]string
if tools.IsRHEL() {
out, err := tools.Exec("firewall-cmd --list-all 2>&1")
if os.IsRHEL() {
out, err := shell.Execf("firewall-cmd --list-all")
if err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
@@ -103,7 +106,7 @@ func (r *SafeController) GetFirewallRules(ctx http.Context) http.Response {
})
}
} else {
out, err := tools.Exec("ufw status | grep -v '(v6)' | grep ALLOW | awk '{print $1}'")
out, err := shell.Execf("ufw status | grep -v '(v6)' | grep ALLOW | awk '{print $1}'")
if err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
@@ -157,14 +160,14 @@ func (r *SafeController) AddFirewallRule(ctx http.Context) http.Response {
}
}
if tools.IsRHEL() {
if out, err := tools.Exec("firewall-cmd --remove-port=" + cast.ToString(port) + "/" + protocol + " --permanent 2>&1"); err != nil {
if os.IsRHEL() {
if out, err := shell.Execf("firewall-cmd --remove-port=%s/%s --permanent", port, protocol); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec("firewall-cmd --add-port=" + cast.ToString(port) + "/" + protocol + " --permanent 2>&1"); err != nil {
if out, err := shell.Execf("firewall-cmd --add-port=%s/%s --permanent", port, protocol); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec("firewall-cmd --reload"); err != nil {
if out, err := shell.Execf("firewall-cmd --reload"); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
} else {
@@ -172,13 +175,13 @@ func (r *SafeController) AddFirewallRule(ctx http.Context) http.Response {
if strings.Contains(port, "-") {
port = strings.ReplaceAll(port, "-", ":")
}
if out, err := tools.Exec("ufw delete allow " + cast.ToString(port) + "/" + protocol); err != nil {
if out, err := shell.Execf("ufw delete allow %s/%s", port, protocol); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec("ufw allow " + cast.ToString(port) + "/" + protocol); err != nil {
if out, err := shell.Execf("ufw allow %s/%s", port, protocol); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec("ufw reload"); err != nil {
if out, err := shell.Execf("ufw reload"); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
}
@@ -192,24 +195,24 @@ func (r *SafeController) DeleteFirewallRule(ctx http.Context) http.Response {
return Error(ctx, http.StatusUnprocessableEntity, "防火墙未启动")
}
port := ctx.Request().InputInt("port", 0)
protocol := ctx.Request().Input("protocol", "")
if port == 0 || protocol == "" {
port := ctx.Request().Input("port")
protocol := ctx.Request().Input("protocol")
if port == "" || protocol == "" {
return Error(ctx, http.StatusUnprocessableEntity, "参数错误")
}
if tools.IsRHEL() {
if out, err := tools.Exec("firewall-cmd --remove-port=" + cast.ToString(port) + "/" + protocol + " --permanent 2>&1"); err != nil {
if os.IsRHEL() {
if out, err := shell.Execf("firewall-cmd --remove-port=%s/%s --permanent", port, protocol); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec("firewall-cmd --reload"); err != nil {
if out, err := shell.Execf("firewall-cmd --reload"); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
} else {
if out, err := tools.Exec("ufw delete allow " + cast.ToString(port) + "/" + protocol); err != nil {
if out, err := shell.Execf("ufw delete allow %s/%s", port, protocol); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec("ufw reload"); err != nil {
if out, err := shell.Execf("ufw reload"); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
}
@@ -220,10 +223,10 @@ func (r *SafeController) DeleteFirewallRule(ctx http.Context) http.Response {
// firewallStatus 获取防火墙状态
func (r *SafeController) firewallStatus() bool {
var running bool
if tools.IsRHEL() {
running, _ = tools.ServiceStatus("firewalld")
if os.IsRHEL() {
running, _ = systemctl.Status("firewalld")
} else {
running, _ = tools.ServiceStatus("ufw")
running, _ = systemctl.Status("ufw")
}
return running
@@ -231,7 +234,7 @@ func (r *SafeController) firewallStatus() bool {
// GetSshStatus 获取 SSH 状态
func (r *SafeController) GetSshStatus(ctx http.Context) http.Response {
running, err := tools.ServiceStatus(r.ssh)
running, err := systemctl.Status(r.ssh)
if err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -242,17 +245,17 @@ func (r *SafeController) GetSshStatus(ctx http.Context) http.Response {
// SetSshStatus 设置 SSH 状态
func (r *SafeController) SetSshStatus(ctx http.Context) http.Response {
if ctx.Request().InputBool("status") {
if err := tools.ServiceEnable(r.ssh); err != nil {
if err := systemctl.Enable(r.ssh); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
if err := tools.ServiceStart(r.ssh); err != nil {
if err := systemctl.Start(r.ssh); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
} else {
if err := tools.ServiceStop(r.ssh); err != nil {
if err := systemctl.Stop(r.ssh); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
if err := tools.ServiceDisable(r.ssh); err != nil {
if err := systemctl.Disable(r.ssh); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
}
@@ -262,7 +265,7 @@ func (r *SafeController) SetSshStatus(ctx http.Context) http.Response {
// GetSshPort 获取 SSH 端口
func (r *SafeController) GetSshPort(ctx http.Context) http.Response {
out, err := tools.Exec("cat /etc/ssh/sshd_config | grep 'Port ' | awk '{print $2}'")
out, err := shell.Execf("cat /etc/ssh/sshd_config | grep 'Port ' | awk '{print $2}'")
if err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
@@ -277,16 +280,16 @@ func (r *SafeController) SetSshPort(ctx http.Context) http.Response {
return Error(ctx, http.StatusUnprocessableEntity, "参数错误")
}
oldPort, err := tools.Exec("cat /etc/ssh/sshd_config | grep 'Port ' | awk '{print $2}'")
oldPort, err := shell.Execf("cat /etc/ssh/sshd_config | grep 'Port ' | awk '{print $2}'")
if err != nil {
return Error(ctx, http.StatusInternalServerError, oldPort)
}
_, _ = tools.Exec("sed -i 's/#Port " + oldPort + "/Port " + cast.ToString(port) + "/g' /etc/ssh/sshd_config")
_, _ = tools.Exec("sed -i 's/Port " + oldPort + "/Port " + cast.ToString(port) + "/g' /etc/ssh/sshd_config")
_, _ = shell.Execf("sed -i 's/#Port %s/Port %d/g' /etc/ssh/sshd_config", oldPort, port)
_, _ = shell.Execf("sed -i 's/Port %s/Port %d/g' /etc/ssh/sshd_config", oldPort, port)
status, _ := tools.ServiceStatus(r.ssh)
status, _ := systemctl.Status(r.ssh)
if status {
_ = tools.ServiceRestart(r.ssh)
_ = systemctl.Restart(r.ssh)
}
return Success(ctx, nil)
@@ -294,8 +297,8 @@ func (r *SafeController) SetSshPort(ctx http.Context) http.Response {
// GetPingStatus 获取 Ping 状态
func (r *SafeController) GetPingStatus(ctx http.Context) http.Response {
if tools.IsRHEL() {
out, err := tools.Exec(`firewall-cmd --list-all 2>&1`)
if os.IsRHEL() {
out, err := shell.Execf(`firewall-cmd --list-all`)
if err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
@@ -306,7 +309,7 @@ func (r *SafeController) GetPingStatus(ctx http.Context) http.Response {
return Success(ctx, false)
}
} else {
config, err := tools.Read("/etc/ufw/before.rules")
config, err := io.Read("/etc/ufw/before.rules")
if err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -322,17 +325,17 @@ func (r *SafeController) GetPingStatus(ctx http.Context) http.Response {
func (r *SafeController) SetPingStatus(ctx http.Context) http.Response {
var out string
var err error
if tools.IsRHEL() {
if os.IsRHEL() {
if ctx.Request().InputBool("status") {
out, err = tools.Exec(`firewall-cmd --permanent --remove-rich-rule='rule protocol value=icmp drop'`)
out, err = shell.Execf(`firewall-cmd --permanent --remove-rich-rule='rule protocol value=icmp drop'`)
} else {
out, err = tools.Exec(`firewall-cmd --permanent --add-rich-rule='rule protocol value=icmp drop'`)
out, err = shell.Execf(`firewall-cmd --permanent --add-rich-rule='rule protocol value=icmp drop'`)
}
} else {
if ctx.Request().InputBool("status") {
out, err = tools.Exec(`sed -i 's/-A ufw-before-input -p icmp --icmp-type echo-request -j DROP/-A ufw-before-input -p icmp --icmp-type echo-request -j ACCEPT/g' /etc/ufw/before.rules`)
out, err = shell.Execf(`sed -i 's/-A ufw-before-input -p icmp --icmp-type echo-request -j DROP/-A ufw-before-input -p icmp --icmp-type echo-request -j ACCEPT/g' /etc/ufw/before.rules`)
} else {
out, err = tools.Exec(`sed -i 's/-A ufw-before-input -p icmp --icmp-type echo-request -j ACCEPT/-A ufw-before-input -p icmp --icmp-type echo-request -j DROP/g' /etc/ufw/before.rules`)
out, err = shell.Execf(`sed -i 's/-A ufw-before-input -p icmp --icmp-type echo-request -j ACCEPT/-A ufw-before-input -p icmp --icmp-type echo-request -j DROP/g' /etc/ufw/before.rules`)
}
}
@@ -340,10 +343,10 @@ func (r *SafeController) SetPingStatus(ctx http.Context) http.Response {
return Error(ctx, http.StatusInternalServerError, out)
}
if tools.IsRHEL() {
out, err = tools.Exec(`firewall-cmd --reload`)
if os.IsRHEL() {
out, err = shell.Execf(`firewall-cmd --reload`)
} else {
out, err = tools.Exec(`ufw reload`)
out, err = shell.Execf(`ufw reload`)
}
if err != nil {
+108 -46
View File
@@ -9,6 +9,10 @@ import (
"github.com/TheTNB/panel/app/models"
"github.com/TheTNB/panel/internal"
"github.com/TheTNB/panel/internal/services"
"github.com/TheTNB/panel/pkg/cert"
"github.com/TheTNB/panel/pkg/io"
"github.com/TheTNB/panel/pkg/os"
"github.com/TheTNB/panel/pkg/shell"
"github.com/TheTNB/panel/pkg/tools"
)
@@ -24,13 +28,12 @@ func NewSettingController() *SettingController {
// List
//
// @Summary 设置列表
// @Description 获取面板设置列表
// @Tags 面板设置
// @Produce json
// @Security BearerToken
// @Success 200 {object} SuccessResponse
// @Router /panel/setting/list [get]
// @Summary 设置列表
// @Tags 面板设置
// @Produce json
// @Security BearerToken
// @Success 200 {object} SuccessResponse
// @Router /panel/setting/list [get]
func (r *SettingController) List(ctx http.Context) http.Response {
var settings []models.Setting
err := facades.Orm().Query().Get(&settings)
@@ -50,7 +53,7 @@ func (r *SettingController) List(ctx http.Context) http.Response {
return ErrorSystem(ctx)
}
port, err := tools.Exec(`cat /www/panel/panel.conf | grep APP_PORT | awk -F '=' '{print $2}' | tr -d '\n'`)
port, err := shell.Execf(`cat /www/panel/panel.conf | grep APP_PORT | awk -F '=' '{print $2}' | tr -d '\n'`)
if err != nil {
facades.Log().Request(ctx.Request()).Tags("面板", "面板设置").With(map[string]any{
"error": err.Error(),
@@ -74,15 +77,14 @@ func (r *SettingController) List(ctx http.Context) http.Response {
// Update
//
// @Summary 更新设置
// @Description 更新面板设置
// @Tags 面板设置
// @Accept json
// @Produce json
// @Security BearerToken
// @Param data body requests.Update true "request"
// @Success 200 {object} SuccessResponse
// @Router /panel/setting/update [post]
// @Summary 更新设置
// @Tags 面板设置
// @Accept json
// @Produce json
// @Security BearerToken
// @Param data body requests.Update true "request"
// @Success 200 {object} SuccessResponse
// @Router /panel/setting/update [post]
func (r *SettingController) Update(ctx http.Context) http.Response {
var updateRequest requests.Update
sanitize := SanitizeRequest(ctx, &updateRequest)
@@ -98,8 +100,8 @@ func (r *SettingController) Update(ctx http.Context) http.Response {
return ErrorSystem(ctx)
}
if !tools.Exists(updateRequest.BackupPath) {
if err = tools.Mkdir(updateRequest.BackupPath, 0644); err != nil {
if !io.Exists(updateRequest.BackupPath) {
if err = io.Mkdir(updateRequest.BackupPath, 0644); err != nil {
return ErrorSystem(ctx)
}
}
@@ -110,11 +112,11 @@ func (r *SettingController) Update(ctx http.Context) http.Response {
}).Info("保存备份目录失败")
return ErrorSystem(ctx)
}
if !tools.Exists(updateRequest.WebsitePath) {
if err = tools.Mkdir(updateRequest.WebsitePath, 0755); err != nil {
if !io.Exists(updateRequest.WebsitePath) {
if err = io.Mkdir(updateRequest.WebsitePath, 0755); err != nil {
return ErrorSystem(ctx)
}
if err = tools.Chown(updateRequest.WebsitePath, "www", "www"); err != nil {
if err = io.Chown(updateRequest.WebsitePath, "www", "www"); err != nil {
return ErrorSystem(ctx)
}
}
@@ -147,7 +149,7 @@ func (r *SettingController) Update(ctx http.Context) http.Response {
return ErrorSystem(ctx)
}
oldPort, err := tools.Exec(`cat /www/panel/panel.conf | grep APP_PORT | awk -F '=' '{print $2}' | tr -d '\n'`)
oldPort, err := shell.Execf(`cat /www/panel/panel.conf | grep APP_PORT | awk -F '=' '{print $2}' | tr -d '\n'`)
if err != nil {
facades.Log().Request(ctx.Request()).Tags("面板", "面板设置").With(map[string]any{
"error": err.Error(),
@@ -157,33 +159,33 @@ func (r *SettingController) Update(ctx http.Context) http.Response {
port := cast.ToString(updateRequest.Port)
if oldPort != port {
if out, err := tools.Exec("sed -i 's/APP_PORT=" + oldPort + "/APP_PORT=" + port + "/g' /www/panel/panel.conf"); err != nil {
if out, err := shell.Execf("sed -i 's/APP_PORT=%s/APP_PORT=%s/g' /www/panel/panel.conf", oldPort, port); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
if tools.IsRHEL() {
if out, err := tools.Exec("firewall-cmd --remove-port=" + cast.ToString(port) + "/tcp --permanent 2>&1"); err != nil {
if os.IsRHEL() {
if out, err := shell.Execf("firewall-cmd --remove-port=%s/tcp --permanent", oldPort); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec("firewall-cmd --add-port=" + cast.ToString(port) + "/tcp --permanent 2>&1"); err != nil {
if out, err := shell.Execf("firewall-cmd --add-port=%s/tcp --permanent", port); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec("firewall-cmd --reload"); err != nil {
if out, err := shell.Execf("firewall-cmd --reload"); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
} else {
if out, err := tools.Exec("ufw delete allow " + cast.ToString(port) + "/tcp"); err != nil {
if out, err := shell.Execf("ufw delete allow %s/tcp", oldPort); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec("ufw allow " + cast.ToString(port) + "/tcp"); err != nil {
if out, err := shell.Execf("ufw allow %s/tcp", port); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec("ufw reload"); err != nil {
if out, err := shell.Execf("ufw reload"); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
}
}
oldEntrance, err := tools.Exec(`cat /www/panel/panel.conf | grep APP_ENTRANCE | awk -F '=' '{print $2}' | tr -d '\n'`)
oldEntrance, err := shell.Execf(`cat /www/panel/panel.conf | grep APP_ENTRANCE | awk -F '=' '{print $2}' | tr -d '\n'`)
if err != nil {
facades.Log().Request(ctx.Request()).Tags("面板", "面板设置").With(map[string]any{
"error": err.Error(),
@@ -192,12 +194,12 @@ func (r *SettingController) Update(ctx http.Context) http.Response {
}
entrance := cast.ToString(updateRequest.Entrance)
if oldEntrance != entrance {
if out, err := tools.Exec("sed -i 's!APP_ENTRANCE=" + oldEntrance + "!APP_ENTRANCE=" + entrance + "!g' /www/panel/panel.conf"); err != nil {
if out, err := shell.Execf("sed -i 's!APP_ENTRANCE=" + oldEntrance + "!APP_ENTRANCE=" + entrance + "!g' /www/panel/panel.conf"); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
}
oldLanguage, err := tools.Exec(`cat /www/panel/panel.conf | grep APP_LOCALE | awk -F '=' '{print $2}' | tr -d '\n'`)
oldLanguage, err := shell.Execf(`cat /www/panel/panel.conf | grep APP_LOCALE | awk -F '=' '{print $2}' | tr -d '\n'`)
if err != nil {
facades.Log().Request(ctx.Request()).Tags("面板", "面板设置").With(map[string]any{
"error": err.Error(),
@@ -205,24 +207,84 @@ func (r *SettingController) Update(ctx http.Context) http.Response {
return ErrorSystem(ctx)
}
if oldLanguage != updateRequest.Language {
if out, err := tools.Exec("sed -i 's/APP_LOCALE=" + oldLanguage + "/APP_LOCALE=" + updateRequest.Language + "/g' /www/panel/panel.conf"); err != nil {
if out, err := shell.Execf("sed -i 's/APP_LOCALE=" + oldLanguage + "/APP_LOCALE=" + updateRequest.Language + "/g' /www/panel/panel.conf"); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
}
if updateRequest.SSL {
if out, err := tools.Exec("sed -i 's/APP_SSL=false/APP_SSL=true/g' /www/panel/panel.conf"); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
} else {
if out, err := tools.Exec("sed -i 's/APP_SSL=true/APP_SSL=false/g' /www/panel/panel.conf"); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
}
if oldPort != port || oldEntrance != entrance || oldLanguage != updateRequest.Language || updateRequest.SSL != facades.Config().GetBool("panel.ssl") {
if oldPort != port || oldEntrance != entrance || oldLanguage != updateRequest.Language {
tools.RestartPanel()
}
return Success(ctx, nil)
}
// GetHttps
//
// @Summary 获取面板 HTTPS 设置
// @Tags 面板设置
// @Produce json
// @Security BearerToken
// @Success 200 {object} SuccessResponse
// @Router /panel/setting/https [get]
func (r *SettingController) GetHttps(ctx http.Context) http.Response {
certPath := facades.Config().GetString("http.tls.ssl.cert")
keyPath := facades.Config().GetString("http.tls.ssl.key")
crt, err := io.Read(certPath)
if err != nil {
return ErrorSystem(ctx)
}
key, err := io.Read(keyPath)
if err != nil {
return ErrorSystem(ctx)
}
return Success(ctx, http.Json{
"https": facades.Config().GetBool("panel.ssl"),
"cert": crt,
"key": key,
})
}
// UpdateHttps
//
// @Summary 更新面板 HTTPS 设置
// @Tags 面板设置
// @Accept json
// @Produce json
// @Security BearerToken
// @Param data body requests.Https true "request"
// @Success 200 {object} SuccessResponse
// @Router /panel/setting/https [post]
func (r *SettingController) UpdateHttps(ctx http.Context) http.Response {
var httpsRequest requests.Https
sanitize := SanitizeRequest(ctx, &httpsRequest)
if sanitize != nil {
return sanitize
}
if httpsRequest.Https {
if _, err := cert.ParseCert(httpsRequest.Cert); err != nil {
return Error(ctx, http.StatusBadRequest, "证书格式错误")
}
if _, err := cert.ParseKey(httpsRequest.Key); err != nil {
return Error(ctx, http.StatusBadRequest, "密钥格式错误")
}
if err := io.Write(facades.App().ExecutablePath("storage/ssl.crt"), httpsRequest.Cert, 0700); err != nil {
return ErrorSystem(ctx)
}
if err := io.Write(facades.App().ExecutablePath("storage/ssl.key"), httpsRequest.Key, 0700); err != nil {
return ErrorSystem(ctx)
}
if out, err := shell.Execf("sed -i 's/APP_SSL=false/APP_SSL=true/g' /www/panel/panel.conf"); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
} else {
if out, err := shell.Execf("sed -i 's/APP_SSL=true/APP_SSL=false/g' /www/panel/panel.conf"); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
}
tools.RestartPanel()
return Success(ctx, nil)
}
+9 -9
View File
@@ -5,7 +5,7 @@ import (
"github.com/goravel/framework/contracts/http"
"github.com/TheTNB/panel/pkg/tools"
"github.com/TheTNB/panel/pkg/systemctl"
)
type SystemController struct {
@@ -32,7 +32,7 @@ func (r *SystemController) ServiceStatus(ctx http.Context) http.Response {
}
service := ctx.Request().Query("service")
status, err := tools.ServiceStatus(service)
status, err := systemctl.Status(service)
if err != nil {
return Error(ctx, http.StatusInternalServerError, fmt.Sprintf("获取 %s 服务运行状态失败", service))
}
@@ -57,7 +57,7 @@ func (r *SystemController) ServiceIsEnabled(ctx http.Context) http.Response {
}
service := ctx.Request().Query("service")
enabled, err := tools.ServiceIsEnabled(service)
enabled, err := systemctl.IsEnabled(service)
if err != nil {
return Error(ctx, http.StatusInternalServerError, fmt.Sprintf("获取 %s 服务启用状态失败", service))
}
@@ -82,7 +82,7 @@ func (r *SystemController) ServiceEnable(ctx http.Context) http.Response {
}
service := ctx.Request().Input("service")
if err := tools.ServiceEnable(service); err != nil {
if err := systemctl.Enable(service); err != nil {
return Error(ctx, http.StatusInternalServerError, fmt.Sprintf("启用 %s 服务失败", service))
}
@@ -106,7 +106,7 @@ func (r *SystemController) ServiceDisable(ctx http.Context) http.Response {
}
service := ctx.Request().Input("service")
if err := tools.ServiceDisable(service); err != nil {
if err := systemctl.Disable(service); err != nil {
return Error(ctx, http.StatusInternalServerError, fmt.Sprintf("禁用 %s 服务失败", service))
}
@@ -130,7 +130,7 @@ func (r *SystemController) ServiceRestart(ctx http.Context) http.Response {
}
service := ctx.Request().Input("service")
if err := tools.ServiceRestart(service); err != nil {
if err := systemctl.Restart(service); err != nil {
return Error(ctx, http.StatusInternalServerError, fmt.Sprintf("重启 %s 服务失败", service))
}
@@ -154,7 +154,7 @@ func (r *SystemController) ServiceReload(ctx http.Context) http.Response {
}
service := ctx.Request().Input("service")
if err := tools.ServiceReload(service); err != nil {
if err := systemctl.Reload(service); err != nil {
return Error(ctx, http.StatusInternalServerError, fmt.Sprintf("重载 %s 服务失败", service))
}
@@ -178,7 +178,7 @@ func (r *SystemController) ServiceStart(ctx http.Context) http.Response {
}
service := ctx.Request().Input("service")
if err := tools.ServiceStart(service); err != nil {
if err := systemctl.Start(service); err != nil {
return Error(ctx, http.StatusInternalServerError, fmt.Sprintf("启动 %s 服务失败", service))
}
@@ -202,7 +202,7 @@ func (r *SystemController) ServiceStop(ctx http.Context) http.Response {
}
service := ctx.Request().Input("service")
if err := tools.ServiceStop(service); err != nil {
if err := systemctl.Stop(service); err != nil {
return Error(ctx, http.StatusInternalServerError, fmt.Sprintf("停止 %s 服务失败", service))
}
+2 -2
View File
@@ -5,7 +5,7 @@ import (
"github.com/goravel/framework/facades"
"github.com/TheTNB/panel/app/models"
"github.com/TheTNB/panel/pkg/tools"
"github.com/TheTNB/panel/pkg/shell"
)
type TaskController struct {
@@ -63,7 +63,7 @@ func (r *TaskController) Log(ctx http.Context) http.Response {
return ErrorSystem(ctx)
}
log, err := tools.Exec(`tail -n 500 '` + task.Log + `'`)
log, err := shell.Execf(`tail -n 500 '` + task.Log + `'`)
if err != nil {
return Error(ctx, http.StatusInternalServerError, "日志已被清理")
}
+32 -24
View File
@@ -13,8 +13,10 @@ import (
"github.com/TheTNB/panel/app/models"
"github.com/TheTNB/panel/internal"
"github.com/TheTNB/panel/internal/services"
"github.com/TheTNB/panel/pkg/tools"
"github.com/TheTNB/panel/types"
"github.com/TheTNB/panel/pkg/io"
"github.com/TheTNB/panel/pkg/str"
"github.com/TheTNB/panel/pkg/systemctl"
"github.com/TheTNB/panel/pkg/types"
)
type WebsiteController struct {
@@ -82,7 +84,7 @@ func (r *WebsiteController) Add(ctx http.Context) http.Response {
addRequest.Path = r.setting.Get(models.SettingKeyWebsitePath) + "/" + addRequest.Name
}
website := types.Website{
website := types.WebsiteAdd{
Name: addRequest.Name,
Status: true,
Domains: addRequest.Domains,
@@ -146,11 +148,11 @@ func (r *WebsiteController) Delete(ctx http.Context) http.Response {
// @Success 200 {object} SuccessResponse{data=map[string]string}
// @Router /panel/website/defaultConfig [get]
func (r *WebsiteController) GetDefaultConfig(ctx http.Context) http.Response {
index, err := tools.Read("/www/server/openresty/html/index.html")
index, err := io.Read("/www/server/openresty/html/index.html")
if err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
stop, err := tools.Read("/www/server/openresty/html/stop.html")
stop, err := io.Read("/www/server/openresty/html/stop.html")
if err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -175,14 +177,14 @@ func (r *WebsiteController) SaveDefaultConfig(ctx http.Context) http.Response {
index := ctx.Request().Input("index")
stop := ctx.Request().Input("stop")
if err := tools.Write("/www/server/openresty/html/index.html", index, 0644); err != nil {
if err := io.Write("/www/server/openresty/html/index.html", index, 0644); err != nil {
facades.Log().Request(ctx.Request()).Tags("面板", "网站管理").With(map[string]any{
"error": err.Error(),
}).Info("保存默认首页配置失败")
return ErrorSystem(ctx)
}
if err := tools.Write("/www/server/openresty/html/stop.html", stop, 0644); err != nil {
if err := io.Write("/www/server/openresty/html/stop.html", stop, 0644); err != nil {
facades.Log().Request(ctx.Request()).Tags("面板", "网站管理").With(map[string]any{
"error": err.Error(),
}).Info("保存默认停止页配置失败")
@@ -200,7 +202,7 @@ func (r *WebsiteController) SaveDefaultConfig(ctx http.Context) http.Response {
// @Produce json
// @Security BearerToken
// @Param id path int true "网站 ID"
// @Success 200 {object} SuccessResponse{data=types.Website}
// @Success 200 {object} SuccessResponse{data=types.WebsiteAdd}
// @Router /panel/websites/{id}/config [get]
func (r *WebsiteController) GetConfig(ctx http.Context) http.Response {
var idRequest requests.ID
@@ -270,7 +272,7 @@ func (r *WebsiteController) ClearLog(ctx http.Context) http.Response {
return ErrorSystem(ctx)
}
if err := tools.Remove("/www/wwwlogs/" + website.Name + ".log"); err != nil {
if err := io.Remove("/www/wwwlogs/" + website.Name + ".log"); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -398,8 +400,8 @@ func (r *WebsiteController) UploadBackup(ctx http.Context) http.Response {
}
backupPath := r.setting.Get(models.SettingKeyBackupPath) + "/website"
if !tools.Exists(backupPath) {
if err = tools.Mkdir(backupPath, 0644); err != nil {
if !io.Exists(backupPath) {
if err = io.Mkdir(backupPath, 0644); err != nil {
return nil
}
}
@@ -468,13 +470,13 @@ func (r *WebsiteController) DeleteBackup(ctx http.Context) http.Response {
}
backupPath := r.setting.Get(models.SettingKeyBackupPath) + "/website"
if !tools.Exists(backupPath) {
if err := tools.Mkdir(backupPath, 0644); err != nil {
if !io.Exists(backupPath) {
if err := io.Mkdir(backupPath, 0644); err != nil {
return nil
}
}
if err := tools.Remove(backupPath + "/" + deleteBackupRequest.Name); err != nil {
if err := io.Remove(backupPath + "/" + deleteBackupRequest.Name); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -547,9 +549,12 @@ server
# waf标记位结束
# 错误页配置,可自行设置
#error_page 404 /404.html;
error_page 404 /404.html;
#error_page 502 /502.html;
# acme证书签发配置,不可修改
include /www/server/vhost/acme/%s.conf;
# 伪静态规则引入,修改后将导致面板设置的伪静态规则失效
include /www/server/vhost/rewrite/%s.conf;
@@ -570,14 +575,17 @@ server
error_log /www/wwwlogs/%s.log;
}
`, website.Path, website.Php, website.Name, website.Name, website.Name)
if err := tools.Write("/www/server/vhost/"+website.Name+".conf", raw, 0644); err != nil {
`, website.Path, website.Php, website.Name, website.Name, website.Name, website.Name)
if err := io.Write("/www/server/vhost/"+website.Name+".conf", raw, 0644); err != nil {
return nil
}
if err := tools.Write("/www/server/vhost/rewrite"+website.Name+".conf", "", 0644); err != nil {
if err := io.Write("/www/server/vhost/rewrite/"+website.Name+".conf", "", 0644); err != nil {
return nil
}
if err := tools.ServiceReload("openresty"); err != nil {
if err := io.Write("/www/server/vhost/acme/"+website.Name+".conf", "", 0644); err != nil {
return nil
}
if err := systemctl.Reload("openresty"); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -611,13 +619,13 @@ func (r *WebsiteController) Status(ctx http.Context) http.Response {
return ErrorSystem(ctx)
}
raw, err := tools.Read("/www/server/vhost/" + website.Name + ".conf")
raw, err := io.Read("/www/server/vhost/" + website.Name + ".conf")
if err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
// 运行目录
rootConfig := tools.Cut(raw, "# root标记位开始\n", "# root标记位结束")
rootConfig := str.Cut(raw, "# root标记位开始\n", "# root标记位结束")
match := regexp.MustCompile(`root\s+(.+);`).FindStringSubmatch(rootConfig)
if len(match) == 2 {
if website.Status {
@@ -629,7 +637,7 @@ func (r *WebsiteController) Status(ctx http.Context) http.Response {
}
// 默认文件
indexConfig := tools.Cut(raw, "# index标记位开始\n", "# index标记位结束")
indexConfig := str.Cut(raw, "# index标记位开始\n", "# index标记位结束")
match = regexp.MustCompile(`index\s+(.+);`).FindStringSubmatch(indexConfig)
if len(match) == 2 {
if website.Status {
@@ -640,10 +648,10 @@ func (r *WebsiteController) Status(ctx http.Context) http.Response {
}
}
if err = tools.Write("/www/server/vhost/"+website.Name+".conf", raw, 0644); err != nil {
if err = io.Write("/www/server/vhost/"+website.Name+".conf", raw, 0644); err != nil {
return ErrorSystem(ctx)
}
if err = tools.ServiceReload("openresty"); err != nil {
if err = systemctl.Reload("openresty"); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
+1 -1
View File
@@ -4,7 +4,7 @@ import (
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/facades"
"github.com/TheTNB/panel/types"
"github.com/TheTNB/panel/pkg/types"
)
// Status 检查程序状态
+2 -2
View File
@@ -21,8 +21,8 @@ func (r *UserStore) Rules(ctx http.Context) map[string]string {
return map[string]string{
"ca": "required|in:letsencrypt,zerossl,sslcom,google,buypass",
"email": "required|email",
"kid": "required_unless:ca,letsencrypt,buypass",
"hmac_encoded": "required_unless:ca,letsencrypt,buypass",
"kid": "required_if:ca,sslcom,google",
"hmac_encoded": "required_if:ca,sslcom,google",
"key_type": "required|in:P256,P384,2048,4096",
}
}
+2 -2
View File
@@ -23,8 +23,8 @@ func (r *UserUpdate) Rules(ctx http.Context) map[string]string {
"id": "required|uint|min:1|exists:cert_users,id",
"ca": "required|in:letsencrypt,zerossl,sslcom,google,buypass",
"email": "required|email",
"kid": "required_unless:ca,letsencrypt,buypass",
"hmac_encoded": "required_unless:ca,letsencrypt,buypass",
"kid": "required_if:ca,sslcom,google",
"hmac_encoded": "required_if:ca,sslcom,google",
"key_type": "required|in:P256,P384,2048,4096",
}
}
@@ -4,7 +4,7 @@ import (
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/contracts/validation"
"github.com/TheTNB/panel/types"
"github.com/TheTNB/panel/pkg/types"
)
type ContainerCreate struct {
@@ -4,7 +4,7 @@ import (
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/contracts/validation"
"github.com/TheTNB/panel/types"
"github.com/TheTNB/panel/pkg/types"
)
type ContainerUpdate struct {
@@ -4,7 +4,7 @@ import (
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/contracts/validation"
"github.com/TheTNB/panel/types"
"github.com/TheTNB/panel/pkg/types"
)
type NetworkCreate struct {
+1 -1
View File
@@ -4,7 +4,7 @@ import (
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/contracts/validation"
"github.com/TheTNB/panel/types"
"github.com/TheTNB/panel/pkg/types"
)
type VolumeCreate struct {
+2 -2
View File
@@ -7,7 +7,7 @@ import (
type Permission struct {
Path string `form:"path" json:"path"`
Mode uint `form:"mode" json:"mode" filter:"uint"`
Mode string `form:"mode" json:"mode"`
Owner string `form:"owner" json:"owner"`
Group string `form:"group" json:"group"`
}
@@ -19,7 +19,7 @@ func (r *Permission) Authorize(ctx http.Context) error {
func (r *Permission) Rules(ctx http.Context) map[string]string {
return map[string]string{
"path": `regex:^/.*$|path_exists`,
"mode": "regex:^[0-7]{3}$|uint",
"mode": "regex:^0[0-7]{3}$",
"owner": "regex:^[a-zA-Z0-9_-]+$",
"group": "regex:^[a-zA-Z0-9_-]+$",
}
+1 -1
View File
@@ -17,7 +17,7 @@ func (r *UnArchive) Authorize(ctx http.Context) error {
func (r *UnArchive) Rules(ctx http.Context) map[string]string {
return map[string]string{
"file": `regex:^/.*$|path_exists`,
"path": `regex:^/.*$|path_not_exists`,
"path": `regex:^/.*$`,
}
}
+36
View File
@@ -0,0 +1,36 @@
package requests
import (
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/contracts/validation"
)
type Https struct {
Https bool `form:"https" json:"https"`
Cert string `form:"cert" json:"cert"`
Key string `form:"key" json:"key"`
}
func (r *Https) Authorize(ctx http.Context) error {
return nil
}
func (r *Https) Rules(ctx http.Context) map[string]string {
return map[string]string{
"https": "bool",
"cert": "string",
"key": "string",
}
}
func (r *Https) Messages(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *Https) Attributes(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *Https) PrepareForValidation(ctx http.Context, data validation.Data) error {
return nil
}
-2
View File
@@ -12,7 +12,6 @@ type Update struct {
BackupPath string `form:"backup_path" json:"backup_path"`
WebsitePath string `form:"website_path" json:"website_path"`
Entrance string `form:"entrance" json:"entrance"`
SSL bool `form:"ssl" json:"ssl"`
UserName string `form:"username" json:"username"`
Email string `form:"email" json:"email"`
Password string `form:"password" json:"password"`
@@ -30,7 +29,6 @@ func (r *Update) Rules(ctx http.Context) map[string]string {
"backup_path": "required|string:2,255",
"website_path": "required|string:2,255",
"entrance": `required|regex:^/(\w+)?$|not_in:/api`,
"ssl": "bool",
"username": "required|string:2,20",
"email": "required|email",
"password": "string:8,255",
+2
View File
@@ -9,6 +9,7 @@ type SaveConfig struct {
ID uint `form:"id" json:"id" filter:"uint"`
Domains []string `form:"domains" json:"domains"`
Ports []uint `form:"ports" json:"ports"`
TLSPorts []uint `form:"tls_ports" json:"tls_ports"`
Hsts bool `form:"hsts" json:"hsts"`
Ssl bool `form:"ssl" json:"ssl"`
HttpRedirect bool `form:"http_redirect" json:"http_redirect"`
@@ -36,6 +37,7 @@ func (r *SaveConfig) Rules(ctx http.Context) map[string]string {
"id": "required|exists:websites,id",
"domains": "required|slice",
"ports": "required|slice",
"tls_ports": "required_if:ssl,true|slice|not_in:80",
"hsts": "bool",
"ssl": "bool",
"http_redirect": "bool",
+7 -28
View File
@@ -1,10 +1,10 @@
package jobs
import (
"github.com/TheTNB/panel/pkg/tools"
"github.com/goravel/framework/facades"
"github.com/TheTNB/panel/app/models"
"github.com/TheTNB/panel/pkg/shell"
)
// ProcessTask 处理面板任务
@@ -27,20 +27,11 @@ func (receiver *ProcessTask) Handle(args ...any) error {
}
var task models.Task
if err := facades.Orm().Query().Where("id = ?", taskID).Get(&task); err != nil {
_ = facades.Orm().Query().Where("id = ?", taskID).Get(&task)
if task.ID == 0 {
facades.Log().Tags("面板", "异步任务").With(map[string]any{
"task_id": taskID,
"error": err.Error(),
}).Infof("获取任务失败")
return nil
}
task.Status = models.TaskStatusRunning
if err := facades.Orm().Query().Save(&task); err != nil {
facades.Log().Tags("面板", "异步任务").With(map[string]any{
"task_id": taskID,
"error": err.Error(),
}).Infof("更新任务失败")
}).Infof("任务不存在")
return nil
}
@@ -48,15 +39,9 @@ func (receiver *ProcessTask) Handle(args ...any) error {
"task_id": taskID,
}).Infof("开始执行任务")
if _, err := tools.Exec(task.Shell); err != nil {
if _, err := shell.Execf(task.Shell); err != nil {
task.Status = models.TaskStatusFailed
if err := facades.Orm().Query().Save(&task); err != nil {
facades.Log().Tags("面板", "异步任务").With(map[string]any{
"task_id": taskID,
"error": err.Error(),
}).Infof("更新任务失败")
return nil
}
_ = facades.Orm().Query().Save(&task)
facades.Log().Tags("面板", "异步任务").With(map[string]any{
"task_id": taskID,
"error": err.Error(),
@@ -65,13 +50,7 @@ func (receiver *ProcessTask) Handle(args ...any) error {
}
task.Status = models.TaskStatusSuccess
if err := facades.Orm().Query().Save(&task); err != nil {
facades.Log().Tags("面板", "异步任务").With(map[string]any{
"task_id": taskID,
"error": err.Error(),
}).Infof("更新任务失败")
return nil
}
_ = facades.Orm().Query().Save(&task)
facades.Log().Tags("面板", "异步任务").With(map[string]any{
"task_id": taskID,
+2 -18
View File
@@ -1,14 +1,12 @@
package providers
import (
"fmt"
"github.com/goravel/framework/contracts/database/seeder"
"github.com/goravel/framework/contracts/foundation"
"github.com/goravel/framework/database/gorm"
"github.com/goravel/framework/facades"
"github.com/TheTNB/panel/app/models"
"github.com/TheTNB/panel/pkg/migrate"
)
type DatabaseServiceProvider struct {
@@ -20,19 +18,5 @@ func (receiver *DatabaseServiceProvider) Register(app foundation.Application) {
func (receiver *DatabaseServiceProvider) Boot(app foundation.Application) {
facades.Seeder().Register([]seeder.Seeder{})
if err := facades.Orm().Query().(*gorm.QueryImpl).Instance().AutoMigrate(
&models.Cert{},
&models.CertDNS{},
&models.CertUser{},
&models.Cron{},
&models.Database{},
&models.Monitor{},
&models.Plugin{},
&models.Setting{},
&models.Task{},
&models.User{},
&models.Website{},
); err != nil {
panic(fmt.Sprintf("Failed to migrate database: %v", err))
}
migrate.Migrate(facades.Orm().Query().(*gorm.QueryImpl).Instance())
}
+21 -1
View File
@@ -2,7 +2,9 @@ package providers
import (
"github.com/goravel/framework/contracts/foundation"
contractshttp "github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/facades"
"github.com/goravel/framework/http/limit"
"github.com/TheTNB/panel/app/http"
"github.com/TheTNB/panel/routes"
@@ -25,5 +27,23 @@ func (receiver *RouteServiceProvider) Boot(app foundation.Application) {
}
func (receiver *RouteServiceProvider) configureRateLimiting() {
facades.RateLimiter().ForWithLimits("login", func(ctx contractshttp.Context) []contractshttp.Limit {
return []contractshttp.Limit{
limit.PerMinute(5).By(ctx.Request().Ip()).Response(func(ctx contractshttp.Context) {
ctx.Request().AbortWithStatusJson(contractshttp.StatusTooManyRequests, contractshttp.Json{
"message": "请求过于频繁,请等待一分钟后再试",
})
}),
limit.PerHour(100).By(ctx.Request().Ip()).Response(func(ctx contractshttp.Context) {
ctx.Request().AbortWithStatusJson(contractshttp.StatusTooManyRequests, contractshttp.Json{
"message": "请求过于频繁,请等待一小时后再试",
})
}),
limit.PerDay(1000).Response(func(ctx contractshttp.Context) {
ctx.Request().AbortWithStatusJson(contractshttp.StatusTooManyRequests, contractshttp.Json{
"message": "面板遭受登录爆破攻击过多,已暂时屏蔽登录,请立刻更换面板端口",
})
}),
}
})
}
+3 -2
View File
@@ -1,9 +1,10 @@
package rules
import (
"github.com/TheTNB/panel/pkg/tools"
"github.com/goravel/framework/contracts/validation"
"github.com/spf13/cast"
"github.com/TheTNB/panel/pkg/io"
)
type PathExists struct {
@@ -27,7 +28,7 @@ func (receiver *PathExists) Passes(_ validation.Data, val any, options ...any) b
return false
}
return tools.Exists(requestValue)
return io.Exists(requestValue)
}
// Message Get the validation error message.
+3 -2
View File
@@ -1,9 +1,10 @@
package rules
import (
"github.com/TheTNB/panel/pkg/tools"
"github.com/goravel/framework/contracts/validation"
"github.com/spf13/cast"
"github.com/TheTNB/panel/pkg/io"
)
type PathNotExists struct {
@@ -27,7 +28,7 @@ func (receiver *PathNotExists) Passes(_ validation.Data, val any, options ...any
return false
}
return !tools.Exists(requestValue)
return !io.Exists(requestValue)
}
// Message Get the validation error message.
+2 -2
View File
@@ -14,10 +14,10 @@ func init() {
// in web browsers. You are free to adjust these settings as needed.
//
// To learn more: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
"paths": []string{"*"},
"paths": []string{"1145141919810"}, // 避免框架使用CORS中间件
"allowed_methods": []string{"*"},
"allowed_origins": []string{"*"},
"allowed_headers": []string{"*"},
"allowed_headers": []string{""},
"exposed_headers": []string{""},
"max_age": 0,
"supports_credentials": false,
+1 -1
View File
@@ -8,7 +8,7 @@ func init() {
config := facades.Config()
config.Add("panel", map[string]any{
"name": "耗子面板",
"version": "v2.2.10",
"version": "v2.2.16",
"ssl": config.Env("APP_SSL", false),
})
}
+83 -8
View File
@@ -2830,6 +2830,66 @@ const docTemplate = `{
}
}
},
"/panel/setting/https": {
"get": {
"security": [
{
"BearerToken": []
}
],
"produces": [
"application/json"
],
"tags": [
"面板设置"
],
"summary": "获取面板 HTTPS 设置",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/controllers.SuccessResponse"
}
}
}
},
"post": {
"security": [
{
"BearerToken": []
}
],
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"面板设置"
],
"summary": "更新面板 HTTPS 设置",
"parameters": [
{
"description": "request",
"name": "data",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/requests.Https"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/controllers.SuccessResponse"
}
}
}
}
},
"/panel/setting/list": {
"get": {
"security": [
@@ -2837,7 +2897,6 @@ const docTemplate = `{
"BearerToken": []
}
],
"description": "获取面板设置列表",
"produces": [
"application/json"
],
@@ -2862,7 +2921,6 @@ const docTemplate = `{
"BearerToken": []
}
],
"description": "更新面板设置",
"consumes": [
"application/json"
],
@@ -3590,7 +3648,7 @@ const docTemplate = `{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/types.Website"
"$ref": "#/definitions/types.WebsiteAdd"
}
}
}
@@ -5006,9 +5064,6 @@ const docTemplate = `{
"port": {
"type": "integer"
},
"ssl": {
"type": "boolean"
},
"username": {
"type": "string"
},
@@ -5467,6 +5522,20 @@ const docTemplate = `{
}
}
},
"requests.Https": {
"type": "object",
"properties": {
"cert": {
"type": "string"
},
"https": {
"type": "boolean"
},
"key": {
"type": "string"
}
}
},
"requests.ImagePull": {
"type": "object",
"properties": {
@@ -5569,7 +5638,7 @@ const docTemplate = `{
"type": "string"
},
"mode": {
"type": "integer"
"type": "string"
},
"owner": {
"type": "string"
@@ -5652,6 +5721,12 @@ const docTemplate = `{
"ssl_certificate_key": {
"type": "string"
},
"tls_ports": {
"type": "array",
"items": {
"type": "integer"
}
},
"waf": {
"type": "boolean"
},
@@ -5846,7 +5921,7 @@ const docTemplate = `{
}
}
},
"types.Website": {
"types.WebsiteAdd": {
"type": "object",
"properties": {
"db": {
+83 -8
View File
@@ -2823,6 +2823,66 @@
}
}
},
"/panel/setting/https": {
"get": {
"security": [
{
"BearerToken": []
}
],
"produces": [
"application/json"
],
"tags": [
"面板设置"
],
"summary": "获取面板 HTTPS 设置",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/controllers.SuccessResponse"
}
}
}
},
"post": {
"security": [
{
"BearerToken": []
}
],
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"面板设置"
],
"summary": "更新面板 HTTPS 设置",
"parameters": [
{
"description": "request",
"name": "data",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/requests.Https"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/controllers.SuccessResponse"
}
}
}
}
},
"/panel/setting/list": {
"get": {
"security": [
@@ -2830,7 +2890,6 @@
"BearerToken": []
}
],
"description": "获取面板设置列表",
"produces": [
"application/json"
],
@@ -2855,7 +2914,6 @@
"BearerToken": []
}
],
"description": "更新面板设置",
"consumes": [
"application/json"
],
@@ -3583,7 +3641,7 @@
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/types.Website"
"$ref": "#/definitions/types.WebsiteAdd"
}
}
}
@@ -4999,9 +5057,6 @@
"port": {
"type": "integer"
},
"ssl": {
"type": "boolean"
},
"username": {
"type": "string"
},
@@ -5460,6 +5515,20 @@
}
}
},
"requests.Https": {
"type": "object",
"properties": {
"cert": {
"type": "string"
},
"https": {
"type": "boolean"
},
"key": {
"type": "string"
}
}
},
"requests.ImagePull": {
"type": "object",
"properties": {
@@ -5562,7 +5631,7 @@
"type": "string"
},
"mode": {
"type": "integer"
"type": "string"
},
"owner": {
"type": "string"
@@ -5645,6 +5714,12 @@
"ssl_certificate_key": {
"type": "string"
},
"tls_ports": {
"type": "array",
"items": {
"type": "integer"
}
},
"waf": {
"type": "boolean"
},
@@ -5839,7 +5914,7 @@
}
}
},
"types.Website": {
"types.WebsiteAdd": {
"type": "object",
"properties": {
"db": {
+52 -7
View File
@@ -84,8 +84,6 @@ definitions:
type: string
port:
type: integer
ssl:
type: boolean
username:
type: string
website_path:
@@ -389,6 +387,15 @@ definitions:
path:
type: string
type: object
requests.Https:
properties:
cert:
type: string
https:
type: boolean
key:
type: string
type: object
requests.ImagePull:
properties:
auth:
@@ -455,7 +462,7 @@ definitions:
group:
type: string
mode:
type: integer
type: string
owner:
type: string
path:
@@ -509,6 +516,10 @@ definitions:
type: string
ssl_certificate_key:
type: string
tls_ports:
items:
type: integer
type: array
waf:
type: boolean
waf_cache:
@@ -634,7 +645,7 @@ definitions:
value:
type: string
type: object
types.Website:
types.WebsiteAdd:
properties:
db:
type: boolean
@@ -2384,9 +2395,44 @@ paths:
summary: 更新插件首页显示状态
tags:
- 插件
/panel/setting/https:
get:
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/controllers.SuccessResponse'
security:
- BearerToken: []
summary: 获取面板 HTTPS 设置
tags:
- 面板设置
post:
consumes:
- application/json
parameters:
- description: request
in: body
name: data
required: true
schema:
$ref: '#/definitions/requests.Https'
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/controllers.SuccessResponse'
security:
- BearerToken: []
summary: 更新面板 HTTPS 设置
tags:
- 面板设置
/panel/setting/list:
get:
description: 获取面板设置列表
produces:
- application/json
responses:
@@ -2403,7 +2449,6 @@ paths:
post:
consumes:
- application/json
description: 更新面板设置
parameters:
- description: request
in: body
@@ -2837,7 +2882,7 @@ paths:
- $ref: '#/definitions/controllers.SuccessResponse'
- properties:
data:
$ref: '#/definitions/types.Website'
$ref: '#/definitions/types.WebsiteAdd'
type: object
security:
- BearerToken: []
+3 -2
View File
@@ -5,7 +5,9 @@ go 1.22
require (
github.com/docker/docker v26.1.4+incompatible
github.com/docker/go-connections v0.5.0
github.com/go-gormigrate/gormigrate/v2 v2.1.2
github.com/go-resty/resty/v2 v2.13.1
github.com/go-sql-driver/mysql v1.8.1
github.com/gookit/validate v1.5.2
github.com/goravel/framework v1.14.1-0.20240618022250-731b8d9930a3
github.com/goravel/gin v1.2.1
@@ -26,6 +28,7 @@ require (
go.uber.org/zap v1.27.0
golang.org/x/crypto v0.24.0
golang.org/x/net v0.26.0
gorm.io/gorm v1.25.10
)
require (
@@ -87,7 +90,6 @@ require (
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.20.0 // indirect
github.com/go-sql-driver/mysql v1.8.1 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
@@ -203,7 +205,6 @@ require (
gorm.io/driver/mysql v1.5.7 // indirect
gorm.io/driver/postgres v1.5.9 // indirect
gorm.io/driver/sqlserver v1.5.3 // indirect
gorm.io/gorm v1.25.10 // indirect
gorm.io/plugin/dbresolver v1.5.2 // indirect
gotest.tools/v3 v3.5.0 // indirect
modernc.org/libc v1.37.6 // indirect
+2
View File
@@ -170,6 +170,8 @@ github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec
github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc=
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=
github.com/go-gormigrate/gormigrate/v2 v2.1.2 h1:F/d1hpHbRAvKezziV2CC5KUE82cVe9zTgHSBoOOZ4CY=
github.com/go-gormigrate/gormigrate/v2 v2.1.2/go.mod h1:9nHVX6z3FCMCQPA7PThGcA55t22yKQfK/Dnsf5i7hUo=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+1 -1
View File
@@ -2,7 +2,7 @@ package internal
import (
"github.com/TheTNB/panel/app/models"
"github.com/TheTNB/panel/types"
"github.com/TheTNB/panel/pkg/types"
)
type Backup interface {
+1 -1
View File
@@ -8,7 +8,7 @@ import (
"github.com/docker/docker/api/types/volume"
requests "github.com/TheTNB/panel/app/http/requests/container"
paneltypes "github.com/TheTNB/panel/types"
paneltypes "github.com/TheTNB/panel/pkg/types"
)
type Container interface {
+3 -1
View File
@@ -1,6 +1,8 @@
package internal
import "github.com/TheTNB/panel/types"
import (
"github.com/TheTNB/panel/pkg/types"
)
type PHP interface {
Status() (bool, error)
+1 -1
View File
@@ -2,7 +2,7 @@ package internal
import (
"github.com/TheTNB/panel/app/models"
"github.com/TheTNB/panel/types"
"github.com/TheTNB/panel/pkg/types"
)
type Plugin interface {
+45 -43
View File
@@ -11,8 +11,10 @@ import (
"github.com/TheTNB/panel/app/models"
"github.com/TheTNB/panel/internal"
"github.com/TheTNB/panel/pkg/tools"
"github.com/TheTNB/panel/types"
"github.com/TheTNB/panel/pkg/io"
"github.com/TheTNB/panel/pkg/shell"
"github.com/TheTNB/panel/pkg/str"
"github.com/TheTNB/panel/pkg/types"
)
type BackupImpl struct {
@@ -33,8 +35,8 @@ func (s *BackupImpl) WebsiteList() ([]types.BackupFile, error) {
}
backupPath += "/website"
if !tools.Exists(backupPath) {
if err := tools.Mkdir(backupPath, 0644); err != nil {
if !io.Exists(backupPath) {
if err := io.Mkdir(backupPath, 0644); err != nil {
return []types.BackupFile{}, err
}
}
@@ -51,7 +53,7 @@ func (s *BackupImpl) WebsiteList() ([]types.BackupFile, error) {
}
backupList = append(backupList, types.BackupFile{
Name: file.Name(),
Size: tools.FormatBytes(float64(info.Size())),
Size: str.FormatBytes(float64(info.Size())),
})
}
@@ -66,14 +68,14 @@ func (s *BackupImpl) WebSiteBackup(website models.Website) error {
}
backupPath += "/website"
if !tools.Exists(backupPath) {
if err := tools.Mkdir(backupPath, 0644); err != nil {
if !io.Exists(backupPath) {
if err := io.Mkdir(backupPath, 0644); err != nil {
return err
}
}
backupFile := backupPath + "/" + website.Name + "_" + carbon.Now().ToShortDateTimeString() + ".zip"
if _, err := tools.Exec(`cd '` + website.Path + `' && zip -r '` + backupFile + `' .`); err != nil {
if _, err := shell.Execf(`cd '` + website.Path + `' && zip -r '` + backupFile + `' .`); err != nil {
return err
}
@@ -88,27 +90,27 @@ func (s *BackupImpl) WebsiteRestore(website models.Website, backupFile string) e
}
backupPath += "/website"
if !tools.Exists(backupPath) {
if err := tools.Mkdir(backupPath, 0644); err != nil {
if !io.Exists(backupPath) {
if err := io.Mkdir(backupPath, 0644); err != nil {
return err
}
}
backupFile = backupPath + "/" + backupFile
if !tools.Exists(backupFile) {
if !io.Exists(backupFile) {
return errors.New("备份文件不存在")
}
if err := tools.Remove(website.Path); err != nil {
if err := io.Remove(website.Path); err != nil {
return err
}
if err := tools.UnArchive(backupFile, website.Path); err != nil {
if err := io.UnArchive(backupFile, website.Path); err != nil {
return err
}
if err := tools.Chmod(website.Path, 0755); err != nil {
if err := io.Chmod(website.Path, 0755); err != nil {
return err
}
if err := tools.Chown(website.Path, "www", "www"); err != nil {
if err := io.Chown(website.Path, "www", "www"); err != nil {
return err
}
@@ -123,8 +125,8 @@ func (s *BackupImpl) MysqlList() ([]types.BackupFile, error) {
}
backupPath += "/mysql"
if !tools.Exists(backupPath) {
if err := tools.Mkdir(backupPath, 0644); err != nil {
if !io.Exists(backupPath) {
if err := io.Mkdir(backupPath, 0644); err != nil {
return []types.BackupFile{}, err
}
}
@@ -141,7 +143,7 @@ func (s *BackupImpl) MysqlList() ([]types.BackupFile, error) {
}
backupList = append(backupList, types.BackupFile{
Name: file.Name(),
Size: tools.FormatBytes(float64(info.Size())),
Size: str.FormatBytes(float64(info.Size())),
})
}
@@ -153,8 +155,8 @@ func (s *BackupImpl) MysqlBackup(database string) error {
backupPath := s.setting.Get(models.SettingKeyBackupPath) + "/mysql"
rootPassword := s.setting.Get(models.SettingKeyMysqlRootPassword)
backupFile := database + "_" + carbon.Now().ToShortDateTimeString() + ".sql"
if !tools.Exists(backupPath) {
if err := tools.Mkdir(backupPath, 0644); err != nil {
if !io.Exists(backupPath) {
if err := io.Mkdir(backupPath, 0644); err != nil {
return err
}
}
@@ -163,13 +165,13 @@ func (s *BackupImpl) MysqlBackup(database string) error {
return err
}
if _, err := tools.Exec("/www/server/mysql/bin/mysqldump -uroot " + database + " > " + backupPath + "/" + backupFile); err != nil {
if _, err := shell.Execf("/www/server/mysql/bin/mysqldump -uroot " + database + " > " + backupPath + "/" + backupFile); err != nil {
return err
}
if _, err := tools.Exec("cd " + backupPath + " && zip -r " + backupPath + "/" + backupFile + ".zip " + backupFile); err != nil {
if _, err := shell.Execf("cd " + backupPath + " && zip -r " + backupPath + "/" + backupFile + ".zip " + backupFile); err != nil {
return err
}
if err := tools.Remove(backupPath + "/" + backupFile); err != nil {
if err := io.Remove(backupPath + "/" + backupFile); err != nil {
return err
}
@@ -181,7 +183,7 @@ func (s *BackupImpl) MysqlRestore(database string, backupFile string) error {
backupPath := s.setting.Get(models.SettingKeyBackupPath) + "/mysql"
rootPassword := s.setting.Get(models.SettingKeyMysqlRootPassword)
backupFullPath := filepath.Join(backupPath, backupFile)
if !tools.Exists(backupFullPath) {
if !io.Exists(backupFullPath) {
return errors.New("备份文件不存在")
}
@@ -189,14 +191,14 @@ func (s *BackupImpl) MysqlRestore(database string, backupFile string) error {
return err
}
tempDir, err := tools.TempDir(backupFile)
tempDir, err := io.TempDir(backupFile)
if err != nil {
return err
}
if !strings.HasSuffix(backupFile, ".sql") {
backupFile = "" // 置空,防止干扰后续判断
if err = tools.UnArchive(backupFullPath, tempDir); err != nil {
if err = io.UnArchive(backupFullPath, tempDir); err != nil {
return err
}
if files, err := os.ReadDir(tempDir); err == nil {
@@ -208,7 +210,7 @@ func (s *BackupImpl) MysqlRestore(database string, backupFile string) error {
}
}
} else {
if err = tools.Cp(backupFullPath, filepath.Join(tempDir, backupFile)); err != nil {
if err = io.Cp(backupFullPath, filepath.Join(tempDir, backupFile)); err != nil {
return err
}
}
@@ -217,11 +219,11 @@ func (s *BackupImpl) MysqlRestore(database string, backupFile string) error {
return errors.New("无法找到备份文件")
}
if _, err = tools.Exec("/www/server/mysql/bin/mysql -uroot " + database + " < " + filepath.Join(tempDir, backupFile)); err != nil {
if _, err = shell.Execf("/www/server/mysql/bin/mysql -uroot " + database + " < " + filepath.Join(tempDir, backupFile)); err != nil {
return err
}
if err = tools.Remove(tempDir); err != nil {
if err = io.Remove(tempDir); err != nil {
return err
}
@@ -236,8 +238,8 @@ func (s *BackupImpl) PostgresqlList() ([]types.BackupFile, error) {
}
backupPath += "/postgresql"
if !tools.Exists(backupPath) {
if err := tools.Mkdir(backupPath, 0644); err != nil {
if !io.Exists(backupPath) {
if err := io.Mkdir(backupPath, 0644); err != nil {
return []types.BackupFile{}, err
}
}
@@ -254,7 +256,7 @@ func (s *BackupImpl) PostgresqlList() ([]types.BackupFile, error) {
}
backupList = append(backupList, types.BackupFile{
Name: file.Name(),
Size: tools.FormatBytes(float64(info.Size())),
Size: str.FormatBytes(float64(info.Size())),
})
}
@@ -265,38 +267,38 @@ func (s *BackupImpl) PostgresqlList() ([]types.BackupFile, error) {
func (s *BackupImpl) PostgresqlBackup(database string) error {
backupPath := s.setting.Get(models.SettingKeyBackupPath) + "/postgresql"
backupFile := database + "_" + carbon.Now().ToShortDateTimeString() + ".sql"
if !tools.Exists(backupPath) {
if err := tools.Mkdir(backupPath, 0644); err != nil {
if !io.Exists(backupPath) {
if err := io.Mkdir(backupPath, 0644); err != nil {
return err
}
}
if _, err := tools.Exec(`su - postgres -c "pg_dump ` + database + `" > ` + backupPath + "/" + backupFile); err != nil {
if _, err := shell.Execf(`su - postgres -c "pg_dump ` + database + `" > ` + backupPath + "/" + backupFile); err != nil {
return err
}
if _, err := tools.Exec("cd " + backupPath + " && zip -r " + backupPath + "/" + backupFile + ".zip " + backupFile); err != nil {
if _, err := shell.Execf("cd " + backupPath + " && zip -r " + backupPath + "/" + backupFile + ".zip " + backupFile); err != nil {
return err
}
return tools.Remove(backupPath + "/" + backupFile)
return io.Remove(backupPath + "/" + backupFile)
}
// PostgresqlRestore PostgreSQL恢复
func (s *BackupImpl) PostgresqlRestore(database string, backupFile string) error {
backupPath := s.setting.Get(models.SettingKeyBackupPath) + "/postgresql"
backupFullPath := filepath.Join(backupPath, backupFile)
if !tools.Exists(backupFullPath) {
if !io.Exists(backupFullPath) {
return errors.New("备份文件不存在")
}
tempDir, err := tools.TempDir(backupFile)
tempDir, err := io.TempDir(backupFile)
if err != nil {
return err
}
if !strings.HasSuffix(backupFile, ".sql") {
backupFile = "" // 置空,防止干扰后续判断
if err = tools.UnArchive(backupFullPath, tempDir); err != nil {
if err = io.UnArchive(backupFullPath, tempDir); err != nil {
return err
}
if files, err := os.ReadDir(tempDir); err == nil {
@@ -308,7 +310,7 @@ func (s *BackupImpl) PostgresqlRestore(database string, backupFile string) error
}
}
} else {
if err = tools.Cp(backupFullPath, filepath.Join(tempDir, backupFile)); err != nil {
if err = io.Cp(backupFullPath, filepath.Join(tempDir, backupFile)); err != nil {
return err
}
}
@@ -317,11 +319,11 @@ func (s *BackupImpl) PostgresqlRestore(database string, backupFile string) error
return errors.New("无法找到备份文件")
}
if _, err = tools.Exec(`su - postgres -c "psql ` + database + `" < ` + filepath.Join(tempDir, backupFile)); err != nil {
if _, err = shell.Execf(`su - postgres -c "psql ` + database + `" < ` + filepath.Join(tempDir, backupFile)); err != nil {
return err
}
if err = tools.Remove(tempDir); err != nil {
if err = io.Remove(tempDir); err != nil {
return err
}
+58 -19
View File
@@ -4,15 +4,19 @@ package services
import (
"context"
"errors"
"fmt"
"strings"
"time"
"github.com/go-resty/resty/v2"
"github.com/goravel/framework/facades"
requests "github.com/TheTNB/panel/app/http/requests/cert"
"github.com/TheTNB/panel/app/models"
"github.com/TheTNB/panel/pkg/acme"
"github.com/TheTNB/panel/pkg/tools"
"github.com/TheTNB/panel/pkg/cert"
"github.com/TheTNB/panel/pkg/io"
"github.com/TheTNB/panel/pkg/systemctl"
)
type CertImpl struct {
@@ -40,7 +44,11 @@ func (s *CertImpl) UserStore(request requests.UserStore) error {
case "buypass":
client, err = acme.NewRegisterAccount(context.Background(), user.Email, acme.CABuypass, nil, acme.KeyType(user.KeyType))
case "zerossl":
client, err = acme.NewRegisterAccount(context.Background(), user.Email, acme.CAZeroSSL, &acme.EAB{KeyID: user.Kid, MACKey: user.HmacEncoded}, acme.KeyType(user.KeyType))
eab, eabErr := s.getZeroSSLEAB(user.Email)
if eabErr != nil {
return eabErr
}
client, err = acme.NewRegisterAccount(context.Background(), user.Email, acme.CAZeroSSL, eab, acme.KeyType(user.KeyType))
case "sslcom":
client, err = acme.NewRegisterAccount(context.Background(), user.Email, acme.CASSLcom, &acme.EAB{KeyID: user.Kid, MACKey: user.HmacEncoded}, acme.KeyType(user.KeyType))
case "google":
@@ -53,7 +61,7 @@ func (s *CertImpl) UserStore(request requests.UserStore) error {
return errors.New("向 CA 注册账号失败,请检查参数是否正确")
}
privateKey, err := acme.EncodePrivateKey(client.Account.PrivateKey)
privateKey, err := cert.EncodeKey(client.Account.PrivateKey)
if err != nil {
return errors.New("获取私钥失败")
}
@@ -83,7 +91,11 @@ func (s *CertImpl) UserUpdate(request requests.UserUpdate) error {
case "buypass":
client, err = acme.NewRegisterAccount(context.Background(), user.Email, acme.CABuypass, nil, acme.KeyType(user.KeyType))
case "zerossl":
client, err = acme.NewRegisterAccount(context.Background(), user.Email, acme.CAZeroSSL, &acme.EAB{KeyID: user.Kid, MACKey: user.HmacEncoded}, acme.KeyType(user.KeyType))
eab, eabErr := s.getZeroSSLEAB(user.Email)
if eabErr != nil {
return eabErr
}
client, err = acme.NewRegisterAccount(context.Background(), user.Email, acme.CAZeroSSL, eab, acme.KeyType(user.KeyType))
case "sslcom":
client, err = acme.NewRegisterAccount(context.Background(), user.Email, acme.CASSLcom, &acme.EAB{KeyID: user.Kid, MACKey: user.HmacEncoded}, acme.KeyType(user.KeyType))
case "google":
@@ -96,7 +108,7 @@ func (s *CertImpl) UserUpdate(request requests.UserUpdate) error {
return errors.New("向 CA 注册账号失败,请检查参数是否正确")
}
privateKey, err := acme.EncodePrivateKey(client.Account.PrivateKey)
privateKey, err := cert.EncodeKey(client.Account.PrivateKey)
if err != nil {
return errors.New("获取私钥失败")
}
@@ -105,6 +117,31 @@ func (s *CertImpl) UserUpdate(request requests.UserUpdate) error {
return facades.Orm().Query().Save(&user)
}
// getZeroSSLEAB 获取 ZeroSSL EAB
func (s *CertImpl) getZeroSSLEAB(email string) (*acme.EAB, error) {
type data struct {
Success bool `json:"success"`
EabKid string `json:"eab_kid"`
EabHmacKey string `json:"eab_hmac_key"`
}
client := resty.New()
client.SetTimeout(5 * time.Second)
client.SetRetryCount(2)
resp, err := client.R().SetFormData(map[string]string{
"email": email,
}).SetResult(&data{}).Post("https://api.zerossl.com/acme/eab-credentials-email")
if err != nil || !resp.IsSuccess() {
return &acme.EAB{}, errors.New("获取ZeroSSL EAB失败")
}
eab := resp.Result().(*data)
if !eab.Success {
return &acme.EAB{}, errors.New("获取ZeroSSL EAB失败")
}
return &acme.EAB{KeyID: eab.EabKid, MACKey: eab.EabHmacKey}, nil
}
// UserShow 根据 ID 获取用户
func (s *CertImpl) UserShow(ID uint) (models.CertUser, error) {
var user models.CertUser
@@ -253,7 +290,8 @@ func (s *CertImpl) ObtainAuto(ID uint) (acme.Certificate, error) {
return acme.Certificate{}, errors.New("通配符域名无法使用 HTTP 验证")
}
}
client.UseHTTP(cert.Website.Path)
conf := fmt.Sprintf("/www/server/vhost/acme/%s.conf", cert.Website.Name)
client.UseHTTP(conf, cert.Website.Path)
}
}
@@ -271,13 +309,13 @@ func (s *CertImpl) ObtainAuto(ID uint) (acme.Certificate, error) {
}
if cert.Website != nil {
if err = tools.Write("/www/server/vhost/ssl/"+cert.Website.Name+".pem", cert.Cert, 0644); err != nil {
if err = io.Write("/www/server/vhost/ssl/"+cert.Website.Name+".pem", cert.Cert, 0644); err != nil {
return acme.Certificate{}, err
}
if err = tools.Write("/www/server/vhost/ssl/"+cert.Website.Name+".key", cert.Key, 0644); err != nil {
if err = io.Write("/www/server/vhost/ssl/"+cert.Website.Name+".key", cert.Key, 0644); err != nil {
return acme.Certificate{}, err
}
if err = tools.ServiceReload("openresty"); err != nil {
if err = systemctl.Reload("openresty"); err != nil {
return acme.Certificate{}, err
}
}
@@ -311,13 +349,13 @@ func (s *CertImpl) ObtainManual(ID uint) (acme.Certificate, error) {
}
if cert.Website != nil {
if err = tools.Write("/www/server/vhost/ssl/"+cert.Website.Name+".pem", cert.Cert, 0644); err != nil {
if err = io.Write("/www/server/vhost/ssl/"+cert.Website.Name+".pem", cert.Cert, 0644); err != nil {
return acme.Certificate{}, err
}
if err = tools.Write("/www/server/vhost/ssl/"+cert.Website.Name+".key", cert.Key, 0644); err != nil {
if err = io.Write("/www/server/vhost/ssl/"+cert.Website.Name+".key", cert.Key, 0644); err != nil {
return acme.Certificate{}, err
}
if err = tools.ServiceReload("openresty"); err != nil {
if err = systemctl.Reload("openresty"); err != nil {
return acme.Certificate{}, err
}
}
@@ -378,7 +416,8 @@ func (s *CertImpl) Renew(ID uint) (acme.Certificate, error) {
return acme.Certificate{}, errors.New("通配符域名无法使用 HTTP 验证")
}
}
client.UseHTTP(cert.Website.Path)
conf := fmt.Sprintf("/www/server/vhost/acme/%s.conf", cert.Website.Name)
client.UseHTTP(conf, cert.Website.Path)
}
}
@@ -396,13 +435,13 @@ func (s *CertImpl) Renew(ID uint) (acme.Certificate, error) {
}
if cert.Website != nil {
if err = tools.Write("/www/server/vhost/ssl/"+cert.Website.Name+".pem", cert.Cert, 0644); err != nil {
if err = io.Write("/www/server/vhost/ssl/"+cert.Website.Name+".pem", cert.Cert, 0644); err != nil {
return acme.Certificate{}, err
}
if err = tools.Write("/www/server/vhost/ssl/"+cert.Website.Name+".key", cert.Key, 0644); err != nil {
if err = io.Write("/www/server/vhost/ssl/"+cert.Website.Name+".key", cert.Key, 0644); err != nil {
return acme.Certificate{}, err
}
if err = tools.ServiceReload("openresty"); err != nil {
if err = systemctl.Reload("openresty"); err != nil {
return acme.Certificate{}, err
}
}
@@ -428,13 +467,13 @@ func (s *CertImpl) Deploy(ID, WebsiteID uint) error {
return err
}
if err = tools.Write("/www/server/vhost/ssl/"+website.Name+".pem", cert.Cert, 0644); err != nil {
if err = io.Write("/www/server/vhost/ssl/"+website.Name+".pem", cert.Cert, 0644); err != nil {
return err
}
if err = tools.Write("/www/server/vhost/ssl/"+website.Name+".key", cert.Key, 0644); err != nil {
if err = io.Write("/www/server/vhost/ssl/"+website.Name+".key", cert.Key, 0644); err != nil {
return err
}
if err = tools.ServiceReload("openresty"); err != nil {
if err = systemctl.Reload("openresty"); err != nil {
return err
}
+1 -1
View File
@@ -16,7 +16,7 @@ import (
"github.com/goravel/framework/support/json"
requests "github.com/TheTNB/panel/app/http/requests/container"
paneltypes "github.com/TheTNB/panel/types"
paneltypes "github.com/TheTNB/panel/pkg/types"
)
type Container struct {
+15 -13
View File
@@ -6,7 +6,9 @@ import (
"strings"
"github.com/TheTNB/panel/app/models"
"github.com/TheTNB/panel/pkg/tools"
"github.com/TheTNB/panel/pkg/os"
"github.com/TheTNB/panel/pkg/shell"
"github.com/TheTNB/panel/pkg/systemctl"
)
type CronImpl struct {
@@ -18,18 +20,18 @@ func NewCronImpl() *CronImpl {
// AddToSystem 添加到系统
func (r *CronImpl) AddToSystem(cron models.Cron) error {
if tools.IsRHEL() {
if _, err := tools.Exec(fmt.Sprintf(`echo "%s %s >> %s 2>&1" >> /var/spool/cron/root`, cron.Time, cron.Shell, cron.Log)); err != nil {
if os.IsRHEL() {
if _, err := shell.Execf(fmt.Sprintf(`echo "%s %s >> %s 2>&1" >> /var/spool/cron/root`, cron.Time, cron.Shell, cron.Log)); err != nil {
return err
}
return tools.ServiceRestart("crond")
return systemctl.Restart("crond")
}
if tools.IsDebian() {
if _, err := tools.Exec(fmt.Sprintf(`echo "%s %s >> %s 2>&1" >> /var/spool/cron/crontabs/root`, cron.Time, cron.Shell, cron.Log)); err != nil {
if os.IsDebian() {
if _, err := shell.Execf(fmt.Sprintf(`echo "%s %s >> %s 2>&1" >> /var/spool/cron/crontabs/root`, cron.Time, cron.Shell, cron.Log)); err != nil {
return err
}
return tools.ServiceRestart("cron")
return systemctl.Restart("cron")
}
return errors.New("不支持的系统")
@@ -39,18 +41,18 @@ func (r *CronImpl) AddToSystem(cron models.Cron) error {
func (r *CronImpl) DeleteFromSystem(cron models.Cron) error {
// 需要转义 shell 路径的/为\/
cron.Shell = strings.ReplaceAll(cron.Shell, "/", "\\/")
if tools.IsRHEL() {
if _, err := tools.Exec("sed -i '/" + cron.Shell + "/d' /var/spool/cron/root"); err != nil {
if os.IsRHEL() {
if _, err := shell.Execf("sed -i '/" + cron.Shell + "/d' /var/spool/cron/root"); err != nil {
return err
}
return tools.ServiceRestart("crond")
return systemctl.Restart("crond")
}
if tools.IsDebian() {
if _, err := tools.Exec("sed -i '/" + cron.Shell + "/d' /var/spool/cron/crontabs/root"); err != nil {
if os.IsDebian() {
if _, err := shell.Execf("sed -i '/" + cron.Shell + "/d' /var/spool/cron/crontabs/root"); err != nil {
return err
}
return tools.ServiceRestart("cron")
return systemctl.Restart("cron")
}
return errors.New("不支持的系统")
+14 -12
View File
@@ -13,8 +13,10 @@ import (
"github.com/spf13/cast"
"github.com/TheTNB/panel/app/models"
"github.com/TheTNB/panel/pkg/tools"
"github.com/TheTNB/panel/types"
"github.com/TheTNB/panel/pkg/io"
"github.com/TheTNB/panel/pkg/shell"
"github.com/TheTNB/panel/pkg/systemctl"
"github.com/TheTNB/panel/pkg/types"
)
type PHPImpl struct {
@@ -28,15 +30,15 @@ func NewPHPImpl(version uint) *PHPImpl {
}
func (r *PHPImpl) Reload() error {
return tools.ServiceReload("php-fpm-" + r.version)
return systemctl.Reload("php-fpm-" + r.version)
}
func (r *PHPImpl) GetConfig() (string, error) {
return tools.Read("/www/server/php/" + r.version + "/etc/php.ini")
return io.Read("/www/server/php/" + r.version + "/etc/php.ini")
}
func (r *PHPImpl) SaveConfig(config string) error {
if err := tools.Write("/www/server/php/"+r.version+"/etc/php.ini", config, 0644); err != nil {
if err := io.Write("/www/server/php/"+r.version+"/etc/php.ini", config, 0644); err != nil {
return err
}
@@ -44,11 +46,11 @@ func (r *PHPImpl) SaveConfig(config string) error {
}
func (r *PHPImpl) GetFPMConfig() (string, error) {
return tools.Read("/www/server/php/" + r.version + "/etc/php-fpm.conf")
return io.Read("/www/server/php/" + r.version + "/etc/php-fpm.conf")
}
func (r *PHPImpl) SaveFPMConfig(config string) error {
if err := tools.Write("/www/server/php/"+r.version+"/etc/php-fpm.conf", config, 0644); err != nil {
if err := io.Write("/www/server/php/"+r.version+"/etc/php-fpm.conf", config, 0644); err != nil {
return err
}
@@ -82,15 +84,15 @@ func (r *PHPImpl) Load() ([]types.NV, error) {
}
func (r *PHPImpl) GetErrorLog() (string, error) {
return tools.Exec("tail -n 500 /www/server/php/" + r.version + "/var/log/php-fpm.log")
return shell.Execf("tail -n 500 /www/server/php/%s/var/log/php-fpm.log", r.version)
}
func (r *PHPImpl) GetSlowLog() (string, error) {
return tools.Exec("tail -n 500 /www/server/php/" + r.version + "/var/log/slow.log")
return shell.Execf("tail -n 500 /www/server/php/%s/var/log/slow.log", r.version)
}
func (r *PHPImpl) ClearErrorLog() error {
if out, err := tools.Exec("echo '' > /www/server/php/" + r.version + "/var/log/php-fpm.log"); err != nil {
if out, err := shell.Execf("echo '' > /www/server/php/%s/var/log/php-fpm.log", r.version); err != nil {
return errors.New(out)
}
@@ -98,7 +100,7 @@ func (r *PHPImpl) ClearErrorLog() error {
}
func (r *PHPImpl) ClearSlowLog() error {
if out, err := tools.Exec("echo '' > /www/server/php/" + r.version + "/var/log/slow.log"); err != nil {
if out, err := shell.Execf("echo '' > /www/server/php/%s/var/log/slow.log", r.version); err != nil {
return errors.New(out)
}
@@ -270,7 +272,7 @@ func (r *PHPImpl) GetExtensions() ([]types.PHPExtension, error) {
})
}
raw, err := tools.Exec("/www/server/php/" + r.version + "/bin/php -m")
raw, err := shell.Execf("/www/server/php/%s/bin/php -m", r.version)
if err != nil {
return extensions, err
}
+44 -5
View File
@@ -8,8 +8,8 @@ import (
"github.com/TheTNB/panel/app/models"
"github.com/TheTNB/panel/internal"
"github.com/TheTNB/panel/pkg/tools"
"github.com/TheTNB/panel/types"
"github.com/TheTNB/panel/pkg/io"
"github.com/TheTNB/panel/pkg/types"
)
type PluginImpl struct {
@@ -113,6 +113,19 @@ func (r *PluginImpl) Install(slug string) error {
}
}
var count int64
if err = facades.Orm().Query().
Model(&models.Task{}).
Where("log LIKE ?", "%"+plugin.Slug+"%").
Where("status", models.TaskStatusWaiting).
OrWhere("status", models.TaskStatusRunning).
Count(&count); err != nil {
return errors.New("查询任务失败")
}
if count > 0 {
return errors.New("任务已添加,请勿重复添加")
}
var task models.Task
task.Name = "安装插件 " + plugin.Name
task.Status = models.TaskStatusWaiting
@@ -122,7 +135,7 @@ func (r *PluginImpl) Install(slug string) error {
return errors.New("创建任务失败")
}
_ = tools.Remove(task.Log)
_ = io.Remove(task.Log)
return r.task.Process(task.ID)
}
@@ -159,6 +172,19 @@ func (r *PluginImpl) Uninstall(slug string) error {
}
}
var count int64
if err = facades.Orm().Query().
Model(&models.Task{}).
Where("log LIKE ?", "%"+plugin.Slug+"%").
Where("status", models.TaskStatusWaiting).
OrWhere("status", models.TaskStatusRunning).
Count(&count); err != nil {
return errors.New("查询任务失败")
}
if count > 0 {
return errors.New("任务已添加,请勿重复添加")
}
var task models.Task
task.Name = "卸载插件 " + plugin.Name
task.Status = models.TaskStatusWaiting
@@ -168,7 +194,7 @@ func (r *PluginImpl) Uninstall(slug string) error {
return errors.New("创建任务失败")
}
_ = tools.Remove(task.Log)
_ = io.Remove(task.Log)
return r.task.Process(task.ID)
}
@@ -205,6 +231,19 @@ func (r *PluginImpl) Update(slug string) error {
}
}
var count int64
if err = facades.Orm().Query().
Model(&models.Task{}).
Where("log LIKE ?", "%"+plugin.Slug+"%").
Where("status", models.TaskStatusWaiting).
OrWhere("status", models.TaskStatusRunning).
Count(&count); err != nil {
return errors.New("查询任务失败")
}
if count > 0 {
return errors.New("任务已添加,请勿重复添加")
}
var task models.Task
task.Name = "更新插件 " + plugin.Name
task.Status = models.TaskStatusWaiting
@@ -214,6 +253,6 @@ func (r *PluginImpl) Update(slug string) error {
return errors.New("创建任务失败")
}
_ = tools.Remove(task.Log)
_ = io.Remove(task.Log)
return r.task.Process(task.ID)
}
+3 -3
View File
@@ -5,7 +5,7 @@ import (
"github.com/goravel/framework/facades"
"github.com/TheTNB/panel/app/models"
"github.com/TheTNB/panel/pkg/tools"
"github.com/TheTNB/panel/pkg/str"
)
type SettingImpl struct {
@@ -19,11 +19,11 @@ func NewSettingImpl() *SettingImpl {
func (r *SettingImpl) Get(key string, defaultValue ...string) string {
var setting models.Setting
if err := facades.Orm().Query().Where("key", key).FirstOrFail(&setting); err != nil {
return tools.FirstElement(defaultValue)
return str.FirstElement(defaultValue)
}
if len(setting.Value) == 0 {
return tools.FirstElement(defaultValue)
return str.FirstElement(defaultValue)
}
return setting.Value
+24
View File
@@ -1,12 +1,17 @@
package services
import (
"sync"
"github.com/goravel/framework/database/orm"
"github.com/goravel/framework/facades"
"github.com/TheTNB/panel/app/jobs"
"github.com/TheTNB/panel/app/models"
)
var taskMap sync.Map
type TaskImpl struct {
}
@@ -15,6 +20,9 @@ func NewTaskImpl() *TaskImpl {
}
func (r *TaskImpl) Process(taskID uint) error {
if err := r.markAsRunning(taskID); err != nil {
return err
}
return facades.Queue().Job(&jobs.ProcessTask{}, []any{taskID}).Dispatch()
}
@@ -25,6 +33,9 @@ func (r *TaskImpl) DispatchWaiting() error {
}
for _, task := range tasks {
if _, ok := taskMap.Load(task.ID); ok {
continue
}
if err := r.Process(task.ID); err != nil {
return err
}
@@ -32,3 +43,16 @@ func (r *TaskImpl) DispatchWaiting() error {
return nil
}
func (r *TaskImpl) markAsRunning(taskID uint) error {
task := models.Task{
Model: orm.Model{ID: taskID},
Status: models.TaskStatusRunning,
}
if _, err := facades.Orm().Query().Where("id", taskID).Update(&task); err != nil {
return err
}
taskMap.Store(taskID, true)
return nil
}
+109 -108
View File
@@ -2,11 +2,11 @@
package services
import (
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"path/filepath"
"regexp"
"slices"
"strconv"
"strings"
@@ -16,8 +16,12 @@ import (
requests "github.com/TheTNB/panel/app/http/requests/website"
"github.com/TheTNB/panel/app/models"
"github.com/TheTNB/panel/internal"
"github.com/TheTNB/panel/pkg/tools"
"github.com/TheTNB/panel/types"
"github.com/TheTNB/panel/pkg/cert"
"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 WebsiteImpl struct {
@@ -42,7 +46,7 @@ func (r *WebsiteImpl) List(page, limit int) (int64, []models.Website, error) {
}
// Add 添加网站
func (r *WebsiteImpl) Add(website types.Website) (models.Website, error) {
func (r *WebsiteImpl) Add(website types.WebsiteAdd) (models.Website, error) {
w := models.Website{
Name: website.Name,
Status: website.Status,
@@ -55,7 +59,7 @@ func (r *WebsiteImpl) Add(website types.Website) (models.Website, error) {
return models.Website{}, err
}
if err := tools.Mkdir(website.Path, 0755); err != nil {
if err := io.Mkdir(website.Path, 0755); err != nil {
return models.Website{}, err
}
@@ -113,7 +117,7 @@ func (r *WebsiteImpl) Add(website types.Website) (models.Website, error) {
</html>
`
if err := tools.Write(website.Path+"/index.html", index, 0644); err != nil {
if err := io.Write(website.Path+"/index.html", index, 0644); err != nil {
return models.Website{}, err
}
@@ -174,7 +178,7 @@ func (r *WebsiteImpl) Add(website types.Website) (models.Website, error) {
</html>
`
if err := tools.Write(website.Path+"/404.html", notFound, 0644); err != nil {
if err := io.Write(website.Path+"/404.html", notFound, 0644); err != nil {
return models.Website{}, err
}
@@ -238,6 +242,9 @@ server
error_page 404 /404.html;
#error_page 502 /502.html;
# acme证书签发配置,不可修改
include /www/server/vhost/acme/%s.conf;
# 伪静态规则引入,修改后将导致面板设置的伪静态规则失效
include /www/server/vhost/rewrite/%s.conf;
@@ -257,47 +264,50 @@ server
access_log /www/wwwlogs/%s.log;
error_log /www/wwwlogs/%s.log;
}
`, portList, domainList, website.Path, website.Php, website.Name, website.Name, website.Name)
`, portList, domainList, website.Path, website.Php, website.Name, website.Name, website.Name, website.Name)
if err := tools.Write("/www/server/vhost/"+website.Name+".conf", nginxConf, 0644); err != nil {
if err := io.Write("/www/server/vhost/"+website.Name+".conf", nginxConf, 0644); err != nil {
return models.Website{}, err
}
if err := tools.Write("/www/server/vhost/rewrite/"+website.Name+".conf", "", 0644); err != nil {
if err := io.Write("/www/server/vhost/rewrite/"+website.Name+".conf", "", 0644); err != nil {
return models.Website{}, err
}
if err := tools.Write("/www/server/vhost/ssl/"+website.Name+".pem", "", 0644); err != nil {
if err := io.Write("/www/server/vhost/acme/"+website.Name+".conf", "", 0644); err != nil {
return models.Website{}, err
}
if err := tools.Write("/www/server/vhost/ssl/"+website.Name+".key", "", 0644); err != nil {
if err := io.Write("/www/server/vhost/ssl/"+website.Name+".pem", "", 0644); err != nil {
return models.Website{}, err
}
if err := io.Write("/www/server/vhost/ssl/"+website.Name+".key", "", 0644); err != nil {
return models.Website{}, err
}
if err := tools.Chmod(website.Path, 0755); err != nil {
if err := io.Chmod(website.Path, 0755); err != nil {
return models.Website{}, err
}
if err := tools.Chown(website.Path, "www", "www"); err != nil {
if err := io.Chown(website.Path, "www", "www"); err != nil {
return models.Website{}, err
}
if err := tools.ServiceReload("openresty"); err != nil {
if err := systemctl.Reload("openresty"); err != nil {
return models.Website{}, err
}
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
if website.Db && website.DbType == "mysql" {
_, _ = tools.Exec(`/www/server/mysql/bin/mysql -uroot -p` + rootPassword + ` -e "CREATE DATABASE IF NOT EXISTS ` + website.DbName + ` DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_general_ci;"`)
_, _ = tools.Exec(`/www/server/mysql/bin/mysql -uroot -p` + rootPassword + ` -e "CREATE USER '` + website.DbUser + `'@'localhost' IDENTIFIED BY '` + website.DbPassword + `';"`)
_, _ = tools.Exec(`/www/server/mysql/bin/mysql -uroot -p` + rootPassword + ` -e "GRANT ALL PRIVILEGES ON ` + website.DbName + `.* TO '` + website.DbUser + `'@'localhost';"`)
_, _ = tools.Exec(`/www/server/mysql/bin/mysql -uroot -p` + rootPassword + ` -e "FLUSH PRIVILEGES;"`)
_, _ = shell.Execf(`/www/server/mysql/bin/mysql -uroot -p` + rootPassword + ` -e "CREATE DATABASE IF NOT EXISTS ` + website.DbName + ` DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_general_ci;"`)
_, _ = shell.Execf(`/www/server/mysql/bin/mysql -uroot -p` + rootPassword + ` -e "CREATE USER '` + website.DbUser + `'@'localhost' IDENTIFIED BY '` + website.DbPassword + `';"`)
_, _ = shell.Execf(`/www/server/mysql/bin/mysql -uroot -p` + rootPassword + ` -e "GRANT ALL PRIVILEGES ON ` + website.DbName + `.* TO '` + website.DbUser + `'@'localhost';"`)
_, _ = shell.Execf(`/www/server/mysql/bin/mysql -uroot -p` + rootPassword + ` -e "FLUSH PRIVILEGES;"`)
}
if website.Db && website.DbType == "postgresql" {
_, _ = tools.Exec(`echo "CREATE DATABASE ` + website.DbName + `;" | su - postgres -c "psql"`)
_, _ = tools.Exec(`echo "CREATE USER ` + website.DbUser + ` WITH PASSWORD '` + website.DbPassword + `';" | su - postgres -c "psql"`)
_, _ = tools.Exec(`echo "ALTER DATABASE ` + website.DbName + ` OWNER TO ` + website.DbUser + `;" | su - postgres -c "psql"`)
_, _ = tools.Exec(`echo "GRANT ALL PRIVILEGES ON DATABASE ` + website.DbName + ` TO ` + website.DbUser + `;" | su - postgres -c "psql"`)
_, _ = shell.Execf(`echo "CREATE DATABASE ` + website.DbName + `;" | su - postgres -c "psql"`)
_, _ = shell.Execf(`echo "CREATE USER ` + website.DbUser + ` WITH PASSWORD '` + website.DbPassword + `';" | su - postgres -c "psql"`)
_, _ = shell.Execf(`echo "ALTER DATABASE ` + website.DbName + ` OWNER TO ` + website.DbUser + `;" | su - postgres -c "psql"`)
_, _ = shell.Execf(`echo "GRANT ALL PRIVILEGES ON DATABASE ` + website.DbName + ` TO ` + website.DbUser + `;" | su - postgres -c "psql"`)
userConfig := "host " + website.DbName + " " + website.DbUser + " 127.0.0.1/32 scram-sha-256"
_, _ = tools.Exec(`echo "` + userConfig + `" >> /www/server/postgresql/data/pg_hba.conf`)
_ = tools.ServiceReload("postgresql")
_, _ = shell.Execf(`echo "` + userConfig + `" >> /www/server/postgresql/data/pg_hba.conf`)
_ = systemctl.Reload("postgresql")
}
return w, nil
@@ -315,15 +325,15 @@ func (r *WebsiteImpl) SaveConfig(config requests.SaveConfig) error {
}
// 原文
raw, err := tools.Read("/www/server/vhost/" + website.Name + ".conf")
raw, err := io.Read("/www/server/vhost/" + website.Name + ".conf")
if err != nil {
return err
}
if strings.TrimSpace(raw) != strings.TrimSpace(config.Raw) {
if err = tools.Write("/www/server/vhost/"+website.Name+".conf", config.Raw, 0644); err != nil {
if err = io.Write("/www/server/vhost/"+website.Name+".conf", config.Raw, 0644); err != nil {
return err
}
if err = tools.ServiceReload("openresty"); err != nil {
if err = systemctl.Reload("openresty"); err != nil {
return err
}
@@ -332,7 +342,7 @@ func (r *WebsiteImpl) SaveConfig(config requests.SaveConfig) error {
// 目录
path := config.Path
if !tools.Exists(path) {
if !io.Exists(path) {
return errors.New("网站目录不存在")
}
website.Path = path
@@ -347,44 +357,40 @@ func (r *WebsiteImpl) SaveConfig(config requests.SaveConfig) error {
domain += " " + v
}
domain += ";"
domainConfigOld := tools.Cut(raw, "# server_name标记位开始", "# server_name标记位结束")
domainConfigOld := str.Cut(raw, "# server_name标记位开始", "# server_name标记位结束")
if len(strings.TrimSpace(domainConfigOld)) == 0 {
return errors.New("配置文件中缺少server_name标记位")
}
raw = strings.Replace(raw, domainConfigOld, "\n "+domain+"\n ", -1)
// 端口
var port strings.Builder
var portConf strings.Builder
ports := config.Ports
for i, v := range ports {
vStr := cast.ToString(v)
if v == 443 && config.Ssl {
vStr = ` listen 443 ssl;
listen [::]:443 ssl;
listen 443 quic;
listen [::]:443 quic;`
port.WriteString(vStr)
if i != len(ports)-1 {
port.WriteString("\n")
}
continue
for _, port := range ports {
https := ""
quic := false
if slices.Contains(config.TLSPorts, port) {
https = " ssl"
quic = true
}
if i != len(ports)-1 {
port.WriteString(" listen " + vStr + ";\n")
port.WriteString(" listen [::]:" + vStr + ";\n")
} else {
port.WriteString(" listen " + vStr + ";\n")
port.WriteString(" listen [::]:" + vStr + ";")
portConf.WriteString(fmt.Sprintf(" listen %d%s;\n", port, https))
portConf.WriteString(fmt.Sprintf(" listen [::]:%d%s;\n", port, https))
if quic {
portConf.WriteString(fmt.Sprintf(" listen %d%s;\n", port, " quic"))
portConf.WriteString(fmt.Sprintf(" listen [::]:%d%s;\n", port, " quic"))
}
}
portConfigOld := tools.Cut(raw, "# port标记位开始", "# port标记位结束")
if len(strings.TrimSpace(portConfigOld)) == 0 {
portConf.WriteString(" ")
portConfNew := portConf.String()
portConfOld := str.Cut(raw, "# port标记位开始", "# port标记位结束")
if len(strings.TrimSpace(portConfOld)) == 0 {
return errors.New("配置文件中缺少port标记位")
}
raw = strings.Replace(raw, portConfigOld, "\n"+port.String()+"\n ", -1)
raw = strings.Replace(raw, portConfOld, "\n"+portConfNew, -1)
// 运行目录
root := tools.Cut(raw, "# root标记位开始", "# root标记位结束")
root := str.Cut(raw, "# root标记位开始", "# root标记位结束")
if len(strings.TrimSpace(root)) == 0 {
return errors.New("配置文件中缺少root标记位")
}
@@ -396,7 +402,7 @@ func (r *WebsiteImpl) SaveConfig(config requests.SaveConfig) error {
raw = strings.Replace(raw, root, rootNew, -1)
// 默认文件
index := tools.Cut(raw, "# index标记位开始", "# index标记位结束")
index := str.Cut(raw, "# index标记位开始", "# index标记位结束")
if len(strings.TrimSpace(index)) == 0 {
return errors.New("配置文件中缺少index标记位")
}
@@ -413,12 +419,12 @@ func (r *WebsiteImpl) SaveConfig(config requests.SaveConfig) error {
root += "/"
}
if config.OpenBasedir {
if err := tools.Write(root+".user.ini", "open_basedir="+path+":/tmp/", 0644); err != nil {
if err := io.Write(root+".user.ini", "open_basedir="+path+":/tmp/", 0644); err != nil {
return err
}
} else {
if tools.Exists(root + ".user.ini") {
if err := tools.Remove(root + ".user.ini"); err != nil {
if io.Exists(root + ".user.ini") {
if err := io.Remove(root + ".user.ini"); err != nil {
return err
}
}
@@ -440,7 +446,7 @@ func (r *WebsiteImpl) SaveConfig(config requests.SaveConfig) error {
waf_cc_deny ` + wafCcDeny + `;
waf_cache ` + wafCache + `;
`
wafConfigOld := tools.Cut(raw, "# waf标记位开始", "# waf标记位结束")
wafConfigOld := str.Cut(raw, "# waf标记位开始", "# waf标记位结束")
if len(strings.TrimSpace(wafConfigOld)) != 0 {
raw = strings.Replace(raw, wafConfigOld, "", -1)
}
@@ -449,10 +455,18 @@ func (r *WebsiteImpl) SaveConfig(config requests.SaveConfig) error {
// SSL
ssl := config.Ssl
website.Ssl = ssl
if err = tools.Write("/www/server/vhost/ssl/"+website.Name+".pem", config.SslCertificate, 0644); err != nil {
if ssl {
if _, err = cert.ParseCert(config.SslCertificate); err != nil {
return errors.New("TLS证书格式错误")
}
if _, err = cert.ParseKey(config.SslCertificateKey); err != nil {
return errors.New("TLS私钥格式错误")
}
}
if err = io.Write("/www/server/vhost/ssl/"+website.Name+".pem", config.SslCertificate, 0644); err != nil {
return err
}
if err = tools.Write("/www/server/vhost/ssl/"+website.Name+".key", config.SslCertificateKey, 0644); err != nil {
if err = io.Write("/www/server/vhost/ssl/"+website.Name+".key", config.SslCertificateKey, 0644); err != nil {
return err
}
if ssl {
@@ -481,13 +495,13 @@ func (r *WebsiteImpl) SaveConfig(config requests.SaveConfig) error {
# hsts标记位结束
`
}
sslConfigOld := tools.Cut(raw, "# ssl标记位开始", "# ssl标记位结束")
sslConfigOld := str.Cut(raw, "# ssl标记位开始", "# ssl标记位结束")
if len(strings.TrimSpace(sslConfigOld)) != 0 {
raw = strings.Replace(raw, sslConfigOld, "", -1)
}
raw = strings.Replace(raw, "# ssl标记位开始", sslConfig, -1)
} else {
sslConfigOld := tools.Cut(raw, "# ssl标记位开始", "# ssl标记位结束")
sslConfigOld := str.Cut(raw, "# ssl标记位开始", "# ssl标记位结束")
if len(strings.TrimSpace(sslConfigOld)) != 0 {
raw = strings.Replace(raw, sslConfigOld, "\n ", -1)
}
@@ -495,7 +509,7 @@ func (r *WebsiteImpl) SaveConfig(config requests.SaveConfig) error {
if website.Php != config.Php {
website.Php = config.Php
phpConfigOld := tools.Cut(raw, "# php标记位开始", "# php标记位结束")
phpConfigOld := str.Cut(raw, "# php标记位开始", "# php标记位结束")
phpConfig := `
include enable-php-` + strconv.Itoa(website.Php) + `.conf;
`
@@ -508,14 +522,14 @@ func (r *WebsiteImpl) SaveConfig(config requests.SaveConfig) error {
return err
}
if err := tools.Write("/www/server/vhost/"+website.Name+".conf", raw, 0644); err != nil {
if err := io.Write("/www/server/vhost/"+website.Name+".conf", raw, 0644); err != nil {
return err
}
if err := tools.Write("/www/server/vhost/rewrite/"+website.Name+".conf", config.Rewrite, 0644); err != nil {
if err := io.Write("/www/server/vhost/rewrite/"+website.Name+".conf", config.Rewrite, 0644); err != nil {
return err
}
return tools.ServiceReload("openresty")
return systemctl.Reload("openresty")
}
// Delete 删除网站
@@ -533,23 +547,14 @@ func (r *WebsiteImpl) Delete(id uint) error {
return err
}
if err := tools.Remove("/www/server/vhost/" + website.Name + ".conf"); err != nil {
return err
}
if err := tools.Remove("/www/server/vhost/rewrite/" + website.Name + ".conf"); err != nil {
return err
}
if err := tools.Remove("/www/server/vhost/ssl/" + website.Name + ".pem"); err != nil {
return err
}
if err := tools.Remove("/www/server/vhost/ssl/" + website.Name + ".key"); err != nil {
return err
}
if err := tools.Remove(website.Path); err != nil {
return err
}
_ = io.Remove("/www/server/vhost/" + website.Name + ".conf")
_ = io.Remove("/www/server/vhost/rewrite/" + website.Name + ".conf")
_ = io.Remove("/www/server/vhost/acme/" + website.Name + ".conf")
_ = io.Remove("/www/server/vhost/ssl/" + website.Name + ".pem")
_ = io.Remove("/www/server/vhost/ssl/" + website.Name + ".key")
_ = io.Remove(website.Path)
return tools.ServiceReload("openresty")
return systemctl.Reload("openresty")
}
// GetConfig 获取网站配置
@@ -559,7 +564,7 @@ func (r *WebsiteImpl) GetConfig(id uint) (types.WebsiteSetting, error) {
return types.WebsiteSetting{}, err
}
config, err := tools.Read("/www/server/vhost/" + website.Name + ".conf")
config, err := io.Read("/www/server/vhost/" + website.Name + ".conf")
if err != nil {
return types.WebsiteSetting{}, err
}
@@ -571,7 +576,7 @@ func (r *WebsiteImpl) GetConfig(id uint) (types.WebsiteSetting, error) {
setting.Php = strconv.Itoa(website.Php)
setting.Raw = config
ports := tools.Cut(config, "# port标记位开始", "# port标记位结束")
ports := str.Cut(config, "# port标记位开始", "# port标记位结束")
matches := regexp.MustCompile(`listen\s+(.*);`).FindAllStringSubmatch(ports, -1)
for _, match := range matches {
if len(match) < 2 {
@@ -588,26 +593,27 @@ func (r *WebsiteImpl) GetConfig(id uint) (types.WebsiteSetting, error) {
setting.Ports = append(setting.Ports, cast.ToUint(ports[0]))
} else if len(ports) > 1 && ports[1] == "ssl" {
setting.Ports = append(setting.Ports, cast.ToUint(ports[0]))
setting.TLSPorts = append(setting.TLSPorts, cast.ToUint(ports[0]))
}
}
serverName := tools.Cut(config, "# server_name标记位开始", "# server_name标记位结束")
serverName := str.Cut(config, "# server_name标记位开始", "# server_name标记位结束")
match := regexp.MustCompile(`server_name\s+(.*);`).FindStringSubmatch(serverName)
if len(match) > 1 {
setting.Domains = strings.Split(match[1], " ")
}
root := tools.Cut(config, "# root标记位开始", "# root标记位结束")
root := str.Cut(config, "# root标记位开始", "# root标记位结束")
match = regexp.MustCompile(`root\s+(.*);`).FindStringSubmatch(root)
if len(match) > 1 {
setting.Root = match[1]
}
index := tools.Cut(config, "# index标记位开始", "# index标记位结束")
index := str.Cut(config, "# index标记位开始", "# index标记位结束")
match = regexp.MustCompile(`index\s+(.*);`).FindStringSubmatch(index)
if len(match) > 1 {
setting.Index = match[1]
}
if tools.Exists(setting.Root + "/.user.ini") {
userIni, _ := tools.Read(setting.Root + "/.user.ini")
if io.Exists(filepath.Join(setting.Root, ".user.ini")) {
userIni, _ := io.Read(filepath.Join(setting.Root, ".user.ini"))
if strings.Contains(userIni, "open_basedir") {
setting.OpenBasedir = true
} else {
@@ -617,32 +623,27 @@ func (r *WebsiteImpl) GetConfig(id uint) (types.WebsiteSetting, error) {
setting.OpenBasedir = false
}
cert, _ := tools.Read("/www/server/vhost/ssl/" + website.Name + ".pem")
setting.SslCertificate = cert
key, _ := tools.Read("/www/server/vhost/ssl/" + website.Name + ".key")
crt, _ := io.Read("/www/server/vhost/ssl/" + website.Name + ".pem")
setting.SslCertificate = crt
key, _ := io.Read("/www/server/vhost/ssl/" + website.Name + ".key")
setting.SslCertificateKey = key
if setting.Ssl {
ssl := tools.Cut(config, "# ssl标记位开始", "# ssl标记位结束")
ssl := str.Cut(config, "# ssl标记位开始", "# ssl标记位结束")
setting.HttpRedirect = strings.Contains(ssl, "# http重定向标记位")
setting.Hsts = strings.Contains(ssl, "# hsts标记位")
block, _ := pem.Decode([]byte(cert))
if block != nil {
cert, err := x509.ParseCertificate(block.Bytes)
if err == nil {
setting.SslNotBefore = cert.NotBefore.Format("2006-01-02 15:04:05")
setting.SslNotAfter = cert.NotAfter.Format("2006-01-02 15:04:05")
setting.SslIssuer = cert.Issuer.CommonName
setting.SslOCSPServer = cert.OCSPServer
setting.SSlDNSNames = cert.DNSNames
}
if decode, err := cert.ParseCert(crt); err == nil {
setting.SslNotBefore = decode.NotBefore.Format("2006-01-02 15:04:05")
setting.SslNotAfter = decode.NotAfter.Format("2006-01-02 15:04:05")
setting.SslIssuer = decode.Issuer.CommonName
setting.SslOCSPServer = decode.OCSPServer
setting.SSlDNSNames = decode.DNSNames
}
} else {
setting.HttpRedirect = false
setting.Hsts = false
}
waf := tools.Cut(config, "# waf标记位开始", "# waf标记位结束")
waf := str.Cut(config, "# waf标记位开始", "# waf标记位结束")
setting.Waf = strings.Contains(waf, "waf on;")
match = regexp.MustCompile(`waf_mode\s+(.+);`).FindStringSubmatch(waf)
if len(match) > 1 {
@@ -657,9 +658,9 @@ func (r *WebsiteImpl) GetConfig(id uint) (types.WebsiteSetting, error) {
setting.WafCache = match[1]
}
rewrite, _ := tools.Read("/www/server/vhost/rewrite/" + website.Name + ".conf")
rewrite, _ := io.Read("/www/server/vhost/rewrite/" + website.Name + ".conf")
setting.Rewrite = rewrite
log, _ := tools.Exec(`tail -n 100 '/www/wwwlogs/` + website.Name + `.log'`)
log, _ := shell.Execf(`tail -n 100 '/www/wwwlogs/%s.log'`, website.Name)
setting.Log = log
return setting, err
+2 -2
View File
@@ -3,12 +3,12 @@ package internal
import (
requests "github.com/TheTNB/panel/app/http/requests/website"
"github.com/TheTNB/panel/app/models"
"github.com/TheTNB/panel/types"
"github.com/TheTNB/panel/pkg/types"
)
type Website interface {
List(page int, limit int) (int64, []models.Website, error)
Add(website types.Website) (models.Website, error)
Add(website types.WebsiteAdd) (models.Website, error)
SaveConfig(config requests.SaveConfig) error
Delete(id uint) error
GetConfig(id uint) (types.WebsiteSetting, error)
+3 -64
View File
@@ -4,20 +4,17 @@ import (
"context"
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"net/http"
"strings"
"github.com/mholt/acmez/v2"
"github.com/mholt/acmez/v2/acme"
"go.uber.org/zap"
"github.com/TheTNB/panel/pkg/cert"
)
const (
@@ -77,7 +74,7 @@ func NewPrivateKeyAccount(email string, privateKey string, CA string, eab *EAB)
return nil, err
}
key, err := parsePrivateKey([]byte(privateKey))
key, err := cert.ParseKey(privateKey)
if err != nil {
return nil, err
}
@@ -102,36 +99,6 @@ func NewPrivateKeyAccount(email string, privateKey string, CA string, eab *EAB)
return &Client{Account: account, zClient: client}, nil
}
func parsePrivateKey(key []byte) (crypto.Signer, error) {
keyBlockDER, _ := pem.Decode(key)
if keyBlockDER == nil {
return nil, errors.New("invalid PEM block")
}
if keyBlockDER.Type != "PRIVATE KEY" && !strings.HasSuffix(keyBlockDER.Type, " PRIVATE KEY") {
return nil, fmt.Errorf("unknown PEM header %q", keyBlockDER.Type)
}
if parse, err := x509.ParsePKCS1PrivateKey(keyBlockDER.Bytes); err == nil {
return parse, nil
}
if parse, err := x509.ParsePKCS8PrivateKey(keyBlockDER.Bytes); err == nil {
switch parse.(type) {
case *rsa.PrivateKey, *ecdsa.PrivateKey, ed25519.PrivateKey:
return parse.(crypto.Signer), nil
default:
return nil, fmt.Errorf("found unknown private key type in PKCS#8 wrapping: %T", key)
}
}
if parse, err := x509.ParseECPrivateKey(keyBlockDER.Bytes); err == nil {
return parse, nil
}
return nil, errors.New("解析私钥失败")
}
func generatePrivateKey(keyType KeyType) (crypto.Signer, error) {
switch keyType {
case KeyEC256:
@@ -149,34 +116,6 @@ func generatePrivateKey(keyType KeyType) (crypto.Signer, error) {
return nil, errors.New("未知的密钥类型")
}
func EncodePrivateKey(key crypto.Signer) ([]byte, error) {
var pemType string
var keyBytes []byte
switch key := key.(type) {
case *ecdsa.PrivateKey:
var err error
pemType = "EC"
keyBytes, err = x509.MarshalECPrivateKey(key)
if err != nil {
return nil, err
}
case *rsa.PrivateKey:
pemType = "RSA"
keyBytes = x509.MarshalPKCS1PrivateKey(key)
case ed25519.PrivateKey:
var err error
pemType = "ED25519"
keyBytes, err = x509.MarshalPKCS8PrivateKey(key)
if err != nil {
return nil, err
}
default:
return nil, fmt.Errorf("未知的密钥类型 %T", key)
}
pemKey := pem.Block{Type: pemType + " PRIVATE KEY", Bytes: keyBytes}
return pem.EncodeToMemory(&pemKey), nil
}
func getClient(CA string) (acmez.Client, error) {
logger, err := zap.NewProduction()
if err != nil {
+7 -2
View File
@@ -7,6 +7,8 @@ import (
"github.com/libdns/libdns"
"github.com/mholt/acmez/v2"
"github.com/mholt/acmez/v2/acme"
"github.com/TheTNB/panel/pkg/cert"
)
type Certificate struct {
@@ -47,9 +49,12 @@ func (c *Client) UseManualDns(total int, check ...bool) {
}
// UseHTTP 使用 HTTP 验证
func (c *Client) UseHTTP(path string) {
// conf openresty 配置文件路径
// path 验证文件存放路径
func (c *Client) UseHTTP(conf, path string) {
c.zClient.ChallengeSolvers = map[string]acmez.Solver{
acme.ChallengeTypeHTTP01: httpSolver{
conf: conf,
path: path,
},
}
@@ -61,7 +66,7 @@ func (c *Client) ObtainSSL(ctx context.Context, domains []string, keyType KeyTyp
if err != nil {
return Certificate{}, err
}
pemPrivateKey, err := EncodePrivateKey(certPrivateKey)
pemPrivateKey, err := cert.EncodeKey(certPrivateKey)
if err != nil {
return Certificate{}, err
}
+34 -13
View File
@@ -13,9 +13,12 @@ import (
"github.com/libdns/libdns"
"github.com/mholt/acmez/v2/acme"
"golang.org/x/net/publicsuffix"
"github.com/TheTNB/panel/pkg/systemctl"
)
type httpSolver struct {
conf string
path string
}
@@ -26,12 +29,24 @@ func (s httpSolver) Present(_ context.Context, challenge acme.Challenge) error {
}
challengeFilePath := filepath.Join(s.path, challenge.HTTP01ResourcePath())
if err = os.MkdirAll(filepath.Dir(challengeFilePath), 0o755); err != nil {
return fmt.Errorf("无法在网站目录创建 HTTP 挑战所需的目录: %w", err)
if err = os.MkdirAll(filepath.Dir(challengeFilePath), 0755); err != nil {
return fmt.Errorf("无法在网站目录创建HTTP挑战所需的目录: %w", err)
}
if err = os.WriteFile(challengeFilePath, []byte(challenge.KeyAuthorization), 0o644); err != nil {
return fmt.Errorf("无法在网站目录创建 HTTP 挑战所需的文件: %w", err)
if err = os.WriteFile(challengeFilePath, []byte(challenge.KeyAuthorization), 0644); err != nil {
return fmt.Errorf("无法在网站目录创建HTTP挑战所需的文件: %w", err)
}
conf := fmt.Sprintf(`location = /.well-known/acme-challenge/%s {
default_type text/plain;
return 200 %q;
}
`, challenge.Token, challenge.KeyAuthorization)
if err = os.WriteFile(s.conf, []byte(conf), 0644); err != nil {
return fmt.Errorf("无法写入OpenResty配置文件: %w", err)
}
if err = systemctl.Reload("openresty"); err != nil {
return fmt.Errorf("无法重载OpenResty: %w", err)
}
return nil
@@ -44,7 +59,13 @@ func (s httpSolver) CleanUp(_ context.Context, challenge acme.Challenge) error {
}
if err := os.Remove(filepath.Join(s.path, challenge.HTTP01ResourcePath())); err != nil {
return fmt.Errorf("无法删除 HTTP 挑战文件: %w", err)
return fmt.Errorf("无法删除HTTP挑战文件: %w", err)
}
if err := os.WriteFile(s.conf, []byte{}, 0644); err != nil {
return fmt.Errorf("无法清空OpenResty配置文件: %w", err)
}
if err := systemctl.Reload("openresty"); err != nil {
return fmt.Errorf("无法重载OpenResty: %w", err)
}
return nil
@@ -61,11 +82,11 @@ func (s dnsSolver) Present(ctx context.Context, challenge acme.Challenge) error
keyAuth := challenge.DNS01KeyAuthorization()
provider, err := s.getDNSProvider()
if err != nil {
return fmt.Errorf("获取 DNS 提供商失败: %w", err)
return fmt.Errorf("获取DNS提供商失败: %w", err)
}
zone, err := publicsuffix.EffectiveTLDPlusOne(dnsName)
if err != nil {
return fmt.Errorf("获取域名 %q 的顶级域失败: %w", dnsName, err)
return fmt.Errorf("获取域名%q的顶级域失败: %w", dnsName, err)
}
rec := libdns.Record{
@@ -76,10 +97,10 @@ func (s dnsSolver) Present(ctx context.Context, challenge acme.Challenge) error
results, err := provider.AppendRecords(ctx, zone+".", []libdns.Record{rec})
if err != nil {
return fmt.Errorf("域名 %q 添加临时记录 %q 失败: %w", zone, dnsName, err)
return fmt.Errorf("域名%q添加临时记录%q失败: %w", zone, dnsName, err)
}
if len(results) != 1 {
return fmt.Errorf("预期添加 1 条记录,但实际添加了 %d 条记录", len(results))
return fmt.Errorf("预期添加1条记录,但实际添加了%d条记录", len(results))
}
s.records = &results
@@ -90,11 +111,11 @@ func (s dnsSolver) CleanUp(ctx context.Context, challenge acme.Challenge) error
dnsName := challenge.DNS01TXTRecordName()
provider, err := s.getDNSProvider()
if err != nil {
return fmt.Errorf("获取 DNS 提供商失败: %w", err)
return fmt.Errorf("获取DNS提供商失败: %w", err)
}
zone, err := publicsuffix.EffectiveTLDPlusOne(dnsName)
if err != nil {
return fmt.Errorf("获取域名 %q 的顶级域失败: %w", dnsName, err)
return fmt.Errorf("获取域名%q的顶级域失败: %w", dnsName, err)
}
ctx, cancel := context.WithTimeout(ctx, 2*time.Minute)
@@ -102,7 +123,7 @@ func (s dnsSolver) CleanUp(ctx context.Context, challenge acme.Challenge) error
_, err = provider.DeleteRecords(ctx, zone+".", *s.records)
if err != nil {
return fmt.Errorf("域名 %q 删除临时记录 %q 失败: %w", zone, dnsName, err)
return fmt.Errorf("域名%q删除临时记录%q失败: %w", zone, dnsName, err)
}
return nil
@@ -126,7 +147,7 @@ func (s dnsSolver) getDNSProvider() (DNSProvider, error) {
APIToken: s.param.APIkey,
}
default:
return nil, fmt.Errorf("未知的 DNS 提供商 %q", s.dns)
return nil, fmt.Errorf("未知的DNS提供商 %q", s.dns)
}
return dns, nil
+89
View File
@@ -0,0 +1,89 @@
package cert
import (
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"strings"
)
func ParseCert(crt string) (x509.Certificate, error) {
certBlock, _ := pem.Decode([]byte(crt))
if certBlock == nil {
return x509.Certificate{}, errors.New("invalid PEM block")
}
cert, err := x509.ParseCertificate(certBlock.Bytes)
if err != nil {
return x509.Certificate{}, err
}
return *cert, nil
}
func ParseKey(key string) (crypto.Signer, error) {
keyBlockDER, _ := pem.Decode([]byte(key))
if keyBlockDER == nil {
return nil, errors.New("invalid PEM block")
}
if keyBlockDER.Type != "PRIVATE KEY" && !strings.HasSuffix(keyBlockDER.Type, " PRIVATE KEY") {
return nil, fmt.Errorf("unknown PEM header %q", keyBlockDER.Type)
}
if parse, err := x509.ParsePKCS1PrivateKey(keyBlockDER.Bytes); err == nil {
return parse, nil
}
if parse, err := x509.ParsePKCS8PrivateKey(keyBlockDER.Bytes); err == nil {
switch parse.(type) {
case *rsa.PrivateKey, *ecdsa.PrivateKey, ed25519.PrivateKey:
return parse.(crypto.Signer), nil
default:
return nil, fmt.Errorf("found unknown private key type in PKCS#8 wrapping: %T", key)
}
}
if parse, err := x509.ParseECPrivateKey(keyBlockDER.Bytes); err == nil {
return parse, nil
}
return nil, errors.New("解析私钥失败")
}
func EncodeCert(cert x509.Certificate) ([]byte, error) {
pemCert := pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}
return pem.EncodeToMemory(&pemCert), nil
}
func EncodeKey(key crypto.Signer) ([]byte, error) {
var pemType string
var keyBytes []byte
switch key := key.(type) {
case *ecdsa.PrivateKey:
var err error
pemType = "EC"
keyBytes, err = x509.MarshalECPrivateKey(key)
if err != nil {
return nil, err
}
case *rsa.PrivateKey:
pemType = "RSA"
keyBytes = x509.MarshalPKCS1PrivateKey(key)
case ed25519.PrivateKey:
var err error
pemType = "ED25519"
keyBytes, err = x509.MarshalPKCS8PrivateKey(key)
if err != nil {
return nil, err
}
default:
return nil, fmt.Errorf("未知的密钥类型 %T", key)
}
pemKey := pem.Block{Type: pemType + " PRIVATE KEY", Bytes: keyBytes}
return pem.EncodeToMemory(&pemKey), nil
}
+173
View File
@@ -0,0 +1,173 @@
package db
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql"
"github.com/TheTNB/panel/pkg/types"
)
type MySQL struct {
db *sql.DB
username string
password string
address string
}
func NewMySQL(username, password, address string, typ ...string) (*MySQL, error) {
dsn := fmt.Sprintf("%s:%s@tcp(%s)/", username, password, address)
if len(typ) > 0 && typ[0] == "unix" {
dsn = fmt.Sprintf("%s:%s@unix(%s)/", username, password, address)
}
db, err := sql.Open("mysql", dsn)
if err != nil {
return nil, fmt.Errorf("初始化MySQL连接失败: %w", err)
}
if db.Ping() != nil {
return nil, fmt.Errorf("连接MySQL失败: %w", err)
}
return &MySQL{
db: db,
username: username,
password: password,
address: address,
}, nil
}
func (m *MySQL) Close() error {
return m.db.Close()
}
func (m *MySQL) Ping() error {
return m.db.Ping()
}
func (m *MySQL) Query(query string, args ...any) (*sql.Rows, error) {
return m.db.Query(query, args...)
}
func (m *MySQL) QueryRow(query string, args ...any) *sql.Row {
return m.db.QueryRow(query, args...)
}
func (m *MySQL) Exec(query string, args ...any) (sql.Result, error) {
return m.db.Exec(query, args...)
}
func (m *MySQL) Prepare(query string) (*sql.Stmt, error) {
return m.db.Prepare(query)
}
func (m *MySQL) DatabaseCreate(name string) error {
_, err := m.Exec(fmt.Sprintf("CREATE DATABASE IF NOT EXISTS %s", name))
m.flushPrivileges()
return err
}
func (m *MySQL) DatabaseDrop(name string) error {
_, err := m.Exec(fmt.Sprintf("DROP DATABASE IF EXISTS %s", name))
m.flushPrivileges()
return err
}
func (m *MySQL) UserCreate(user, password string) error {
_, err := m.Exec(fmt.Sprintf("CREATE USER IF NOT EXISTS '%s'@'localhost' IDENTIFIED BY '%s'", user, password))
m.flushPrivileges()
return err
}
func (m *MySQL) UserDrop(user string) error {
_, err := m.Exec(fmt.Sprintf("DROP USER IF EXISTS '%s'@'localhost'", user))
m.flushPrivileges()
return err
}
func (m *MySQL) UserPassword(user, password string) error {
_, err := m.Exec(fmt.Sprintf("ALTER USER '%s'@'localhost' IDENTIFIED BY '%s'", user, password))
m.flushPrivileges()
return err
}
func (m *MySQL) PrivilegesGrant(user, database string) error {
_, err := m.Exec(fmt.Sprintf("GRANT ALL PRIVILEGES ON %s.* TO '%s'@'localhost'", database, user))
m.flushPrivileges()
return err
}
func (m *MySQL) PrivilegesRevoke(user, database string) error {
_, err := m.Exec(fmt.Sprintf("REVOKE ALL PRIVILEGES ON %s.* FROM '%s'@'localhost'", database, user))
m.flushPrivileges()
return err
}
func (m *MySQL) Users() ([]types.MySQLUser, error) {
rows, err := m.Query("SELECT user, host FROM mysql.user")
if err != nil {
return nil, err
}
defer rows.Close()
var users []types.MySQLUser
for rows.Next() {
var user, host string
if err := rows.Scan(&user, &host); err != nil {
continue
}
grants, err := m.userGrants(user, host)
if err != nil {
continue
}
users = append(users, types.MySQLUser{
User: user,
Host: host,
Grants: grants,
})
}
return users, nil
}
func (m *MySQL) Databases() ([]types.MySQLDatabase, error) {
rows, err := m.Query("SHOW DATABASES")
if err != nil {
return nil, err
}
defer rows.Close()
var databases []types.MySQLDatabase
for rows.Next() {
var database string
if err := rows.Scan(&database); err != nil {
continue
}
databases = append(databases, types.MySQLDatabase{
Name: database,
})
}
return databases, nil
}
func (m *MySQL) userGrants(user, host string) ([]string, error) {
rows, err := m.Query(fmt.Sprintf("SHOW GRANTS FOR '%s'@'%s'", user, host))
if err != nil {
return nil, err
}
defer rows.Close()
var grants []string
for rows.Next() {
var grant string
if err := rows.Scan(&grant); err != nil {
continue
}
grants = append(grants, grant)
}
return grants, nil
}
func (m *MySQL) flushPrivileges() {
_, _ = m.Exec("FLUSH PRIVILEGES")
}
+33
View File
@@ -0,0 +1,33 @@
package db
import (
"errors"
"fmt"
"github.com/TheTNB/panel/pkg/shell"
"github.com/TheTNB/panel/pkg/systemctl"
)
// MySQLResetRootPassword 重置 MySQL root密码
func MySQLResetRootPassword(password string) error {
_ = systemctl.Stop("mysqld")
if run, err := systemctl.Status("mysqld"); err != nil || run {
return fmt.Errorf("停止MySQL失败: %w", err)
}
_, _ = shell.Execf(`systemctl set-environment MYSQLD_OPTS="--skip-grant-tables --skip-networking"`)
if err := systemctl.Start("mysqld"); err != nil {
return fmt.Errorf("以安全模式启动MySQL失败: %w", err)
}
if _, err := shell.Execf(`mysql -uroot -e "FLUSH PRIVILEGES;UPDATE mysql.user SET authentication_string=null WHERE user='root' AND host='localhost';ALTER USER 'root'@'localhost' IDENTIFIED BY '%s';FLUSH PRIVILEGES;"`, password); err != nil {
return errors.New("设置root密码失败")
}
if err := systemctl.Stop("mysqld"); err != nil {
return fmt.Errorf("停止MySQL失败: %w", err)
}
_, _ = shell.Execf(`systemctl unset-environment MYSQLD_OPTS`)
if err := systemctl.Start("mysqld"); err != nil {
return fmt.Errorf("启动MySQL失败: %w", err)
}
return nil
}
+85
View File
@@ -0,0 +1,85 @@
package io
import (
"os"
"path/filepath"
"strings"
"github.com/mholt/archiver/v3"
)
// Write 写入文件
func Write(path string, data string, permission os.FileMode) error {
if err := os.MkdirAll(filepath.Dir(path), permission); err != nil {
return err
}
err := os.WriteFile(path, []byte(data), permission)
if err != nil {
return err
}
return nil
}
// WriteAppend 追加写入文件
func WriteAppend(path string, data string) error {
file, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return err
}
defer file.Close()
_, err = file.WriteString(data)
if err != nil {
return err
}
return nil
}
// Read 读取文件
func Read(path string) (string, error) {
data, err := os.ReadFile(path)
return string(data), err
}
// FileInfo 获取文件大小
func FileInfo(path string) (os.FileInfo, error) {
return os.Stat(path)
}
// UnArchive 智能解压文件
func UnArchive(file string, dst string) error {
return archiver.Unarchive(file, dst)
}
// Archive 智能压缩文件
func Archive(src []string, dst string) error {
return archiver.Archive(src, dst)
}
// TempFile 创建临时文件
func TempFile(prefix string) (*os.File, error) {
return os.CreateTemp("", prefix)
}
// IsSymlink 判读是否为软链接
func IsSymlink(mode os.FileMode) bool {
return mode&os.ModeSymlink != 0
}
// IsHidden 判断是否为隐藏文件
func IsHidden(path string) bool {
_, file := filepath.Split(path)
return strings.HasPrefix(file, ".")
}
// GetSymlink 获取软链接目标
func GetSymlink(path string) string {
linkPath, err := os.Readlink(path)
if err != nil {
return ""
}
return linkPath
}
+151
View File
@@ -0,0 +1,151 @@
package io
import (
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
)
// Remove 删除文件/目录
func Remove(path string) error {
return os.RemoveAll(path)
}
// Mkdir 创建目录
func Mkdir(path string, permission os.FileMode) error {
return os.MkdirAll(path, permission)
}
// Chmod 修改文件/目录权限
func Chmod(path string, permission os.FileMode) error {
cmd := exec.Command("chmod", "-R", fmt.Sprintf("%o", permission), path)
return cmd.Run()
}
// Chown 修改文件或目录所有者
func Chown(path, user, group string) error {
cmd := exec.Command("chown", "-R", user+":"+group, path)
return cmd.Run()
}
// Exists 判断路径是否存在
func Exists(path string) bool {
_, err := os.Stat(path)
return err == nil
}
// Empty 判断路径是否为空
func Empty(path string) bool {
files, err := os.ReadDir(path)
if err != nil {
return true
}
return len(files) == 0
}
func Mv(src, dst string) error {
err := os.Rename(src, dst)
if err != nil {
// 如果在不同的文件系统中移动文件,os.Rename 可能会失败
err = Cp(src, dst)
if err != nil {
return err
}
err = os.RemoveAll(src)
}
return err
}
// Cp 复制文件或目录
func Cp(src, dst string) error {
srcInfo, err := os.Stat(src)
if err != nil {
return err
}
if srcInfo.IsDir() {
return copyDir(src, dst)
}
return copyFile(src, dst)
}
func copyFile(src, dst string) error {
srcFile, err := os.Open(src)
if err != nil {
return err
}
defer srcFile.Close()
dstFile, err := os.Create(dst)
if err != nil {
return err
}
defer dstFile.Close()
_, err = io.Copy(dstFile, srcFile)
return err
}
func copyDir(src, dst string) error {
srcInfo, err := os.Stat(src)
if err != nil {
return err
}
err = os.MkdirAll(dst, srcInfo.Mode())
if err != nil {
return err
}
entries, err := os.ReadDir(src)
if err != nil {
return err
}
for _, entry := range entries {
srcPath := filepath.Join(src, entry.Name())
dstPath := filepath.Join(dst, entry.Name())
if entry.IsDir() {
err = copyDir(srcPath, dstPath)
if err != nil {
return err
}
} else {
err = copyFile(srcPath, dstPath)
if err != nil {
return err
}
}
}
return nil
}
// Size 获取路径大小
func Size(path string) (int64, error) {
var size int64
err := filepath.Walk(path, func(filePath string, info os.FileInfo, walkErr error) error {
if walkErr != nil {
return walkErr
}
size += info.Size()
return nil
})
return size, err
}
// TempDir 创建临时目录
func TempDir(prefix string) (string, error) {
return os.MkdirTemp("", prefix)
}
// ReadDir 读取目录
func ReadDir(path string) ([]os.DirEntry, error) {
return os.ReadDir(path)
}
+22
View File
@@ -0,0 +1,22 @@
package migrate
import (
"fmt"
"github.com/go-gormigrate/gormigrate/v2"
"gorm.io/gorm"
)
func Migrate(db *gorm.DB) {
options := &gormigrate.Options{
TableName: "new_migrations",
IDColumnName: "id",
IDColumnSize: 255,
}
migrator := gormigrate.New(db, options, []*gormigrate.Migration{
Init,
})
if err := migrator.Migrate(); err != nil {
panic(fmt.Sprintf("Failed to migrate database: %v", err))
}
}
+42
View File
@@ -0,0 +1,42 @@
package migrate
import (
"github.com/go-gormigrate/gormigrate/v2"
"gorm.io/gorm"
"github.com/TheTNB/panel/app/models"
)
var Init = &gormigrate.Migration{
ID: "20240624-init",
Migrate: func(tx *gorm.DB) error {
return tx.AutoMigrate(
&models.Cert{},
&models.CertDNS{},
&models.CertUser{},
&models.Cron{},
&models.Database{},
&models.Monitor{},
&models.Plugin{},
&models.Setting{},
&models.Task{},
&models.User{},
&models.Website{},
)
},
Rollback: func(tx *gorm.DB) error {
return tx.Migrator().DropTable(
&models.Cert{},
&models.CertDNS{},
&models.CertUser{},
&models.Cron{},
&models.Database{},
&models.Monitor{},
&models.Plugin{},
&models.Setting{},
&models.Task{},
&models.User{},
&models.Website{},
)
},
}
+1 -1
View File
@@ -1,4 +1,4 @@
package tools
package os
import (
"os"
+1 -8
View File
@@ -1,9 +1,8 @@
package tools
package os
import (
"testing"
"github.com/goravel/framework/support/env"
"github.com/stretchr/testify/suite"
)
@@ -16,15 +15,9 @@ func TestOSHelperTestSuite(t *testing.T) {
}
func (s *OSHelperTestSuite) TestIsDebian() {
if env.IsWindows() {
return
}
s.True(IsDebian())
}
func (s *OSHelperTestSuite) TestIsRHEL() {
if env.IsWindows() {
return
}
s.False(IsRHEL())
}
+25
View File
@@ -0,0 +1,25 @@
package os
import (
"os/user"
"github.com/spf13/cast"
)
// GetUser 通过 uid 获取用户名
func GetUser(uid uint32) string {
usr, err := user.LookupId(cast.ToString(uid))
if err != nil {
return ""
}
return usr.Username
}
// GetGroup 通过 gid 获取组名
func GetGroup(gid uint32) string {
usr, err := user.LookupGroupId(cast.ToString(gid))
if err != nil {
return ""
}
return usr.Name
}
+120
View File
@@ -0,0 +1,120 @@
package shell
import (
"bytes"
"errors"
"fmt"
"os"
"os/exec"
"strings"
"time"
"github.com/goravel/framework/support"
"github.com/TheTNB/panel/pkg/slice"
)
// Execf 执行 shell 命令
func Execf(shell string, args ...any) (string, error) {
if !CheckArgs(slice.ToString(args)...) {
return "", errors.New("你想干什么?")
}
var cmd *exec.Cmd
_ = os.Setenv("LC_ALL", "C")
cmd = exec.Command("bash", "-c", fmt.Sprintf(shell, args...))
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
err := cmd.Run()
if err != nil {
return "", errors.New(strings.TrimSpace(stderr.String()))
}
return strings.TrimSpace(stdout.String()), err
}
// ExecfAsync 异步执行 shell 命令
func ExecfAsync(shell string, args ...any) error {
if !CheckArgs(slice.ToString(args)...) {
return errors.New("你想干什么?")
}
var cmd *exec.Cmd
_ = os.Setenv("LC_ALL", "C")
cmd = exec.Command("bash", "-c", fmt.Sprintf(shell, args...))
err := cmd.Start()
if err != nil {
return err
}
go func() {
err := cmd.Wait()
if err != nil {
if support.Env == support.EnvTest {
fmt.Println(err.Error())
panic(err)
}
}
}()
return nil
}
// ExecfWithTimeout 执行 shell 命令并设置超时时间
func ExecfWithTimeout(timeout time.Duration, shell string, args ...any) (string, error) {
if !CheckArgs(slice.ToString(args)...) {
return "", errors.New("你想干什么?")
}
var cmd *exec.Cmd
_ = os.Setenv("LC_ALL", "C")
cmd = exec.Command("bash", "-c", fmt.Sprintf(shell, args...))
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
err := cmd.Start()
if err != nil {
return "", err
}
done := make(chan error)
go func() {
done <- cmd.Wait()
}()
select {
case <-time.After(timeout):
_ = cmd.Process.Kill()
return "", errors.New("执行超时")
case err = <-done:
if err != nil {
return "", errors.New(strings.TrimSpace(stderr.String()))
}
}
return strings.TrimSpace(stdout.String()), err
}
// CheckArgs 检查危险的参数
func CheckArgs(args ...string) bool {
if len(args) == 0 {
return true
}
dangerous := []string{"&", "|", ";", "$", "'", `"`, "(", ")", "`", "\n", "\r", ">", "<", "{", "}", "[", "]", "\\"}
for _, arg := range args {
for _, char := range dangerous {
if strings.Contains(arg, char) {
return false
}
}
}
return true
}
+30
View File
@@ -0,0 +1,30 @@
package slice
import "github.com/spf13/cast"
// ToAny 将任意类型切片转换为 []any
func ToAny[T any](s []T) []any {
result := make([]any, len(s))
for i, v := range s {
result[i] = v
}
return result
}
// ToString 将任意类型切片转换为 []string
func ToString[T any](s []T) []string {
result := make([]string, len(s))
for i, v := range s {
result[i] = cast.ToString(v)
}
return result
}
// ToInt 将任意类型切片转换为 []int
func ToInt[T any](s []T) []int {
result := make([]int, len(s))
for i, v := range s {
result[i] = cast.ToInt(v)
}
return result
}
+2 -2
View File
@@ -5,7 +5,7 @@ import (
"golang.org/x/crypto/ssh"
"github.com/TheTNB/panel/pkg/tools"
"github.com/TheTNB/panel/pkg/io"
)
type AuthMethod int8
@@ -68,7 +68,7 @@ func NewSSHClient(conf *ClientConfig) (*ssh.Client, error) {
}
func getKey(keyPath string) (ssh.Signer, error) {
key, err := tools.Read(keyPath)
key, err := io.Read(keyPath)
if err != nil {
return nil, err
}
+1
View File
@@ -63,6 +63,7 @@ func (t *Turn) Write(p []byte) (n int, err error) {
return writer.Write(p)
}
func (t *Turn) Close() error {
if t.Session != nil {
t.Session.Close()
+1 -1
View File
@@ -1,4 +1,4 @@
package tools
package str
import (
"crypto/md5"
@@ -1,4 +1,4 @@
package tools
package str
import (
"testing"
+73
View File
@@ -0,0 +1,73 @@
package systemctl
import (
"errors"
"os/exec"
"strings"
"github.com/TheTNB/panel/pkg/shell"
)
// Status 获取服务状态
func Status(name string) (bool, error) {
output, err := shell.Execf("systemctl status %s | grep Active | grep -v grep | awk '{print $2}'", name)
return output == "active", err
}
// IsEnabled 服务是否启用
func IsEnabled(name string) (bool, error) {
cmd := exec.Command("systemctl", "is-enabled", name)
output, _ := cmd.CombinedOutput()
status := strings.TrimSpace(string(output))
switch status {
case "enabled":
return true, nil
case "disabled":
return false, nil
case "masked":
return false, errors.New("服务已被屏蔽")
case "static":
return false, errors.New("服务已被静态启用")
case "indirect":
return false, errors.New("服务已被间接启用")
default:
return false, errors.New("无法确定服务状态")
}
}
// Start 启动服务
func Start(name string) error {
_, err := shell.Execf("systemctl start %s", name)
return err
}
// Stop 停止服务
func Stop(name string) error {
_, err := shell.Execf("systemctl stop %s", name)
return err
}
// Restart 重启服务
func Restart(name string) error {
_, err := shell.Execf("systemctl restart %s", name)
return err
}
// Reload 重载服务
func Reload(name string) error {
_, err := shell.Execf("systemctl reload %s", name)
return err
}
// Enable 启用服务
func Enable(name string) error {
_, err := shell.Execf("systemctl enable %s", name)
return err
}
// Disable 禁用服务
func Disable(name string) error {
_, err := shell.Execf("systemctl disable %s", name)
return err
}
-72
View File
@@ -1,72 +0,0 @@
package tools
import (
"errors"
"fmt"
"os/exec"
"strings"
)
// ServiceStatus 获取服务状态
func ServiceStatus(name string) (bool, error) {
output, err := Exec(fmt.Sprintf("systemctl status %s | grep Active | grep -v grep | awk '{print $2}'", name))
return output == "active", err
}
// ServiceIsEnabled 服务是否启用
func ServiceIsEnabled(name string) (bool, error) {
cmd := exec.Command("systemctl", "is-enabled", name)
output, _ := cmd.CombinedOutput()
status := strings.TrimSpace(string(output))
switch status {
case "enabled":
return true, nil
case "disabled":
return false, nil
case "masked":
return false, errors.New("服务已被屏蔽")
case "static":
return false, errors.New("服务已被静态启用")
case "indirect":
return false, errors.New("服务已被间接启用")
default:
return false, errors.New("无法确定服务状态")
}
}
// ServiceStart 启动服务
func ServiceStart(name string) error {
_, err := Exec(fmt.Sprintf("systemctl start %s", name))
return err
}
// ServiceStop 停止服务
func ServiceStop(name string) error {
_, err := Exec(fmt.Sprintf("systemctl stop %s", name))
return err
}
// ServiceRestart 重启服务
func ServiceRestart(name string) error {
_, err := Exec(fmt.Sprintf("systemctl restart %s", name))
return err
}
// ServiceReload 重载服务
func ServiceReload(name string) error {
_, err := Exec(fmt.Sprintf("systemctl reload %s", name))
return err
}
// ServiceEnable 启用服务
func ServiceEnable(name string) error {
_, err := Exec(fmt.Sprintf("systemctl enable %s", name))
return err
}
// ServiceDisable 禁用服务
func ServiceDisable(name string) error {
_, err := Exec(fmt.Sprintf("systemctl disable %s", name))
return err
}
-305
View File
@@ -1,305 +0,0 @@
package tools
import (
"bytes"
"errors"
"fmt"
"io"
"os"
"os/exec"
"os/user"
"path/filepath"
"strings"
"github.com/goravel/framework/support"
"github.com/goravel/framework/support/env"
"github.com/mholt/archiver/v3"
"github.com/spf13/cast"
)
// Write 写入文件
func Write(path string, data string, permission os.FileMode) error {
if err := os.MkdirAll(filepath.Dir(path), permission); err != nil {
return err
}
err := os.WriteFile(path, []byte(data), permission)
if err != nil {
return err
}
return nil
}
// WriteAppend 追加写入文件
func WriteAppend(path string, data string) error {
file, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return err
}
defer file.Close()
_, err = file.WriteString(data)
if err != nil {
return err
}
return nil
}
// Read 读取文件
func Read(path string) (string, error) {
data, err := os.ReadFile(path)
return string(data), err
}
// Remove 删除文件/目录
func Remove(path string) error {
return os.RemoveAll(path)
}
// Exec 执行 shell 命令
func Exec(shell string) (string, error) {
var cmd *exec.Cmd
if env.IsLinux() {
cmd = exec.Command("bash", "-c", "LC_ALL=C "+shell)
} else {
cmd = exec.Command("cmd", "/C", "chcp 65001 >nul && "+shell)
}
var stdoutBuf, stderrBuf bytes.Buffer
cmd.Stdout = &stdoutBuf
cmd.Stderr = &stderrBuf
err := cmd.Run()
if err != nil {
return "", errors.New(strings.TrimSpace(stderrBuf.String()))
}
return strings.TrimSpace(stdoutBuf.String()), err
}
// ExecAsync 异步执行 shell 命令
func ExecAsync(shell string) error {
var cmd *exec.Cmd
if env.IsLinux() {
cmd = exec.Command("bash", "-c", "LC_ALL=C "+shell)
} else {
cmd = exec.Command("cmd", "/C", "chcp 65001 >nul && "+shell)
}
err := cmd.Start()
if err != nil {
return err
}
go func() {
err := cmd.Wait()
if err != nil {
if support.Env == support.EnvTest {
fmt.Println(err.Error())
panic(err)
}
}
}()
return nil
}
// Mkdir 创建目录
func Mkdir(path string, permission os.FileMode) error {
return os.MkdirAll(path, permission)
}
// Chmod 修改文件/目录权限
func Chmod(path string, permission os.FileMode) error {
if env.IsWindows() {
return errors.New("chmod is not supported on Windows")
}
cmd := exec.Command("chmod", "-R", fmt.Sprintf("%o", permission), path)
return cmd.Run()
}
// Chown 修改文件或目录所有者
func Chown(path, user, group string) error {
if env.IsWindows() {
return errors.New("chown is not supported on Windows")
}
cmd := exec.Command("chown", "-R", user+":"+group, path)
return cmd.Run()
}
// Exists 判断路径是否存在
func Exists(path string) bool {
_, err := os.Stat(path)
return err == nil
}
// Empty 判断路径是否为空
func Empty(path string) bool {
files, err := os.ReadDir(path)
if err != nil {
return true
}
return len(files) == 0
}
func Mv(src, dst string) error {
err := os.Rename(src, dst)
if err != nil {
// 如果在不同的文件系统中移动文件,os.Rename 可能会失败
err = Cp(src, dst)
if err != nil {
return err
}
err = os.RemoveAll(src)
}
return err
}
// Cp 复制文件或目录
func Cp(src, dst string) error {
srcInfo, err := os.Stat(src)
if err != nil {
return err
}
if srcInfo.IsDir() {
return copyDir(src, dst)
}
return copyFile(src, dst)
}
func copyFile(src, dst string) error {
srcFile, err := os.Open(src)
if err != nil {
return err
}
defer srcFile.Close()
dstFile, err := os.Create(dst)
if err != nil {
return err
}
defer dstFile.Close()
_, err = io.Copy(dstFile, srcFile)
return err
}
func copyDir(src, dst string) error {
srcInfo, err := os.Stat(src)
if err != nil {
return err
}
err = os.MkdirAll(dst, srcInfo.Mode())
if err != nil {
return err
}
entries, err := os.ReadDir(src)
if err != nil {
return err
}
for _, entry := range entries {
srcPath := filepath.Join(src, entry.Name())
dstPath := filepath.Join(dst, entry.Name())
if entry.IsDir() {
err = copyDir(srcPath, dstPath)
if err != nil {
return err
}
} else {
err = copyFile(srcPath, dstPath)
if err != nil {
return err
}
}
}
return nil
}
// Size 获取路径大小
func Size(path string) (int64, error) {
var size int64
err := filepath.Walk(path, func(filePath string, info os.FileInfo, walkErr error) error {
if walkErr != nil {
return walkErr
}
size += info.Size()
return nil
})
return size, err
}
// FileInfo 获取文件大小
func FileInfo(path string) (os.FileInfo, error) {
return os.Stat(path)
}
// UnArchive 智能解压文件
func UnArchive(file string, dst string) error {
return archiver.Unarchive(file, dst)
}
// Archive 智能压缩文件
func Archive(src []string, dst string) error {
return archiver.Archive(src, dst)
}
// TempDir 创建临时目录
func TempDir(prefix string) (string, error) {
return os.MkdirTemp("", prefix)
}
// TempFile 创建临时文件
func TempFile(prefix string) (*os.File, error) {
return os.CreateTemp("", prefix)
}
// IsSymlink 判读是否为软链接
func IsSymlink(mode os.FileMode) bool {
return mode&os.ModeSymlink != 0
}
// IsHidden 判断是否为隐藏文件
func IsHidden(path string) bool {
_, file := filepath.Split(path)
return strings.HasPrefix(file, ".")
}
// GetSymlink 获取软链接目标
func GetSymlink(path string) string {
linkPath, err := os.Readlink(path)
if err != nil {
return ""
}
return linkPath
}
// GetUser 通过 uid 获取用户名
func GetUser(uid uint32) string {
usr, err := user.LookupId(cast.ToString(uid))
if err != nil {
return ""
}
return usr.Username
}
// GetGroup 通过 gid 获取组名
func GetGroup(gid uint32) string {
usr, err := user.LookupGroupId(cast.ToString(gid))
if err != nil {
return ""
}
return usr.Name
}
-330
View File
@@ -1,330 +0,0 @@
package tools
import (
"os/user"
"path/filepath"
"testing"
"time"
"github.com/goravel/framework/support/env"
"github.com/stretchr/testify/suite"
)
type SystemHelperTestSuite struct {
suite.Suite
}
func TestSystemHelperTestSuite(t *testing.T) {
suite.Run(t, &SystemHelperTestSuite{})
}
func (s *SystemHelperTestSuite) WriteCreatesFileWithCorrectContent() {
filePath, _ := TempFile("testfile")
err := Write(filePath.Name(), "test data", 0644)
s.Nil(err)
content, _ := Read(filePath.Name())
s.Equal("test data", content)
s.Nil(filePath.Close())
s.Nil(Remove(filePath.Name()))
}
func (s *SystemHelperTestSuite) WriteCreatesDirectoriesIfNeeded() {
filePath, _ := TempFile("testdir/testfile")
err := Write(filePath.Name(), "test data", 0644)
s.Nil(err)
content, _ := Read(filePath.Name())
s.Equal("test data", content)
s.Nil(filePath.Close())
s.Nil(Remove(filePath.Name()))
}
func (s *SystemHelperTestSuite) WriteFailsIfDirectoryCannotBeCreated() {
filePath := "/nonexistent/testfile"
err := Write(filePath, "test data", 0644)
s.NotNil(err)
}
func (s *SystemHelperTestSuite) WriteFailsIfFileCannotBeWritten() {
filePath, _ := TempFile("testfile")
s.Nil(filePath.Close())
s.Nil(Chmod(filePath.Name(), 0400))
err := Write(filePath.Name(), "test data", 0644)
s.NotNil(err)
s.Nil(Chmod(filePath.Name(), 0644))
s.Nil(Remove(filePath.Name()))
}
func (s *SystemHelperTestSuite) WriteAppendSuccessfullyAppendsDataToFile() {
filePath, _ := TempFile("testfile")
err := Write(filePath.Name(), "initial data", 0644)
s.Nil(err)
err = WriteAppend(filePath.Name(), " appended data")
s.Nil(err)
content, _ := Read(filePath.Name())
s.Equal("initial data appended data", content)
s.Nil(filePath.Close())
s.Nil(Remove(filePath.Name()))
}
func (s *SystemHelperTestSuite) WriteAppendCreatesFileIfNotExists() {
filePath, _ := TempFile("testfile")
s.Nil(filePath.Close())
s.Nil(Remove(filePath.Name()))
err := WriteAppend(filePath.Name(), "test data")
s.Nil(err)
content, _ := Read(filePath.Name())
s.Equal("test data", content)
s.Nil(Remove(filePath.Name()))
}
func (s *SystemHelperTestSuite) WriteAppendReturnsErrorIfPathIsADirectory() {
dirPath, _ := TempDir("testdir")
err := WriteAppend(dirPath, "test data")
s.NotNil(err)
s.Nil(Remove(dirPath))
}
func (s *SystemHelperTestSuite) ReadSuccessfullyReadsFileContent() {
filePath, _ := TempFile("testfile")
err := Write(filePath.Name(), "test data", 0644)
s.Nil(err)
content, err := Read(filePath.Name())
s.Nil(err)
s.Equal("test data", content)
s.Nil(filePath.Close())
s.Nil(Remove(filePath.Name()))
}
func (s *SystemHelperTestSuite) ReadReturnsErrorForNonExistentFile() {
_, err := Read("/nonexistent/testfile")
s.NotNil(err)
}
func (s *SystemHelperTestSuite) RemoveSuccessfullyRemovesFile() {
filePath, _ := TempFile("testfile")
err := Write(filePath.Name(), "test data", 0644)
s.Nil(err)
err = Remove(filePath.Name())
s.Nil(err)
s.False(Exists(filePath.Name()))
}
func (s *SystemHelperTestSuite) RemoveReturnsErrorForNonExistentFile() {
err := Remove("/nonexistent/testfile")
s.NotNil(err)
}
func (s *SystemHelperTestSuite) TestExec() {
output, err := Exec("echo test")
s.Equal("test", output)
s.Nil(err)
}
func (s *SystemHelperTestSuite) TestExecAsync() {
command := "echo test > test.txt"
if env.IsWindows() {
command = "echo test> test.txt"
}
err := ExecAsync(command)
s.Nil(err)
time.Sleep(time.Second)
content, err := Read("test.txt")
s.Nil(err)
condition := "test\n"
if env.IsWindows() {
condition = "test\r\n"
}
s.Equal(condition, content)
s.Nil(Remove("test.txt"))
}
func (s *SystemHelperTestSuite) TestMkdir() {
dirPath, _ := TempDir("testdir")
s.Nil(Mkdir(dirPath, 0755))
s.Nil(Remove(dirPath))
}
func (s *SystemHelperTestSuite) TestChmod() {
filePath, _ := TempFile("testfile")
err := Write(filePath.Name(), "test data", 0644)
s.Nil(err)
s.Nil(filePath.Close())
s.Nil(Chmod(filePath.Name(), 0755))
s.Nil(Remove(filePath.Name()))
}
func (s *SystemHelperTestSuite) TestChown() {
filePath, _ := TempFile("testfile")
err := Write(filePath.Name(), "test data", 0644)
s.Nil(err)
currentUser, err := user.Current()
s.Nil(err)
groups, err := currentUser.GroupIds()
s.Nil(err)
err = Chown(filePath.Name(), currentUser.Username, groups[0])
if env.IsWindows() {
s.NotNil(err)
} else {
s.Nil(err)
}
s.Nil(filePath.Close())
s.Nil(Remove(filePath.Name()))
}
func (s *SystemHelperTestSuite) TestExists() {
filePath, _ := TempFile("testfile")
s.True(Exists(filePath.Name()))
s.False(Exists("123"))
s.Nil(filePath.Close())
s.Nil(Remove(filePath.Name()))
}
func (s *SystemHelperTestSuite) TestEmpty() {
filePath, _ := TempFile("testfile")
s.True(Empty(filePath.Name()))
if env.IsWindows() {
s.True(Empty("C:\\Windows\\System32\\drivers\\etc\\hosts"))
} else {
s.True(Empty("/etc/hosts"))
}
s.Nil(filePath.Close())
s.Nil(Remove(filePath.Name()))
}
func (s *SystemHelperTestSuite) TestMv() {
filePath, _ := TempFile("testfile")
err := Write(filePath.Name(), "test data", 0644)
s.Nil(err)
newFilePath, _ := TempFile("testfile2")
s.Nil(newFilePath.Close())
s.Nil(filePath.Close())
s.Nil(Mv(filePath.Name(), newFilePath.Name()))
s.False(Exists(filePath.Name()))
s.Nil(Remove(newFilePath.Name()))
s.Nil(Remove(filePath.Name()))
}
func (s *SystemHelperTestSuite) TestCp() {
tempDir, _ := TempDir("testdir")
err := Write(filepath.Join(tempDir, "testfile"), "test data", 0644)
s.Nil(err)
s.Nil(Cp(filepath.Join(tempDir, "testfile"), filepath.Join(tempDir, "testfile2")))
s.True(Exists(filepath.Join(tempDir, "testfile2")))
s.Nil(Remove(tempDir))
}
func (s *SystemHelperTestSuite) TestSize() {
filePath, _ := TempFile("testfile")
err := Write(filePath.Name(), "test data", 0644)
s.Nil(err)
size, err := Size(filePath.Name())
s.Nil(err)
s.Equal(int64(len("test data")), size)
s.Nil(filePath.Close())
s.Nil(Remove(filePath.Name()))
}
func (s *SystemHelperTestSuite) TestFileInfo() {
filePath, _ := TempFile("testfile")
err := Write(filePath.Name(), "test data", 0644)
s.Nil(err)
info, err := FileInfo(filePath.Name())
s.Nil(err)
s.Equal(filepath.Base(filePath.Name()), info.Name())
s.Nil(filePath.Close())
s.Nil(Remove(filePath.Name()))
}
func (s *SystemHelperTestSuite) TestUnArchiveSuccessfullyUnarchivesFile() {
file, _ := TempFile("test")
dstDir, _ := TempDir("archive")
err := Write(file.Name(), "test data", 0644)
s.Nil(err)
err = Archive([]string{file.Name()}, filepath.Join(dstDir, "test.zip"))
s.Nil(err)
s.FileExists(filepath.Join(dstDir, "test.zip"))
err = UnArchive(filepath.Join(dstDir, "test.zip"), dstDir)
s.Nil(err)
s.FileExists(filepath.Join(dstDir, filepath.Base(file.Name())))
s.Nil(file.Close())
s.Nil(Remove(file.Name()))
s.Nil(Remove(dstDir))
}
func (s *SystemHelperTestSuite) TestUnArchiveFailsForNonExistentFile() {
srcFile := "nonexistent.zip"
dstDir, _ := TempDir("unarchived")
err := UnArchive(srcFile, dstDir)
s.NotNil(err)
s.Nil(Remove(dstDir))
}
func (s *SystemHelperTestSuite) TestArchiveSuccessfullyArchivesFiles() {
srcFile, _ := TempFile("test")
dstDir, _ := TempDir("archive")
err := Write(srcFile.Name(), "test data", 0644)
s.Nil(err)
err = Archive([]string{srcFile.Name()}, filepath.Join(dstDir, "test.zip"))
s.Nil(err)
s.FileExists(filepath.Join(dstDir, "test.zip"))
s.Nil(srcFile.Close())
s.Nil(Remove(srcFile.Name()))
s.Nil(Remove(dstDir))
}
func (s *SystemHelperTestSuite) TestArchiveFailsForNonExistentFiles() {
srcFile := "nonexistent"
dstDir, _ := TempDir("archive")
err := Archive([]string{srcFile}, filepath.Join(dstDir, "test.zip"))
s.NotNil(err)
s.Nil(Remove(dstDir))
}
+79 -73
View File
@@ -4,7 +4,6 @@ package tools
import (
"errors"
"fmt"
"github.com/spf13/cast"
"os"
"strings"
"time"
@@ -19,6 +18,10 @@ import (
"github.com/shirou/gopsutil/load"
"github.com/shirou/gopsutil/mem"
"github.com/shirou/gopsutil/net"
"github.com/spf13/cast"
"github.com/TheTNB/panel/pkg/io"
"github.com/TheTNB/panel/pkg/shell"
)
// MonitoringInfo 监控信息
@@ -48,8 +51,8 @@ func GetMonitoringInfo() MonitoringInfo {
res.Disk, _ = disk.Partitions(true)
ioCounters, _ := disk.IOCounters()
for _, io := range ioCounters {
res.DiskIO = append(res.DiskIO, io)
for _, info := range ioCounters {
res.DiskIO = append(res.DiskIO, info)
}
for _, partition := range res.Disk {
@@ -80,9 +83,12 @@ func VersionCompare(ver1, ver2, operator string) bool {
}
for i := 0; i < len(v1s); i++ {
if v1s[i] > v2s[i] {
v1i := cast.ToInt(v1s[i])
v2i := cast.ToInt(v2s[i])
if v1i > v2i {
return operator == ">" || operator == ">=" || operator == "!="
} else if v1s[i] < v2s[i] {
} else if v1i < v2i {
return operator == "<" || operator == "<=" || operator == "!="
}
}
@@ -156,9 +162,9 @@ func GetLatestPanelVersion() (PanelInfo, error) {
isChina := IsChina()
if isChina {
output, err = Exec(`curl -sSL "https://git.haozi.net/api/v4/projects/opensource%2Fpanel/releases/permalink/latest"`)
output, err = shell.Execf(`curl -sSL "https://git.haozi.net/api/v4/projects/opensource%%2Fpanel/releases/permalink/latest"`)
} else {
output, err = Exec(`curl -sSL "https://api.github.com/repos/TheTNB/panel/releases/latest"`)
output, err = shell.Execf(`curl -sSL "https://api.github.com/repos/TheTNB/panel/releases/latest"`)
}
if len(output) == 0 || err != nil {
@@ -182,70 +188,70 @@ func GetLatestPanelVersion() (PanelInfo, error) {
var name, version, body, date, downloadName, downloadUrl, checksums, checksumsUrl string
if isChina {
if name, err = Exec("jq -r '.name' " + fileName); err != nil {
if name, err = shell.Execf("jq -r '.name' " + fileName); err != nil {
return info, errors.New("获取最新版本失败")
}
if version, err = Exec("jq -r '.tag_name' " + fileName); err != nil {
if version, err = shell.Execf("jq -r '.tag_name' " + fileName); err != nil {
return info, errors.New("获取最新版本失败")
}
if body, err = Exec("jq -r '.description' " + fileName); err != nil {
if body, err = shell.Execf("jq -r '.description' " + fileName); err != nil {
return info, errors.New("获取最新版本失败")
}
if date, err = Exec("jq -r '.created_at' " + fileName); err != nil {
if date, err = shell.Execf("jq -r '.created_at' " + fileName); err != nil {
return info, errors.New("获取最新版本失败")
}
if checksums, err = Exec("jq -r '.assets.links[] | select(.name | contains(\"checksums\")) | .name' " + fileName); err != nil {
if checksums, err = shell.Execf("jq -r '.assets.links[] | select(.name | contains(\"checksums\")) | .name' " + fileName); err != nil {
return info, errors.New("获取最新版本失败")
}
if checksumsUrl, err = Exec("jq -r '.assets.links[] | select(.name | contains(\"checksums\")) | .direct_asset_url' " + fileName); err != nil {
if checksumsUrl, err = shell.Execf("jq -r '.assets.links[] | select(.name | contains(\"checksums\")) | .direct_asset_url' " + fileName); err != nil {
return info, errors.New("获取最新版本失败")
}
if env.IsArm() {
if downloadName, err = Exec("jq -r '.assets.links[] | select(.name | contains(\"arm64\")) | .name' " + fileName); err != nil {
if downloadName, err = shell.Execf("jq -r '.assets.links[] | select(.name | contains(\"arm64\")) | .name' " + fileName); err != nil {
return info, errors.New("获取最新版本失败")
}
if downloadUrl, err = Exec("jq -r '.assets.links[] | select(.name | contains(\"arm64\")) | .direct_asset_url' " + fileName); err != nil {
if downloadUrl, err = shell.Execf("jq -r '.assets.links[] | select(.name | contains(\"arm64\")) | .direct_asset_url' " + fileName); err != nil {
return info, errors.New("获取最新版本失败")
}
} else {
if downloadName, err = Exec("jq -r '.assets.links[] | select(.name | contains(\"amd64v2\")) | .name' " + fileName); err != nil {
if downloadName, err = shell.Execf("jq -r '.assets.links[] | select(.name | contains(\"amd64v2\")) | .name' " + fileName); err != nil {
return info, errors.New("获取最新版本失败")
}
if downloadUrl, err = Exec("jq -r '.assets.links[] | select(.name | contains(\"amd64v2\")) | .direct_asset_url' " + fileName); err != nil {
if downloadUrl, err = shell.Execf("jq -r '.assets.links[] | select(.name | contains(\"amd64v2\")) | .direct_asset_url' " + fileName); err != nil {
return info, errors.New("获取最新版本失败")
}
}
} else {
if name, err = Exec("jq -r '.name' " + fileName); err != nil {
if name, err = shell.Execf("jq -r '.name' " + fileName); err != nil {
return info, errors.New("获取最新版本失败")
}
if version, err = Exec("jq -r '.tag_name' " + fileName); err != nil {
if version, err = shell.Execf("jq -r '.tag_name' " + fileName); err != nil {
return info, errors.New("获取最新版本失败")
}
if body, err = Exec("jq -r '.body' " + fileName); err != nil {
if body, err = shell.Execf("jq -r '.body' " + fileName); err != nil {
return info, errors.New("获取最新版本失败")
}
if date, err = Exec("jq -r '.published_at' " + fileName); err != nil {
if date, err = shell.Execf("jq -r '.published_at' " + fileName); err != nil {
return info, errors.New("获取最新版本失败")
}
if checksums, err = Exec("jq -r '.assets[] | select(.name | contains(\"checksums\")) | .name' " + fileName); err != nil {
if checksums, err = shell.Execf("jq -r '.assets[] | select(.name | contains(\"checksums\")) | .name' " + fileName); err != nil {
return info, errors.New("获取最新版本失败")
}
if checksumsUrl, err = Exec("jq -r '.assets[] | select(.name | contains(\"checksums\")) | .browser_download_url' " + fileName); err != nil {
if checksumsUrl, err = shell.Execf("jq -r '.assets[] | select(.name | contains(\"checksums\")) | .browser_download_url' " + fileName); err != nil {
return info, errors.New("获取最新版本失败")
}
if env.IsArm() {
if downloadName, err = Exec("jq -r '.assets[] | select(.name | contains(\"arm64\")) | .name' " + fileName); err != nil {
if downloadName, err = shell.Execf("jq -r '.assets[] | select(.name | contains(\"arm64\")) | .name' " + fileName); err != nil {
return info, errors.New("获取最新版本失败")
}
if downloadUrl, err = Exec("jq -r '.assets[] | select(.name | contains(\"arm64\")) | .browser_download_url' " + fileName); err != nil {
if downloadUrl, err = shell.Execf("jq -r '.assets[] | select(.name | contains(\"arm64\")) | .browser_download_url' " + fileName); err != nil {
return info, errors.New("获取最新版本失败")
}
} else {
if downloadName, err = Exec("jq -r '.assets[] | select(.name | contains(\"amd64v2\")) | .name' " + fileName); err != nil {
if downloadName, err = shell.Execf("jq -r '.assets[] | select(.name | contains(\"amd64v2\")) | .name' " + fileName); err != nil {
return info, errors.New("获取最新版本失败")
}
if downloadUrl, err = Exec("jq -r '.assets[] | select(.name | contains(\"amd64v2\")) | .browser_download_url' " + fileName); err != nil {
if downloadUrl, err = shell.Execf("jq -r '.assets[] | select(.name | contains(\"amd64v2\")) | .browser_download_url' " + fileName); err != nil {
return info, errors.New("获取最新版本失败")
}
}
@@ -275,9 +281,9 @@ func GetPanelVersion(version string) (PanelInfo, error) {
}
if isChina {
output, err = Exec(`curl -sSL "https://git.haozi.net/api/v4/projects/opensource%2Fpanel/releases/` + version + `"`)
output, err = shell.Execf(`curl -sSL "https://git.haozi.net/api/v4/projects/opensource%%2Fpanel/releases/` + version + `"`)
} else {
output, err = Exec(`curl -sSL "https://api.github.com/repos/TheTNB/panel/releases/tags/` + version + `"`)
output, err = shell.Execf(`curl -sSL "https://api.github.com/repos/TheTNB/panel/releases/tags/` + version + `"`)
}
if len(output) == 0 || err != nil {
@@ -301,70 +307,70 @@ func GetPanelVersion(version string) (PanelInfo, error) {
var name, version2, body, date, downloadName, downloadUrl, checksums, checksumsUrl string
if isChina {
if name, err = Exec("jq -r '.name' " + fileName); err != nil {
if name, err = shell.Execf("jq -r '.name' " + fileName); err != nil {
return info, errors.New("获取面板版本失败")
}
if version2, err = Exec("jq -r '.tag_name' " + fileName); err != nil {
if version2, err = shell.Execf("jq -r '.tag_name' " + fileName); err != nil {
return info, errors.New("获取面板版本失败")
}
if body, err = Exec("jq -r '.description' " + fileName); err != nil {
if body, err = shell.Execf("jq -r '.description' " + fileName); err != nil {
return info, errors.New("获取面板版本失败")
}
if date, err = Exec("jq -r '.created_at' " + fileName); err != nil {
if date, err = shell.Execf("jq -r '.created_at' " + fileName); err != nil {
return info, errors.New("获取面板版本失败")
}
if checksums, err = Exec("jq -r '.assets.links[] | select(.name | contains(\"checksums\")) | .name' " + fileName); err != nil {
if checksums, err = shell.Execf("jq -r '.assets.links[] | select(.name | contains(\"checksums\")) | .name' " + fileName); err != nil {
return info, errors.New("获取面板版本失败")
}
if checksumsUrl, err = Exec("jq -r '.assets.links[] | select(.name | contains(\"checksums\")) | .direct_asset_url' " + fileName); err != nil {
if checksumsUrl, err = shell.Execf("jq -r '.assets.links[] | select(.name | contains(\"checksums\")) | .direct_asset_url' " + fileName); err != nil {
return info, errors.New("获取面板版本失败")
}
if env.IsArm() {
if downloadName, err = Exec("jq -r '.assets.links[] | select(.name | contains(\"arm64\")) | .name' " + fileName); err != nil {
if downloadName, err = shell.Execf("jq -r '.assets.links[] | select(.name | contains(\"arm64\")) | .name' " + fileName); err != nil {
return info, errors.New("获取面板版本失败")
}
if downloadUrl, err = Exec("jq -r '.assets.links[] | select(.name | contains(\"arm64\")) | .direct_asset_url' " + fileName); err != nil {
if downloadUrl, err = shell.Execf("jq -r '.assets.links[] | select(.name | contains(\"arm64\")) | .direct_asset_url' " + fileName); err != nil {
return info, errors.New("获取面板版本失败")
}
} else {
if downloadName, err = Exec("jq -r '.assets.links[] | select(.name | contains(\"amd64v2\")) | .name' " + fileName); err != nil {
if downloadName, err = shell.Execf("jq -r '.assets.links[] | select(.name | contains(\"amd64v2\")) | .name' " + fileName); err != nil {
return info, errors.New("获取面板版本失败")
}
if downloadUrl, err = Exec("jq -r '.assets.links[] | select(.name | contains(\"amd64v2\")) | .direct_asset_url' " + fileName); err != nil {
if downloadUrl, err = shell.Execf("jq -r '.assets.links[] | select(.name | contains(\"amd64v2\")) | .direct_asset_url' " + fileName); err != nil {
return info, errors.New("获取面板版本失败")
}
}
} else {
if name, err = Exec("jq -r '.name' " + fileName); err != nil {
if name, err = shell.Execf("jq -r '.name' " + fileName); err != nil {
return info, errors.New("获取面板版本失败")
}
if version2, err = Exec("jq -r '.tag_name' " + fileName); err != nil {
if version2, err = shell.Execf("jq -r '.tag_name' " + fileName); err != nil {
return info, errors.New("获取面板版本失败")
}
if body, err = Exec("jq -r '.body' " + fileName); err != nil {
if body, err = shell.Execf("jq -r '.body' " + fileName); err != nil {
return info, errors.New("获取面板版本失败")
}
if date, err = Exec("jq -r '.published_at' " + fileName); err != nil {
if date, err = shell.Execf("jq -r '.published_at' " + fileName); err != nil {
return info, errors.New("获取面板版本失败")
}
if checksums, err = Exec("jq -r '.assets[] | select(.name | contains(\"checksums\")) | .name' " + fileName); err != nil {
if checksums, err = shell.Execf("jq -r '.assets[] | select(.name | contains(\"checksums\")) | .name' " + fileName); err != nil {
return info, errors.New("获取面板版本失败")
}
if checksumsUrl, err = Exec("jq -r '.assets[] | select(.name | contains(\"checksums\")) | .browser_download_url' " + fileName); err != nil {
if checksumsUrl, err = shell.Execf("jq -r '.assets[] | select(.name | contains(\"checksums\")) | .browser_download_url' " + fileName); err != nil {
return info, errors.New("获取面板版本失败")
}
if env.IsArm() {
if downloadName, err = Exec("jq -r '.assets[] | select(.name | contains(\"arm64\")) | .name' " + fileName); err != nil {
if downloadName, err = shell.Execf("jq -r '.assets[] | select(.name | contains(\"arm64\")) | .name' " + fileName); err != nil {
return info, errors.New("获取面板版本失败")
}
if downloadUrl, err = Exec("jq -r '.assets[] | select(.name | contains(\"arm64\")) | .browser_download_url' " + fileName); err != nil {
if downloadUrl, err = shell.Execf("jq -r '.assets[] | select(.name | contains(\"arm64\")) | .browser_download_url' " + fileName); err != nil {
return info, errors.New("获取面板版本失败")
}
} else {
if downloadName, err = Exec("jq -r '.assets[] | select(.name | contains(\"amd64v2\")) | .name' " + fileName); err != nil {
if downloadName, err = shell.Execf("jq -r '.assets[] | select(.name | contains(\"amd64v2\")) | .name' " + fileName); err != nil {
return info, errors.New("获取面板版本失败")
}
if downloadUrl, err = Exec("jq -r '.assets[] | select(.name | contains(\"amd64v2\")) | .browser_download_url' " + fileName); err != nil {
if downloadUrl, err = shell.Execf("jq -r '.assets[] | select(.name | contains(\"amd64v2\")) | .browser_download_url' " + fileName); err != nil {
return info, errors.New("获取面板版本失败")
}
}
@@ -388,112 +394,112 @@ func UpdatePanel(panelInfo PanelInfo) error {
color.Green().Printfln("下载链接: " + panelInfo.DownloadUrl)
color.Green().Printfln("前置检查...")
if Exists("/tmp/panel-storage.zip") || Exists("/tmp/panel.conf.bak") {
if io.Exists("/tmp/panel-storage.zip") || io.Exists("/tmp/panel.conf.bak") {
return errors.New("检测到 /tmp 存在临时文件,可能是上次更新失败导致的,请谨慎排除后重试")
}
color.Green().Printfln("备份面板数据...")
// 备份面板
if err := Archive([]string{"/www/panel"}, "/www/backup/panel/panel-"+carbon.Now().ToShortDateTimeString()+".zip"); err != nil {
if err := io.Archive([]string{"/www/panel"}, "/www/backup/panel/panel-"+carbon.Now().ToShortDateTimeString()+".zip"); err != nil {
color.Red().Printfln("备份面板失败")
return err
}
if _, err := Exec("cd /www/panel/storage && zip -r /tmp/panel-storage.zip *"); err != nil {
if _, err := shell.Execf("cd /www/panel/storage && zip -r /tmp/panel-storage.zip *"); err != nil {
color.Red().Printfln("备份面板数据失败")
return err
}
if _, err := Exec("cp -f /www/panel/panel.conf /tmp/panel.conf.bak"); err != nil {
if _, err := shell.Execf("cp -f /www/panel/panel.conf /tmp/panel.conf.bak"); err != nil {
color.Red().Printfln("备份面板配置失败")
return err
}
if !Exists("/tmp/panel-storage.zip") || !Exists("/tmp/panel.conf.bak") {
if !io.Exists("/tmp/panel-storage.zip") || !io.Exists("/tmp/panel.conf.bak") {
return errors.New("备份面板数据失败")
}
color.Green().Printfln("备份完成")
color.Green().Printfln("清理旧版本...")
if _, err := Exec("rm -rf /www/panel/*"); err != nil {
if _, err := shell.Execf("rm -rf /www/panel/*"); err != nil {
color.Red().Printfln("清理旧版本失败")
return err
}
color.Green().Printfln("清理完成")
color.Green().Printfln("正在下载...")
if _, err := Exec("wget -T 120 -t 3 -O /www/panel/" + panelInfo.DownloadName + " " + panelInfo.DownloadUrl); err != nil {
if _, err := shell.Execf("wget -T 120 -t 3 -O /www/panel/" + panelInfo.DownloadName + " " + panelInfo.DownloadUrl); err != nil {
color.Red().Printfln("下载失败")
return err
}
if _, err := Exec("wget -T 20 -t 3 -O /www/panel/" + panelInfo.Checksums + " " + panelInfo.ChecksumsUrl); err != nil {
if _, err := shell.Execf("wget -T 20 -t 3 -O /www/panel/" + panelInfo.Checksums + " " + panelInfo.ChecksumsUrl); err != nil {
color.Red().Printfln("下载失败")
return err
}
if !Exists("/www/panel/"+panelInfo.DownloadName) || !Exists("/www/panel/"+panelInfo.Checksums) {
if !io.Exists("/www/panel/"+panelInfo.DownloadName) || !io.Exists("/www/panel/"+panelInfo.Checksums) {
return errors.New("下载失败")
}
color.Green().Printfln("下载完成")
color.Green().Printfln("校验下载文件...")
check, err := Exec("cd /www/panel && sha256sum -c " + panelInfo.Checksums + " --ignore-missing")
check, err := shell.Execf("cd /www/panel && sha256sum -c " + panelInfo.Checksums + " --ignore-missing")
if check != panelInfo.DownloadName+": OK" || err != nil {
return errors.New("下载文件校验失败")
}
if err = Remove("/www/panel/" + panelInfo.Checksums); err != nil {
if err = io.Remove("/www/panel/" + panelInfo.Checksums); err != nil {
color.Red().Printfln("清理临时文件失败")
return err
}
color.Green().Printfln("文件校验完成")
color.Green().Printfln("更新新版本...")
if _, err = Exec("cd /www/panel && unzip -o " + panelInfo.DownloadName + " && rm -rf " + panelInfo.DownloadName); err != nil {
if _, err = shell.Execf("cd /www/panel && unzip -o " + panelInfo.DownloadName + " && rm -rf " + panelInfo.DownloadName); err != nil {
color.Red().Printfln("更新失败")
return err
}
if !Exists("/www/panel/panel") {
if !io.Exists("/www/panel/panel") {
return errors.New("更新失败,可能是下载过程中出现了问题")
}
color.Green().Printfln("更新完成")
color.Green().Printfln("恢复面板数据...")
if _, err = Exec("cp -f /tmp/panel-storage.zip /www/panel/storage/panel-storage.zip && cd /www/panel/storage && unzip -o panel-storage.zip && rm -rf panel-storage.zip"); err != nil {
if _, err = shell.Execf("cp -f /tmp/panel-storage.zip /www/panel/storage/panel-storage.zip && cd /www/panel/storage && unzip -o panel-storage.zip && rm -rf panel-storage.zip"); err != nil {
color.Red().Printfln("恢复面板数据失败")
return err
}
if _, err = Exec("cp -f /tmp/panel.conf.bak /www/panel/panel.conf"); err != nil {
if _, err = shell.Execf("cp -f /tmp/panel.conf.bak /www/panel/panel.conf"); err != nil {
color.Red().Printfln("恢复面板配置失败")
return err
}
if _, err = Exec("cp -f /www/panel/scripts/panel.sh /usr/bin/panel"); err != nil {
if _, err = shell.Execf("cp -f /www/panel/scripts/panel.sh /usr/bin/panel"); err != nil {
color.Red().Printfln("恢复面板脚本失败")
return err
}
if !Exists("/www/panel/storage/panel.db") || !Exists("/www/panel/panel.conf") {
if !io.Exists("/www/panel/storage/panel.db") || !io.Exists("/www/panel/panel.conf") {
return errors.New("恢复面板数据失败")
}
color.Green().Printfln("恢复完成")
color.Green().Printfln("设置面板文件权限...")
_, _ = Exec("chmod -R 700 /www/panel")
_, _ = Exec("chmod -R 700 /usr/bin/panel")
_, _ = shell.Execf("chmod -R 700 /www/panel")
_, _ = shell.Execf("chmod -R 700 /usr/bin/panel")
color.Green().Printfln("设置完成")
if _, err = Exec("bash /www/panel/scripts/update_panel.sh"); err != nil {
if _, err = shell.Execf("bash /www/panel/scripts/update_panel.sh"); err != nil {
color.Red().Printfln("执行面板升级后脚本失败")
return err
}
if _, err = Exec("panel writeSetting version " + panelInfo.Version); err != nil {
if _, err = shell.Execf("panel writeSetting version " + panelInfo.Version); err != nil {
color.Red().Printfln("写入面板版本号失败")
return err
}
_, _ = Exec("rm -rf /tmp/panel-storage.zip")
_, _ = Exec("rm -rf /tmp/panel.conf.bak")
_, _ = shell.Execf("rm -rf /tmp/panel-storage.zip")
_, _ = shell.Execf("rm -rf /tmp/panel.conf.bak")
return nil
}
func RestartPanel() {
color.Green().Printfln("重启面板...")
err := ExecAsync("sleep 2 && systemctl restart panel")
err := shell.ExecfAsync("sleep 2 && systemctl restart panel")
if err != nil {
color.Red().Printfln("重启失败")
return
+3 -7
View File
@@ -3,7 +3,6 @@ package tools
import (
"testing"
"github.com/goravel/framework/support/env"
"github.com/stretchr/testify/suite"
)
@@ -44,6 +43,9 @@ func (s *HelperTestSuite) TestVersionCompare() {
s.True(VersionCompare("v1.0.0", "1.0.0", "=="))
s.True(VersionCompare("1.0.0", "v1.0.0", "=="))
s.True(VersionCompare("v1.0.0", "v1.0.0", "=="))
s.False(VersionCompare("v2.2.9", "v2.2.10", ">="))
s.True(VersionCompare("v2.2.10", "v2.2.9", ">="))
}
func (s *HelperTestSuite) TestGenerateVersions() {
@@ -77,18 +79,12 @@ func (s *HelperTestSuite) TestGenerateVersions() {
}
func (s *HelperTestSuite) TestGetLatestPanelVersion() {
if env.IsWindows() {
return
}
version, err := GetLatestPanelVersion()
s.NotEmpty(version)
s.Nil(err)
}
func (s *HelperTestSuite) TestGetPanelVersion() {
if env.IsWindows() {
return
}
version, err := GetPanelVersion("v2.1.29")
s.NotEmpty(version)
s.Nil(err)
+11
View File
@@ -0,0 +1,11 @@
package types
type MySQLUser struct {
User string `json:"user"`
Host string `json:"host"`
Grants []string `json:"grants"`
}
type MySQLDatabase struct {
Name string `json:"name"`
}
View File

Some files were not shown because too many files have changed in this diff Show More