mirror of
https://github.com/tnb-labs/panel.git
synced 2026-10-28 03:27:53 +08:00
refactor: packages to pkg
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
package tools
|
||||
|
||||
import (
|
||||
"os"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
// IsDebian 判断是否是 Debian 系统
|
||||
func IsDebian() bool {
|
||||
_, err := os.Stat("/etc/debian_version")
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// IsRHEL 判断是否是 RHEL 系统
|
||||
func IsRHEL() bool {
|
||||
_, err := os.Stat("/etc/redhat-release")
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// IsArm 判断是否是 ARM 架构
|
||||
func IsArm() bool {
|
||||
return runtime.GOARCH == "arm"
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package tools
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
type OSHelperTestSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func TestOSHelperTestSuite(t *testing.T) {
|
||||
suite.Run(t, &OSHelperTestSuite{})
|
||||
}
|
||||
|
||||
func (s *OSHelperTestSuite) TestIsDebian() {
|
||||
s.True(IsDebian())
|
||||
}
|
||||
|
||||
func (s *OSHelperTestSuite) TestIsRHEL() {
|
||||
s.False(IsRHEL())
|
||||
}
|
||||
|
||||
func (s *OSHelperTestSuite) TestIsArm() {
|
||||
s.False(IsArm())
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
package tools
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// Empty 类似于 PHP 的 empty() 函数
|
||||
func Empty(val interface{}) bool {
|
||||
if val == nil {
|
||||
return true
|
||||
}
|
||||
v := reflect.ValueOf(val)
|
||||
switch v.Kind() {
|
||||
case reflect.String, reflect.Array:
|
||||
return v.Len() == 0
|
||||
case reflect.Map, reflect.Slice:
|
||||
return v.Len() == 0 || v.IsNil()
|
||||
case reflect.Bool:
|
||||
return !v.Bool()
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return v.Int() == 0
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
return v.Uint() == 0
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return v.Float() == 0
|
||||
case reflect.Interface, reflect.Ptr:
|
||||
return v.IsNil()
|
||||
}
|
||||
return reflect.DeepEqual(val, reflect.Zero(v.Type()).Interface())
|
||||
}
|
||||
|
||||
// FirstElement 安全地获取 args[0],避免 panic: runtime error: index out of range
|
||||
func FirstElement(args []string) string {
|
||||
if len(args) > 0 {
|
||||
return args[0]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// RandomNumber 生成长度为 length 随机数字字符串
|
||||
func RandomNumber(length int) string {
|
||||
table := [...]byte{'1', '2', '3', '4', '5', '6', '7', '8', '9', '0'}
|
||||
b := make([]byte, length)
|
||||
n, err := io.ReadAtLeast(rand.Reader, b, length)
|
||||
if n != length {
|
||||
panic(err)
|
||||
}
|
||||
for i := 0; i < len(b); i++ {
|
||||
b[i] = table[int(b[i])%len(table)]
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// RandomString 生成长度为 length 的随机字符串
|
||||
func RandomString(length int) string {
|
||||
b := make([]byte, length)
|
||||
_, err := rand.Read(b)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
letters := "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
for i, v := range b {
|
||||
b[i] = letters[v%byte(len(letters))]
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// MD5 生成字符串的 MD5 值
|
||||
func MD5(str string) string {
|
||||
return fmt.Sprintf("%x", md5.Sum([]byte(str)))
|
||||
}
|
||||
|
||||
// FormatBytes 格式化bytes
|
||||
func FormatBytes(size float64) string {
|
||||
units := []string{"B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"}
|
||||
|
||||
i := 0
|
||||
for ; size >= 1024 && i < len(units); i++ {
|
||||
size /= 1024
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%.2f %s", size, units[i])
|
||||
}
|
||||
|
||||
// Cut 裁剪字符串
|
||||
func Cut(str, begin, end string) string {
|
||||
bIndex := strings.Index(str, begin)
|
||||
eIndex := strings.Index(str, end)
|
||||
if bIndex == -1 || eIndex == -1 || bIndex > eIndex {
|
||||
return ""
|
||||
}
|
||||
|
||||
b := utf8.RuneCountInString(str[:bIndex]) + utf8.RuneCountInString(begin)
|
||||
e := utf8.RuneCountInString(str[:eIndex])
|
||||
if b > e {
|
||||
return ""
|
||||
}
|
||||
|
||||
return string([]rune(str)[b:e])
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package tools
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
type StringHelperTestSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func TestStringHelperTestSuite(t *testing.T) {
|
||||
suite.Run(t, &StringHelperTestSuite{})
|
||||
}
|
||||
|
||||
func (s *StringHelperTestSuite) TestEmpty() {
|
||||
s.True(Empty(""))
|
||||
s.True(Empty(nil))
|
||||
s.True(Empty([]string{}))
|
||||
s.True(Empty(map[string]string{}))
|
||||
s.True(Empty(0))
|
||||
s.True(Empty(0.0))
|
||||
s.True(Empty(false))
|
||||
|
||||
s.False(Empty(" "))
|
||||
s.False(Empty([]string{"Panel"}))
|
||||
s.False(Empty(map[string]string{"Panel": "HaoZi"}))
|
||||
s.False(Empty(1))
|
||||
s.False(Empty(1.0))
|
||||
s.False(Empty(true))
|
||||
}
|
||||
|
||||
func (s *StringHelperTestSuite) TestFirstElement() {
|
||||
s.Equal("HaoZi", FirstElement([]string{"HaoZi"}))
|
||||
}
|
||||
|
||||
func (s *StringHelperTestSuite) TestRandomNumber() {
|
||||
s.Len(RandomNumber(10), 10)
|
||||
}
|
||||
|
||||
func (s *StringHelperTestSuite) TestRandomString() {
|
||||
s.Len(RandomString(10), 10)
|
||||
}
|
||||
|
||||
func (s *StringHelperTestSuite) TestMD5() {
|
||||
s.Equal("e10adc3949ba59abbe56e057f20f883e", MD5("123456"))
|
||||
}
|
||||
|
||||
func (s *StringHelperTestSuite) TestFormatBytes() {
|
||||
s.Equal("1.00 B", FormatBytes(1))
|
||||
s.Equal("1.00 KB", FormatBytes(1024))
|
||||
s.Equal("1.00 MB", FormatBytes(1024*1024))
|
||||
s.Equal("1.00 GB", FormatBytes(1024*1024*1024))
|
||||
s.Equal("1.00 TB", FormatBytes(1024*1024*1024*1024))
|
||||
s.Equal("1.00 PB", FormatBytes(1024*1024*1024*1024*1024))
|
||||
s.Equal("1.00 EB", FormatBytes(1024*1024*1024*1024*1024*1024))
|
||||
s.Equal("1.00 ZB", FormatBytes(1024*1024*1024*1024*1024*1024*1024))
|
||||
s.Equal("1.00 YB", FormatBytes(1024*1024*1024*1024*1024*1024*1024*1024))
|
||||
}
|
||||
|
||||
func (s *StringHelperTestSuite) TestCut() {
|
||||
s.Equal("aoZ", Cut("HaoZi", "H", "i"))
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
package tools
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/goravel/framework/facades"
|
||||
)
|
||||
|
||||
// WriteFile 写入文件
|
||||
func WriteFile(path string, data string, permission os.FileMode) bool {
|
||||
if err := os.MkdirAll(filepath.Dir(path), permission); err != nil {
|
||||
facades.Log().Errorf("[面板][Helpers] 创建目录失败: %s", err.Error())
|
||||
return false
|
||||
}
|
||||
|
||||
err := os.WriteFile(path, []byte(data), permission)
|
||||
if err != nil {
|
||||
facades.Log().Errorf("[面板][Helpers] 写入文件 %s 失败: %s", path, err.Error())
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// ReadFile 读取文件
|
||||
func ReadFile(path string) string {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
facades.Log().Errorf("[面板][Helpers] 读取文件 %s 失败: %s", path, err.Error())
|
||||
return ""
|
||||
}
|
||||
|
||||
return string(data)
|
||||
}
|
||||
|
||||
// RemoveFile 删除文件
|
||||
func RemoveFile(path string) bool {
|
||||
if err := os.Remove(path); err != nil {
|
||||
facades.Log().Errorf("[面板][Helpers] 删除文件 %s 失败: %s", path, err.Error())
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// ExecShell 执行 shell 命令
|
||||
func ExecShell(shell string) string {
|
||||
cmd := exec.Command("bash", "-c", shell)
|
||||
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
facades.Log().Errorf("[面板][Helpers] 执行命令 $s 失败: %s", shell, err.Error())
|
||||
return ""
|
||||
}
|
||||
|
||||
return string(output)
|
||||
}
|
||||
|
||||
// ExecShellAsync 异步执行 shell 命令
|
||||
func ExecShellAsync(shell string) {
|
||||
cmd := exec.Command("bash", "-c", shell)
|
||||
|
||||
err := cmd.Start()
|
||||
if err != nil {
|
||||
facades.Log().Errorf("[面板][Helpers] 执行命令 $s 失败: %s", shell, err.Error())
|
||||
}
|
||||
|
||||
go func() {
|
||||
err := cmd.Wait()
|
||||
if err != nil {
|
||||
facades.Log().Errorf("[面板][Helpers] 执行命令 $s 失败: %s", shell, err.Error())
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Mkdir 创建目录
|
||||
func Mkdir(path string, permission os.FileMode) bool {
|
||||
if err := os.MkdirAll(path, permission); err != nil {
|
||||
facades.Log().Errorf("[面板][Helpers] 创建目录 %s 失败: %s", path, err.Error())
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Chmod 修改文件权限
|
||||
func Chmod(path string, permission os.FileMode) bool {
|
||||
if err := os.Chmod(path, permission); err != nil {
|
||||
facades.Log().Errorf("[面板][Helpers] 修改文件 %s 权限失败: %s", path, err.Error())
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Chown 修改路径所有者
|
||||
func Chown(path, user, group string) bool {
|
||||
cmd := exec.Command("chown", "-R", user+":"+group, path)
|
||||
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
facades.Log().Errorf("[面板][Helpers] 修改路径 %s 所有者失败: %s", path, err.Error())
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Exists 判断路径是否存在
|
||||
func Exists(path string) bool {
|
||||
_, err := os.Stat(path)
|
||||
return err == nil
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package tools
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/user"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
type SystemHelperTestSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func TestSystemHelperTestSuite(t *testing.T) {
|
||||
suite.Run(t, &SystemHelperTestSuite{})
|
||||
}
|
||||
|
||||
func (s *SystemHelperTestSuite) TestWriteFile() {
|
||||
filePath := "/tmp/testfile"
|
||||
defer os.Remove(filePath)
|
||||
|
||||
s.True(WriteFile(filePath, "test data", 0644))
|
||||
s.FileExists(filePath)
|
||||
|
||||
content, _ := os.ReadFile(filePath)
|
||||
s.Equal("test data", string(content))
|
||||
}
|
||||
|
||||
func (s *SystemHelperTestSuite) TestReadFile() {
|
||||
filePath := "/tmp/testfile"
|
||||
defer os.Remove(filePath)
|
||||
|
||||
err := os.WriteFile(filePath, []byte("test data"), 0644)
|
||||
s.Nil(err)
|
||||
|
||||
s.Equal("test data", ReadFile(filePath))
|
||||
}
|
||||
|
||||
func (s *SystemHelperTestSuite) TestRemoveFile() {
|
||||
filePath := "/tmp/testfile"
|
||||
|
||||
err := os.WriteFile(filePath, []byte("test data"), 0644)
|
||||
s.Nil(err)
|
||||
|
||||
s.True(RemoveFile(filePath))
|
||||
}
|
||||
|
||||
func (s *SystemHelperTestSuite) TestExecShell() {
|
||||
s.Equal("test\n", ExecShell("echo 'test'"))
|
||||
}
|
||||
|
||||
func (s *SystemHelperTestSuite) TestExecShellAsync() {
|
||||
command := "echo 'test' > /tmp/testfile"
|
||||
defer os.Remove("/tmp/testfile")
|
||||
|
||||
ExecShellAsync(command)
|
||||
|
||||
time.Sleep(time.Second)
|
||||
|
||||
content, _ := os.ReadFile("/tmp/testfile")
|
||||
s.Equal("test\n", string(content))
|
||||
}
|
||||
|
||||
func (s *SystemHelperTestSuite) TestMkdir() {
|
||||
dirPath := "/tmp/testdir"
|
||||
defer os.RemoveAll(dirPath)
|
||||
|
||||
s.True(Mkdir(dirPath, 0755))
|
||||
}
|
||||
|
||||
func (s *SystemHelperTestSuite) TestChmod() {
|
||||
filePath := "/tmp/testfile"
|
||||
defer os.Remove(filePath)
|
||||
|
||||
err := os.WriteFile(filePath, []byte("test data"), 0644)
|
||||
s.Nil(err)
|
||||
|
||||
s.True(Chmod(filePath, 0755))
|
||||
}
|
||||
|
||||
func (s *SystemHelperTestSuite) TestChown() {
|
||||
filePath := "/tmp/testfile"
|
||||
defer os.Remove(filePath)
|
||||
|
||||
err := os.WriteFile(filePath, []byte("test data"), 0644)
|
||||
s.Nil(err)
|
||||
|
||||
currentUser, err := user.Current()
|
||||
s.Nil(err)
|
||||
groups, err := currentUser.GroupIds()
|
||||
s.Nil(err)
|
||||
|
||||
s.True(Chown(filePath, currentUser.Username, groups[0]))
|
||||
}
|
||||
|
||||
func (s *SystemHelperTestSuite) TestExists() {
|
||||
s.True(Exists("/tmp"))
|
||||
s.False(Exists("/tmp/123"))
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
// Package tools 存放辅助方法
|
||||
package tools
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/shirou/gopsutil/cpu"
|
||||
"github.com/shirou/gopsutil/disk"
|
||||
"github.com/shirou/gopsutil/host"
|
||||
"github.com/shirou/gopsutil/load"
|
||||
"github.com/shirou/gopsutil/mem"
|
||||
"github.com/shirou/gopsutil/net"
|
||||
)
|
||||
|
||||
// MonitoringInfo 监控信息
|
||||
type MonitoringInfo struct {
|
||||
Cpus []cpu.InfoStat `json:"cpus"`
|
||||
Percent []float64 `json:"percent"`
|
||||
Load *load.AvgStat `json:"load"`
|
||||
Host *host.InfoStat `json:"host"`
|
||||
Mem *mem.VirtualMemoryStat `json:"mem"`
|
||||
Swap *mem.SwapMemoryStat `json:"swap"`
|
||||
Net []net.IOCountersStat `json:"net"`
|
||||
DiskIO map[string]disk.IOCountersStat `json:"disk_io"`
|
||||
Disk []disk.PartitionStat `json:"disk"`
|
||||
DiskUsage map[string]*disk.UsageStat `json:"disk_usage"`
|
||||
}
|
||||
|
||||
// GetMonitoringInfo 获取监控数据
|
||||
func GetMonitoringInfo() MonitoringInfo {
|
||||
var res MonitoringInfo
|
||||
res.Cpus, _ = cpu.Info()
|
||||
res.Percent, _ = cpu.Percent(time.Second, false)
|
||||
res.Load, _ = load.Avg()
|
||||
res.Host, _ = host.Info()
|
||||
res.Mem, _ = mem.VirtualMemory()
|
||||
res.Swap, _ = mem.SwapMemory()
|
||||
res.Net, _ = net.IOCounters(true)
|
||||
res.DiskIO, _ = disk.IOCounters()
|
||||
res.Disk, _ = disk.Partitions(true)
|
||||
|
||||
res.DiskUsage = make(map[string]*disk.UsageStat)
|
||||
for _, partition := range res.Disk {
|
||||
if strings.HasPrefix(partition.Mountpoint, "/dev") || strings.HasPrefix(partition.Mountpoint, "/sys") || strings.HasPrefix(partition.Mountpoint, "/proc") || strings.HasPrefix(partition.Mountpoint, "/run") || strings.HasPrefix(partition.Mountpoint, "/boot") || strings.HasPrefix(partition.Mountpoint, "/usr") {
|
||||
continue
|
||||
}
|
||||
usage, _ := disk.Usage(partition.Mountpoint)
|
||||
res.DiskUsage[partition.Mountpoint] = usage
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
type PanelInfo struct {
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
DownloadUrl string `json:"download_url"`
|
||||
Body string `json:"body"`
|
||||
Date string `json:"date"`
|
||||
}
|
||||
|
||||
// GetLatestPanelVersion 获取最新版本
|
||||
func GetLatestPanelVersion() (PanelInfo, error) {
|
||||
var info PanelInfo
|
||||
|
||||
cmd := exec.Command("/bin/bash", "-c", "curl \"https://api.github.com/repos/HaoZi-Team/Panel/releases/latest\"")
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return info, errors.New("获取最新版本失败")
|
||||
}
|
||||
|
||||
file, err := os.CreateTemp("", "panel")
|
||||
if err != nil {
|
||||
return info, errors.New("创建临时文件失败")
|
||||
}
|
||||
defer os.Remove(file.Name())
|
||||
_, err = file.Write(output)
|
||||
if err != nil {
|
||||
return info, errors.New("写入临时文件失败")
|
||||
}
|
||||
err = file.Close()
|
||||
if err != nil {
|
||||
return info, errors.New("关闭临时文件失败")
|
||||
}
|
||||
fileName := file.Name()
|
||||
|
||||
name := exec.Command("/bin/bash", "-c", "jq -r '.name' "+fileName)
|
||||
version := exec.Command("/bin/bash", "-c", "jq -r '.tag_name' "+fileName)
|
||||
body := exec.Command("/bin/bash", "-c", "jq -r '.body' "+fileName)
|
||||
date := exec.Command("/bin/bash", "-c", "jq -r '.published_at' "+fileName)
|
||||
nameOutput, _ := name.Output()
|
||||
versionOutput, _ := version.Output()
|
||||
bodyOutput, _ := body.Output()
|
||||
dateOutput, _ := date.Output()
|
||||
info.Name = strings.TrimSpace(string(nameOutput))
|
||||
info.Version = strings.TrimSpace(string(versionOutput))
|
||||
info.Body = strings.TrimSpace(string(bodyOutput))
|
||||
info.Date = strings.TrimSpace(string(dateOutput))
|
||||
if IsArm() {
|
||||
downloadUrl := exec.Command("/bin/bash", "-c", "jq -r '.assets[] | select(.name | contains(\"arm64\")) | .browser_download_url' "+fileName)
|
||||
downloadUrlOutput, _ := downloadUrl.Output()
|
||||
info.DownloadUrl = strings.TrimSpace(string(downloadUrlOutput))
|
||||
} else {
|
||||
downloadUrl := exec.Command("/bin/bash", "-c", "jq -r '.assets[] | select(.name | contains(\"amd64v3\")) | .browser_download_url' "+fileName)
|
||||
downloadUrlOutput, _ := downloadUrl.Output()
|
||||
info.DownloadUrl = strings.TrimSpace(string(downloadUrlOutput))
|
||||
}
|
||||
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// UpdatePanel 更新面板
|
||||
func UpdatePanel() error {
|
||||
panelInfo, err := GetLatestPanelVersion()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd := exec.Command("/bin/bash", "-c", "wget -O panel.tar.gz "+panelInfo.DownloadUrl+" && tar -zxvf panel.tar.gz && rm -rf panel.tar.gz && chmod +x panel && ./panel artisan migrate")
|
||||
_, err = cmd.Output()
|
||||
if err != nil {
|
||||
return errors.New("更新面板失败")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package tools
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
type HelperTestSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func TestHelperTestSuite(t *testing.T) {
|
||||
suite.Run(t, &HelperTestSuite{})
|
||||
}
|
||||
|
||||
func (s *HelperTestSuite) TestGetMonitoringInfo() {
|
||||
s.NotNil(GetMonitoringInfo())
|
||||
}
|
||||
Reference in New Issue
Block a user