Compare commits

...

19 Commits

Author SHA1 Message Date
耗子 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
耗子 d48963ab50 fix: 由于v2.2.8迁移问题导致更新脚本可能未运行 2024-06-19 01:20:48 +08:00
耗子 59bfed66dc chore: 发布v2.2.10 2024-06-19 01:18:38 +08:00
耗子 5c0bc8a1b7 feat: 安装后删除安装脚本和checksum 2024-06-19 01:15:00 +08:00
耗子 ae9e39d0fe feat: 优化分页函数 2024-06-19 00:47:57 +08:00
96 changed files with 1830 additions and 2007 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()
+2 -9
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 系统监控
@@ -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
+31 -27
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
}
+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))
}
+11 -88
View File
@@ -5,19 +5,15 @@ import (
"strconv"
"strings"
"github.com/TheTNB/panel/pkg/tools"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/image"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/api/types/volume"
"github.com/docker/go-connections/nat"
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/support/carbon"
commonrequests "github.com/TheTNB/panel/app/http/requests/common"
requests "github.com/TheTNB/panel/app/http/requests/container"
"github.com/TheTNB/panel/internal/services"
"github.com/TheTNB/panel/pkg/str"
)
type ContainerController struct {
@@ -41,28 +37,12 @@ func NewContainerController() *ContainerController {
// @Success 200 {object} SuccessResponse
// @Router /panel/container/list [get]
func (r *ContainerController) ContainerList(ctx http.Context) http.Response {
var request commonrequests.Paginate
if sanitize := SanitizeRequest(ctx, &request); sanitize != nil {
return sanitize
}
containers, err := r.container.ContainerListAll()
if err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
startIndex := (request.Page - 1) * request.Limit
endIndex := request.Page * request.Limit
if startIndex > len(containers) {
return Success(ctx, http.Json{
"total": 0,
"items": []any{},
})
}
if endIndex > len(containers) {
endIndex = len(containers)
}
paged := containers[startIndex:endIndex]
paged, total := Paginate(ctx, containers)
items := make([]any, 0)
for _, item := range paged {
@@ -85,7 +65,7 @@ func (r *ContainerController) ContainerList(ctx http.Context) http.Response {
}
return Success(ctx, http.Json{
"total": len(containers),
"total": total,
"items": items,
})
}
@@ -516,31 +496,12 @@ func (r *ContainerController) ContainerPrune(ctx http.Context) http.Response {
// @Success 200 {object} SuccessResponse
// @Router /panel/container/network/list [get]
func (r *ContainerController) NetworkList(ctx http.Context) http.Response {
var request commonrequests.Paginate
if sanitize := SanitizeRequest(ctx, &request); sanitize != nil {
return sanitize
}
networks, err := r.container.NetworkList()
if err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
startIndex := (request.Page - 1) * request.Limit
endIndex := request.Page * request.Limit
if startIndex > len(networks) {
return Success(ctx, http.Json{
"total": 0,
"items": []any{},
})
}
if endIndex > len(networks) {
endIndex = len(networks)
}
paged := networks[startIndex:endIndex]
if paged == nil {
paged = []types.NetworkResource{}
}
paged, total := Paginate(ctx, networks)
items := make([]any, 0)
for _, item := range paged {
@@ -574,7 +535,7 @@ func (r *ContainerController) NetworkList(ctx http.Context) http.Response {
}
return Success(ctx, http.Json{
"total": len(networks),
"total": total,
"items": items,
})
}
@@ -751,31 +712,12 @@ func (r *ContainerController) NetworkPrune(ctx http.Context) http.Response {
// @Success 200 {object} SuccessResponse
// @Router /panel/container/image/list [get]
func (r *ContainerController) ImageList(ctx http.Context) http.Response {
var request commonrequests.Paginate
if sanitize := SanitizeRequest(ctx, &request); sanitize != nil {
return sanitize
}
images, err := r.container.ImageList()
if err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
startIndex := (request.Page - 1) * request.Limit
endIndex := request.Page * request.Limit
if startIndex > len(images) {
return Success(ctx, http.Json{
"total": 0,
"items": []any{},
})
}
if endIndex > len(images) {
endIndex = len(images)
}
paged := images[startIndex:endIndex]
if paged == nil {
paged = []image.Summary{}
}
paged, total := Paginate(ctx, images)
items := make([]any, 0)
for _, item := range paged {
@@ -783,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,
@@ -791,7 +733,7 @@ func (r *ContainerController) ImageList(ctx http.Context) http.Response {
}
return Success(ctx, http.Json{
"total": len(images),
"total": total,
"items": items,
})
}
@@ -919,31 +861,12 @@ func (r *ContainerController) ImageInspect(ctx http.Context) http.Response {
// @Success 200 {object} SuccessResponse
// @Router /panel/container/volume/list [get]
func (r *ContainerController) VolumeList(ctx http.Context) http.Response {
var request commonrequests.Paginate
if sanitize := SanitizeRequest(ctx, &request); sanitize != nil {
return sanitize
}
volumes, err := r.container.VolumeList()
if err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
startIndex := (request.Page - 1) * request.Limit
endIndex := request.Page * request.Limit
if startIndex > len(volumes) {
return Success(ctx, http.Json{
"total": 0,
"items": []any{},
})
}
if endIndex > len(volumes) {
endIndex = len(volumes)
}
paged := volumes[startIndex:endIndex]
if paged == nil {
paged = []*volume.Volume{}
}
paged, total := Paginate(ctx, volumes)
items := make([]any, 0)
for _, item := range paged {
@@ -951,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{
@@ -968,7 +891,7 @@ func (r *ContainerController) VolumeList(ctx http.Context) http.Response {
}
return Success(ctx, http.Json{
"total": len(volumes),
"total": total,
"items": items,
})
}
+28
View File
@@ -3,6 +3,8 @@ package controllers
import (
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/facades"
commonrequests "github.com/TheTNB/panel/app/http/requests/common"
)
// SuccessResponse 通用成功响应
@@ -38,6 +40,32 @@ func ErrorSystem(ctx http.Context) http.Response {
})
}
func Paginate[T any](ctx http.Context, allItems []T) (pagedItems []T, total int) {
var paginateRequest commonrequests.Paginate
sanitize := SanitizeRequest(ctx, &paginateRequest)
if sanitize != nil {
return []T{}, 0
}
page := ctx.Request().QueryInt("page", 1)
limit := ctx.Request().QueryInt("limit", 10)
total = len(allItems)
startIndex := (page - 1) * limit
endIndex := page * limit
if total == 0 {
return []T{}, 0
}
if startIndex > total {
return []T{}, total
}
if endIndex > total {
endIndex = total
}
return allItems[startIndex:endIndex], total
}
// SanitizeRequest 消毒请求参数
func SanitizeRequest(ctx http.Context, request http.FormRequest) http.Response {
errors, err := ctx.Request().ValidateRequest(request)
+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())
}
+40 -56
View File
@@ -2,18 +2,20 @@ package controllers
import (
"fmt"
"io"
"os"
stdio "io"
stdos "os"
"path/filepath"
"strings"
"syscall"
commonrequests "github.com/TheTNB/panel/app/http/requests/common"
"github.com/goravel/framework/contracts/http"
"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 {
@@ -43,11 +45,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())
}
}
@@ -74,7 +76,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())
}
@@ -85,7 +87,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())
}
@@ -111,11 +113,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())
}
@@ -141,7 +143,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())
}
@@ -173,16 +175,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())
}
@@ -208,11 +210,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())
}
@@ -238,11 +240,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())
}
@@ -268,7 +270,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())
}
@@ -319,14 +321,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(),
@@ -352,10 +354,10 @@ func (r *FileController) Permission(ctx http.Context) http.Response {
return sanitize
}
if err := tools.Chmod(request.Path, os.FileMode(request.Mode)); err != nil {
if err := io.Chmod(request.Path, stdos.FileMode(request.Mode)); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
if err := tools.Chown(request.Path, request.Owner, request.Group); err != nil {
if err := io.Chown(request.Path, request.Owner, request.Group); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -380,7 +382,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())
}
@@ -406,7 +408,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())
}
@@ -431,8 +433,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
}
@@ -466,13 +468,7 @@ func (r *FileController) List(ctx http.Context) http.Response {
return sanitize
}
var paginate commonrequests.Paginate
paginateSanitize := SanitizeRequest(ctx, &paginate)
if paginateSanitize != nil {
return paginateSanitize
}
fileInfoList, err := os.ReadDir(request.Path)
fileInfoList, err := io.ReadDir(request.Path)
if err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -485,43 +481,31 @@ 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(),
})
}
start := paginate.Limit * (paginate.Page - 1)
end := paginate.Limit * paginate.Page
if start > len(paths) {
start = len(paths)
}
if end > len(paths) {
end = len(paths)
}
paged := paths[start:end]
if paged == nil {
paged = []any{}
}
paged, total := Paginate(ctx, paths)
return Success(ctx, http.Json{
"total": len(paths),
"total": total,
"items": paged,
})
}
// setPermission
func (r *FileController) setPermission(path string, mode uint, owner, group string) {
_ = tools.Chmod(path, os.FileMode(mode))
_ = tools.Chown(path, owner, group)
_ = io.Chmod(path, stdos.FileMode(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))
+3 -16
View File
@@ -75,24 +75,11 @@ func (r *PluginController) List(ctx http.Context) http.Response {
})
}
page := ctx.Request().QueryInt("page", 1)
limit := ctx.Request().QueryInt("limit", 10)
startIndex := (page - 1) * limit
endIndex := page * limit
if startIndex > len(pluginArr) {
return Success(ctx, http.Json{
"total": 0,
"items": []plugin{},
})
}
if endIndex > len(pluginArr) {
endIndex = len(pluginArr)
}
pagedPlugins := pluginArr[startIndex:endIndex]
paged, total := Paginate(ctx, pluginArr)
return Success(ctx, http.Json{
"total": len(pluginArr),
"items": pagedPlugins,
"total": total,
"items": paged,
})
}
@@ -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,9 +31,7 @@ func NewFail2banController() *Fail2banController {
// List 所有 Fail2ban 规则
func (r *Fail2banController) List(ctx http.Context) http.Response {
page := ctx.Request().QueryInt("page", 1)
limit := ctx.Request().QueryInt("limit", 10)
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())
}
@@ -47,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
}
@@ -67,25 +68,11 @@ func (r *Fail2banController) List(ctx http.Context) http.Response {
})
}
startIndex := (page - 1) * limit
endIndex := page * limit
if startIndex > len(jails) {
return controllers.Success(ctx, http.Json{
"total": 0,
"items": []types.Fail2banJail{},
})
}
if endIndex > len(jails) {
endIndex = len(jails)
}
pagedJails := jails[startIndex:endIndex]
if pagedJails == nil {
pagedJails = []types.Fail2banJail{}
}
paged, total := controllers.Paginate(ctx, jails)
return controllers.Success(ctx, http.Json{
"total": len(jails),
"items": pagedJails,
"total": total,
"items": paged,
})
}
@@ -113,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())
}
@@ -152,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规则失败")
}
@@ -170,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规则失败")
}
@@ -181,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, "未知服务")
}
@@ -217,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, "重载配置失败")
}
@@ -232,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())
}
@@ -240,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, "重载配置失败")
}
@@ -261,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, "获取封禁列表失败")
}
@@ -303,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, "解封失败")
}
@@ -317,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())
}
@@ -329,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)
@@ -341,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())
}
+57 -112
View File
@@ -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/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("/www/server/mysql/bin/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,7 +175,7 @@ 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")
status, err := systemctl.Status("mysqld")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取MySQL状态失败")
}
@@ -187,21 +190,16 @@ func (r *MySQLController) SetRootPassword(ctx http.Context) http.Response {
oldRootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
if oldRootPassword != rootPassword {
if _, err := tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + oldRootPassword + " -e \"ALTER USER 'root'@'localhost' IDENTIFIED BY '" + rootPassword + "';\""); err != nil {
if _, err = shell.Execf(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))
}
if _, err = shell.Execf(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 := tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + oldRootPassword + " -e \"FLUSH PRIVILEGES;\""); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "设置root密码失败")
}
err := r.setting.Set(models.SettingKeyMysqlRootPassword, rootPassword)
if err != nil {
if _, err := tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"ALTER USER 'root'@'localhost' IDENTIFIED BY '" + oldRootPassword + "';\""); err != nil {
return nil
}
if _, err := tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"FLUSH PRIVILEGES;\""); err != nil {
return nil
}
return controllers.Error(ctx, http.StatusInternalServerError, "设置root密码失败")
if err = r.setting.Set(models.SettingKeyMysqlRootPassword, rootPassword); err != nil {
_, _ = shell.Execf(fmt.Sprintf(`/www/server/mysql/bin/mysql -uroot -p%s -e "ALTER USER 'root'@'localhost' IDENTIFIED BY '%s';"`, rootPassword, oldRootPassword))
_, _ = shell.Execf(fmt.Sprintf(`/www/server/mysql/bin/mysql -uroot -p%s -e "FLUSH PRIVILEGES;"`, oldRootPassword))
return controllers.Error(ctx, http.StatusInternalServerError, fmt.Sprintf("设置保存失败: %v", err))
}
}
@@ -240,40 +238,22 @@ func (r *MySQLController) DatabaseList(ctx http.Context) http.Response {
var databases []database
for rows.Next() {
var d database
err := rows.Scan(&d.Name)
if err != nil {
if err = rows.Scan(&d.Name); err != nil {
continue
}
databases = append(databases, d)
}
if err := rows.Err(); err != nil {
if err = rows.Err(); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取数据库列表失败")
}
page := ctx.Request().QueryInt("page", 1)
limit := ctx.Request().QueryInt("limit", 10)
startIndex := (page - 1) * limit
endIndex := page * limit
if startIndex > len(databases) {
return controllers.Success(ctx, http.Json{
"total": 0,
"items": []database{},
})
}
if endIndex > len(databases) {
endIndex = len(databases)
}
pagedDatabases := databases[startIndex:endIndex]
if pagedDatabases == nil {
pagedDatabases = []database{}
}
paged, total := controllers.Paginate(ctx, databases)
return controllers.Success(ctx, http.Json{
"total": len(databases),
"items": pagedDatabases,
"total": total,
"items": paged,
})
}
@@ -292,16 +272,16 @@ 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 {
if out, err := shell.Execf("/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)
}
if out, err := tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"CREATE USER '" + user + "'@'localhost' IDENTIFIED BY '" + password + "';\""); err != nil {
if out, err := shell.Execf("/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 out, err := tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"GRANT ALL PRIVILEGES ON " + database + ".* TO '" + user + "'@'localhost';\""); err != nil {
if out, err := shell.Execf("/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 {
if out, err := shell.Execf("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"FLUSH PRIVILEGES;\""); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
@@ -318,7 +298,7 @@ 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 {
if out, err := shell.Execf("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"DROP DATABASE IF EXISTS " + database + ";\""); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
@@ -327,32 +307,16 @@ func (r *MySQLController) DeleteDatabase(ctx http.Context) http.Response {
// BackupList 获取备份列表
func (r *MySQLController) BackupList(ctx http.Context) http.Response {
backupList, err := r.backup.MysqlList()
backups, err := r.backup.MysqlList()
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
page := ctx.Request().QueryInt("page", 1)
limit := ctx.Request().QueryInt("limit", 10)
startIndex := (page - 1) * limit
endIndex := page * limit
if startIndex > len(backupList) {
return controllers.Success(ctx, http.Json{
"total": 0,
"items": []types.BackupFile{},
})
}
if endIndex > len(backupList) {
endIndex = len(backupList)
}
pagedBackupList := backupList[startIndex:endIndex]
if pagedBackupList == nil {
pagedBackupList = []types.BackupFile{}
}
paged, total := controllers.Paginate(ctx, backups)
return controllers.Success(ctx, http.Json{
"total": len(backupList),
"items": pagedBackupList,
"total": total,
"items": paged,
})
}
@@ -364,8 +328,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
}
}
@@ -405,7 +369,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())
}
@@ -463,8 +427,7 @@ func (r *MySQLController) UserList(ctx http.Context) http.Response {
for rows.Next() {
var u user
err := rows.Scan(&u.User, &u.Host)
if err != nil {
if err = rows.Scan(&u.User, &u.Host); err != nil {
continue
}
@@ -477,47 +440,29 @@ func (r *MySQLController) UserList(ctx http.Context) http.Response {
for grantsRows.Next() {
var grant string
err := grantsRows.Scan(&grant)
if err != nil {
if err = grantsRows.Scan(&grant); err != nil {
continue
}
u.Grants = append(u.Grants, grant)
}
if err := grantsRows.Err(); err != nil {
if err = grantsRows.Err(); err != nil {
continue
}
userGrants = append(userGrants, u)
}
if err := rows.Err(); err != nil {
if err = rows.Err(); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取用户列表失败")
}
page := ctx.Request().QueryInt("page", 1)
limit := ctx.Request().QueryInt("limit", 10)
startIndex := (page - 1) * limit
endIndex := page * limit
if startIndex > len(userGrants) {
return controllers.Success(ctx, http.Json{
"total": 0,
"items": []user{},
})
}
if endIndex > len(userGrants) {
endIndex = len(userGrants)
}
pagedUserGrants := userGrants[startIndex:endIndex]
if pagedUserGrants == nil {
pagedUserGrants = []user{}
}
paged, total := controllers.Paginate(ctx, userGrants)
return controllers.Success(ctx, http.Json{
"total": len(userGrants),
"items": pagedUserGrants,
"total": total,
"items": paged,
})
}
@@ -535,13 +480,13 @@ 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 {
if out, err := shell.Execf("/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 out, err := tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"GRANT ALL PRIVILEGES ON " + database + ".* TO '" + user + "'@'localhost';\""); err != nil {
if out, err := shell.Execf("/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 {
if out, err := shell.Execf("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"FLUSH PRIVILEGES;\""); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
@@ -558,7 +503,7 @@ 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 {
if out, err := shell.Execf("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"DROP USER '" + user + "'@'localhost';\""); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
@@ -577,10 +522,10 @@ 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 {
if out, err := shell.Execf("/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)
}
if out, err := tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"FLUSH PRIVILEGES;\""); err != nil {
if out, err := shell.Execf("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"FLUSH PRIVILEGES;\""); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
@@ -599,13 +544,13 @@ 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 {
if out, err := shell.Execf("/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)
}
if out, err := tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"GRANT ALL PRIVILEGES ON " + database + ".* TO '" + user + "'@'localhost';\""); err != nil {
if out, err := shell.Execf("/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 {
if out, err := shell.Execf("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"FLUSH PRIVILEGES;\""); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
@@ -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)
}
@@ -189,28 +191,11 @@ func (r *PostgreSQLController) DatabaseList(ctx http.Context) http.Response {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
page := ctx.Request().QueryInt("page", 1)
limit := ctx.Request().QueryInt("limit", 10)
startIndex := (page - 1) * limit
endIndex := page * limit
if startIndex > len(databases) {
return controllers.Success(ctx, http.Json{
"total": 0,
"items": []database{},
})
}
if endIndex > len(databases) {
endIndex = len(databases)
}
pagedDatabases := databases[startIndex:endIndex]
if pagedDatabases == nil {
pagedDatabases = []database{}
}
paged, total := controllers.Paginate(ctx, databases)
return controllers.Success(ctx, http.Json{
"total": len(databases),
"items": pagedDatabases,
"total": total,
"items": paged,
})
}
@@ -228,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, "重载服务失败")
}
@@ -262,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)
}
@@ -271,32 +256,16 @@ func (r *PostgreSQLController) DeleteDatabase(ctx http.Context) http.Response {
// BackupList 获取备份列表
func (r *PostgreSQLController) BackupList(ctx http.Context) http.Response {
backupList, err := r.backup.PostgresqlList()
backups, err := r.backup.PostgresqlList()
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取备份列表失败")
}
page := ctx.Request().QueryInt("page", 1)
limit := ctx.Request().QueryInt("limit", 10)
startIndex := (page - 1) * limit
endIndex := page * limit
if startIndex > len(backupList) {
return controllers.Success(ctx, http.Json{
"total": 0,
"items": []types.BackupFile{},
})
}
if endIndex > len(backupList) {
endIndex = len(backupList)
}
pagedBackupList := backupList[startIndex:endIndex]
if pagedBackupList == nil {
pagedBackupList = []types.BackupFile{}
}
paged, total := controllers.Paginate(ctx, backups)
return controllers.Success(ctx, http.Json{
"total": len(backupList),
"items": pagedBackupList,
"total": total,
"items": paged,
})
}
@@ -308,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
}
}
@@ -349,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())
}
@@ -440,28 +409,11 @@ func (r *PostgreSQLController) RoleList(ctx http.Context) http.Response {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
page := ctx.Request().QueryInt("page", 1)
limit := ctx.Request().QueryInt("limit", 10)
startIndex := (page - 1) * limit
endIndex := page * limit
if startIndex > len(roles) {
return controllers.Success(ctx, http.Json{
"total": 0,
"items": []role{},
})
}
if endIndex > len(roles) {
endIndex = len(roles)
}
pagedRoles := roles[startIndex:endIndex]
if pagedRoles == nil {
pagedRoles = []role{}
}
paged, total := controllers.Paginate(ctx, roles)
return controllers.Success(ctx, http.Json{
"total": len(roles),
"items": pagedRoles,
"total": total,
"items": paged,
})
}
@@ -478,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, "重载服务失败")
}
@@ -506,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, "重载服务失败")
}
@@ -531,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,
@@ -43,28 +46,11 @@ func (r *PureFtpdController) List(ctx http.Context) http.Response {
})
}
page := ctx.Request().QueryInt("page", 1)
limit := ctx.Request().QueryInt("limit", 10)
startIndex := (page - 1) * limit
endIndex := page * limit
if startIndex > len(users) {
return controllers.Success(ctx, http.Json{
"total": 0,
"items": []types.PureFtpdUser{},
})
}
if endIndex > len(users) {
endIndex = len(users)
}
pagedUsers := users[startIndex:endIndex]
if pagedUsers == nil {
pagedUsers = []types.PureFtpdUser{}
}
paged, total := controllers.Paginate(ctx, users)
return controllers.Success(ctx, http.Json{
"total": len(users),
"items": pagedUsers,
"total": total,
"items": paged,
})
}
@@ -85,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)
}
@@ -115,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)
}
@@ -137,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)
}
@@ -149,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端口失败")
}
@@ -166,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负载失败")
}
@@ -7,10 +7,12 @@ import (
"github.com/goravel/framework/contracts/http"
"github.com/TheTNB/panel/app/http/controllers"
commonrequests "github.com/TheTNB/panel/app/http/requests/common"
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 {
@@ -31,13 +33,7 @@ func NewRsyncController() *RsyncController {
// @Success 200 {object} controllers.SuccessResponse
// @Router /plugins/rsync/modules [get]
func (r *RsyncController) List(ctx http.Context) http.Response {
var paginateRequest commonrequests.Paginate
sanitize := controllers.SanitizeRequest(ctx, &paginateRequest)
if sanitize != nil {
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())
}
@@ -76,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+"的密钥失败")
}
@@ -91,25 +87,11 @@ func (r *RsyncController) List(ctx http.Context) http.Response {
modules = append(modules, *currentModule)
}
startIndex := (paginateRequest.Page - 1) * paginateRequest.Limit
endIndex := paginateRequest.Page * paginateRequest.Limit
if startIndex > len(modules) {
return controllers.Success(ctx, http.Json{
"total": 0,
"items": []types.RsyncModule{},
})
}
if endIndex > len(modules) {
endIndex = len(modules)
}
pagedModules := modules[startIndex:endIndex]
if pagedModules == nil {
pagedModules = []types.RsyncModule{}
}
paged, total := controllers.Paginate(ctx, modules)
return controllers.Success(ctx, http.Json{
"total": len(modules),
"items": pagedModules,
"total": total,
"items": paged,
})
}
@@ -130,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())
}
@@ -149,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())
}
@@ -179,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())
}
@@ -187,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())
}
@@ -227,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())
}
@@ -245,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())
}
@@ -280,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())
}
@@ -305,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())
}
+20 -36
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 {
@@ -27,34 +28,17 @@ func NewS3fsController() *S3fsController {
// List 所有 S3fs 挂载
func (r *S3fsController) List(ctx http.Context) http.Response {
page := ctx.Request().QueryInt("page", 1)
limit := ctx.Request().QueryInt("limit", 10)
var s3fsList []types.S3fsMount
err := json.UnmarshalString(r.setting.Get("s3fs", "[]"), &s3fsList)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取 S3fs 挂载失败")
}
startIndex := (page - 1) * limit
endIndex := page * limit
if startIndex > len(s3fsList) {
return controllers.Success(ctx, http.Json{
"total": 0,
"items": []types.S3fsMount{},
})
}
if endIndex > len(s3fsList) {
endIndex = len(s3fsList)
}
pagedS3fsList := s3fsList[startIndex:endIndex]
if pagedS3fsList == nil {
pagedS3fsList = []types.S3fsMount{}
}
paged, total := controllers.Paginate(ctx, s3fsList)
return controllers.Success(ctx, http.Json{
"total": len(s3fsList),
"items": pagedS3fsList,
"total": total,
"items": paged,
})
}
@@ -82,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, "挂载目录必须为空")
}
@@ -104,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, "挂载失败,请检查配置是否正确")
}
@@ -162,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))
}
@@ -92,9 +95,6 @@ func (r *SupervisorController) SaveConfig(ctx http.Context) http.Response {
// Processes 进程列表
func (r *SupervisorController) Processes(ctx http.Context) http.Response {
page := ctx.Request().QueryInt("page", 1)
limit := ctx.Request().QueryInt("limit", 10)
type process struct {
Name string `json:"name"`
Status string `json:"status"`
@@ -102,12 +102,12 @@ 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)
}
var processList []process
var processes []process
for _, line := range strings.Split(out, "\n") {
if len(line) == 0 {
continue
@@ -115,47 +115,33 @@ 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 = "-"
p.Uptime = "-"
}
processList = append(processList, p)
processes = append(processes, p)
}
startIndex := (page - 1) * limit
endIndex := page * limit
if startIndex > len(processList) {
return controllers.Success(ctx, http.Json{
"total": 0,
"items": []process{},
})
}
if endIndex > len(processList) {
endIndex = len(processList)
}
pagedProcessList := processList[startIndex:endIndex]
if pagedProcessList == nil {
pagedProcessList = []process{}
}
paged, total := controllers.Paginate(ctx, processes)
return controllers.Success(ctx, http.Json{
"total": len(processList),
"items": pagedProcessList,
"total": total,
"items": paged,
})
}
// 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)
}
@@ -165,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)
}
@@ -175,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)
}
@@ -187,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)
}
@@ -210,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)
}
@@ -232,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 {
@@ -250,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)
}
@@ -298,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)
}
@@ -318,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())
}
}
@@ -340,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)
}
+63 -82
View File
@@ -4,11 +4,13 @@ import (
"regexp"
"strings"
commonrequests "github.com/TheTNB/panel/app/http/requests/common"
"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 {
@@ -17,7 +19,7 @@ type SafeController struct {
func NewSafeController() *SafeController {
var ssh string
if tools.IsRHEL() {
if os.IsRHEL() {
ssh = "sshd"
} else {
ssh = "ssh"
@@ -37,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")
}
}
}
@@ -77,19 +79,13 @@ func (r *SafeController) SetFirewallStatus(ctx http.Context) http.Response {
// GetFirewallRules 获取防火墙规则
func (r *SafeController) GetFirewallRules(ctx http.Context) http.Response {
var paginateRequest commonrequests.Paginate
sanitize := SanitizeRequest(ctx, &paginateRequest)
if sanitize != nil {
return sanitize
}
if !r.firewallStatus() {
return Success(ctx, nil)
}
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)
}
@@ -110,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)
}
@@ -130,26 +126,11 @@ func (r *SafeController) GetFirewallRules(ctx http.Context) http.Response {
}
}
startIndex := (paginateRequest.Page - 1) * paginateRequest.Limit
endIndex := paginateRequest.Page * paginateRequest.Limit
if startIndex > len(rules) {
return Success(ctx, http.Json{
"total": 0,
"items": []map[string]string{},
})
}
if endIndex > len(rules) {
endIndex = len(rules)
}
pagedRules := rules[startIndex:endIndex]
if pagedRules == nil {
pagedRules = []map[string]string{}
}
paged, total := Paginate(ctx, rules)
return Success(ctx, http.Json{
"total": len(rules),
"items": pagedRules,
"total": total,
"items": paged,
})
}
@@ -179,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 {
@@ -194,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)
}
}
@@ -214,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)
}
}
@@ -242,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
@@ -253,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())
}
@@ -264,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())
}
}
@@ -284,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)
}
@@ -299,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)
@@ -316,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)
}
@@ -328,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())
}
@@ -344,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`)
}
}
@@ -362,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, "日志已被清理")
}
+26 -38
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 {
@@ -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("保存默认停止页配置失败")
@@ -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())
}
@@ -328,7 +330,7 @@ func (r *WebsiteController) BackupList(ctx http.Context) http.Response {
return sanitize
}
backupList, err := r.backup.WebsiteList()
backups, err := r.backup.WebsiteList()
if err != nil {
facades.Log().Request(ctx.Request()).Tags("面板", "网站管理").With(map[string]any{
"error": err.Error(),
@@ -336,25 +338,11 @@ func (r *WebsiteController) BackupList(ctx http.Context) http.Response {
return ErrorSystem(ctx)
}
startIndex := (paginateRequest.Page - 1) * paginateRequest.Limit
endIndex := paginateRequest.Page * paginateRequest.Limit
if startIndex > len(backupList) {
return Success(ctx, http.Json{
"total": 0,
"items": []types.BackupFile{},
})
}
if endIndex > len(backupList) {
endIndex = len(backupList)
}
pagedBackupList := backupList[startIndex:endIndex]
if pagedBackupList == nil {
pagedBackupList = []types.BackupFile{}
}
paged, total := Paginate(ctx, backups)
return Success(ctx, http.Json{
"total": len(backupList),
"items": pagedBackupList,
"total": total,
"items": paged,
})
}
@@ -412,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
}
}
@@ -482,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())
}
@@ -585,13 +573,13 @@ server
}
`, website.Path, website.Php, website.Name, website.Name, website.Name)
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 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 := systemctl.Reload("openresty"); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -625,13 +613,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 {
@@ -643,7 +631,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 {
@@ -654,10 +642,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 检查程序状态
@@ -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 {
+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 -2
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 处理面板任务
@@ -48,7 +48,7 @@ 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{
+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.
+1 -1
View File
@@ -8,7 +8,7 @@ func init() {
config := facades.Config()
config.Add("panel", map[string]any{
"name": "耗子面板",
"version": "v2.2.9",
"version": "v2.2.13",
"ssl": config.Env("APP_SSL", false),
})
}
+74 -5
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"
],
@@ -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": {
+74 -5
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"
],
@@ -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": {
+45 -4
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:
@@ -2384,9 +2391,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 +2445,6 @@ paths:
post:
consumes:
- application/json
description: 更新面板设置
parameters:
- description: request
in: body
+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
}
+17 -15
View File
@@ -12,7 +12,9 @@ import (
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 {
@@ -53,7 +55,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("获取私钥失败")
}
@@ -96,7 +98,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("获取私钥失败")
}
@@ -271,13 +273,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 +313,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
}
}
@@ -396,13 +398,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 +430,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
}
+5 -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 {
@@ -122,7 +122,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)
}
@@ -168,7 +168,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)
}
@@ -214,6 +214,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
+80 -75
View File
@@ -2,8 +2,6 @@
package services
import (
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"regexp"
@@ -16,8 +14,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 {
@@ -55,7 +57,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 +115,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 +176,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
}
@@ -259,45 +261,45 @@ server
}
`, portList, domainList, website.Path, website.Php, 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/ssl/"+website.Name+".pem", "", 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+".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 +317,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 +334,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,7 +349,7 @@ 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标记位")
}
@@ -377,14 +379,14 @@ func (r *WebsiteImpl) SaveConfig(config requests.SaveConfig) error {
port.WriteString(" listen [::]:" + vStr + ";")
}
}
portConfigOld := tools.Cut(raw, "# port标记位开始", "# port标记位结束")
portConfigOld := str.Cut(raw, "# port标记位开始", "# port标记位结束")
if len(strings.TrimSpace(portConfigOld)) == 0 {
return errors.New("配置文件中缺少port标记位")
}
raw = strings.Replace(raw, portConfigOld, "\n"+port.String()+"\n ", -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 +398,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 +415,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 +442,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 +451,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 +491,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 +505,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 +518,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 +543,23 @@ func (r *WebsiteImpl) Delete(id uint) error {
return err
}
if err := tools.Remove("/www/server/vhost/" + website.Name + ".conf"); err != nil {
if err := io.Remove("/www/server/vhost/" + website.Name + ".conf"); err != nil {
return err
}
if err := tools.Remove("/www/server/vhost/rewrite/" + website.Name + ".conf"); err != nil {
if err := io.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 {
if err := io.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 {
if err := io.Remove("/www/server/vhost/ssl/" + website.Name + ".key"); err != nil {
return err
}
if err := tools.Remove(website.Path); err != nil {
if err := io.Remove(website.Path); err != nil {
return err
}
return tools.ServiceReload("openresty")
return systemctl.Reload("openresty")
}
// GetConfig 获取网站配置
@@ -559,7 +569,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 +581,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 {
@@ -590,24 +600,24 @@ func (r *WebsiteImpl) GetConfig(id uint) (types.WebsiteSetting, error) {
setting.Ports = append(setting.Ports, 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(setting.Root + "/.user.ini") {
userIni, _ := io.Read(setting.Root + "/.user.ini")
if strings.Contains(userIni, "open_basedir") {
setting.OpenBasedir = true
} else {
@@ -617,32 +627,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 +662,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
+1 -1
View File
@@ -3,7 +3,7 @@ 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 {
+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 {
+3 -1
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 {
@@ -61,7 +63,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
}
+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
}
+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)
}
+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)
View File
View File
+1 -2
View File
@@ -19,7 +19,6 @@
}
],
"ignoreDeps": [
"github.com/goravel/framework",
"github.com/goravel/gin"
"github.com/goravel/framework"
]
}
+2
View File
@@ -199,6 +199,8 @@ func Api() {
settingController := controllers.NewSettingController()
r.Get("list", settingController.List)
r.Post("update", settingController.Update)
r.Get("https", settingController.GetHttps)
r.Post("https", settingController.UpdateHttps)
})
r.Prefix("system").Middleware(middleware.Jwt()).Group(func(r route.Router) {
controller := controllers.NewSystemController()
+3
View File
@@ -21,6 +21,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
LOGO="+----------------------------------------------------\n| 耗子面板安装脚本\n+----------------------------------------------------\n| Copyright © 2022-"$(date +%Y)" 耗子科技 All rights reserved.\n+----------------------------------------------------"
HR="+----------------------------------------------------"
setup_Path="/www"
current_Path=$(pwd)
sshPort=$(cat /etc/ssh/sshd_config | grep 'Port ' | awk '{print $2}')
inChina=$(curl --retry 2 -m 10 -L https://www.cloudflare-cn.com/cdn-cgi/trace 2> /dev/null | grep -qx 'loc=CN' && echo "true" || echo "false")
@@ -338,6 +339,8 @@ EOF
echo -e $HR
panel init
panel getInfo
cd ${current_Path}
rm -f install_panel.sh
rm -f install_panel.sh.checksum.txt
}
+3 -3
View File
@@ -106,9 +106,9 @@ if version_lt "$oldVersion" "2.2.4"; then
fi
fi
if version_lt "$oldVersion" "2.2.9"; then
echo "更新面板到 v2.2.9 ..."
echo "Update panel to v2.2.9 ..."
if version_lt "$oldVersion" "2.2.10"; then
echo "更新面板到 v2.2.10 ..."
echo "Update panel to v2.2.10 ..."
if [ -f "/usr/bin/podman" ]; then
panel writePlugin podman 4.0.0
if [ "${OS}" == "debian" ]; then
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 118 KiB

After

Width:  |  Height:  |  Size: 97 KiB