mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-10-12 08:26:50 +08:00
* feat(systemctl): 实现服务管理器初始化和命令执行 - 新增 systemctl 包,实现对 systemd、openrc 和 sysvinit 三种服务管理器的支持 - 添加服务状态检查、启动、停止、重启和启用/禁用功能 - 实现服务发现和智能服务名处理 - 添加配置文件查看功能 - 优化错误处理和日志记录 * refactor(system): 重构系统服务管理逻辑 - 引入 systemctl 工具包以统一处理系统服务 - 优化服务状态获取、配置文件路径解析等逻辑 - 重构 HostToolService 中的 GetToolStatus 方法 - 更新 DockerService、SettingService 等相关服务的处理方式 - 调整快照创建和恢复过程中的服务处理逻辑 * feat(utils): 添加目录复制功能并优化文件复制逻辑 - 新增 CopyDirs 函数,用于复制整个目录及其内容 - 添加对符号链接的复制支持 - 实现通用的 Copy 函数,根据文件类型自动选择 CopyFile 或 CopyDirs - 在 CopyFile 函数中增加对源文件是目录的检查和错误提示 * refactortoolbox: 重构 Fail2ban 和 Pure-FTPd 的管理逻辑 - 优化了 Fail2ban 和 Pure-FTPd 的启动、停止、重启等操作的实现 - 改进了 Fail2ban 版本信息的获取方法 - 统一了错误处理和日志记录的格式 - 调整了部分导入的包,提高了代码的可维护性 * build: 禁用 CGO 以提高构建性能和兼容性 - 在 Linux 后端构建命令中添加 CGO_ENABLED=0 环境变量 - 此修改可以提高构建速度,并确保生成的二进制文件在没有 C 库依赖的环境中也能运行 * refactor(docker): 重构 Docker 服务的重启和操作逻辑 - 添加 isDockerSnapInstalled 函数来判断 Docker 是否通过 Snap 安装 - 在 OperateDocker 和 restartDocker 函数中增加对 Snap 安装的处理 - 移除未使用的 getDockerRestartCommand 函数 * fix(service): 优化快照恢复后的服务重启逻辑 - 在使用 systemd 管理服务时,增加 daemon-reload 操作以确保服务配置更新 - 重启 1panel 服务,以应用快照恢复的更改 * refactor(server): 支持非 systemd 系统的恢复操作 - 增加 isSystemd 函数判断系统是否为 systemd 类型 - 根据系统类型选择性地恢复服务文件 - 兼容 systemd 和非 systemd 系统的恢复流程 * fix(upgrade): 优化升级过程中的服务重启逻辑 - 移动服务重启逻辑到版本号更新之后,修复因提前重启导致的版本号未更新BUG。 - 在 systemctl 重启之前添加 daemon-reload 命令 --------- Co-authored-by: gcsong023 <gcsong023@users.noreply.github.com>
428 lines
8.7 KiB
Go
428 lines
8.7 KiB
Go
package common
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"fmt"
|
|
"io"
|
|
mathRand "math/rand"
|
|
"net"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"reflect"
|
|
"regexp"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
"unicode"
|
|
|
|
"github.com/1Panel-dev/1Panel/backend/buserr"
|
|
"github.com/gin-gonic/gin"
|
|
|
|
"golang.org/x/net/idna"
|
|
|
|
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
|
)
|
|
|
|
func CompareVersion(version1, version2 string) bool {
|
|
v1s := extractNumbers(version1)
|
|
v2s := extractNumbers(version2)
|
|
|
|
maxLen := max(len(v1s), len(v2s))
|
|
v1s = append(v1s, make([]string, maxLen-len(v1s))...)
|
|
v2s = append(v2s, make([]string, maxLen-len(v2s))...)
|
|
|
|
for i := 0; i < maxLen; i++ {
|
|
v1, err1 := strconv.Atoi(v1s[i])
|
|
v2, err2 := strconv.Atoi(v2s[i])
|
|
if err1 != nil {
|
|
v1 = 0
|
|
}
|
|
if err2 != nil {
|
|
v2 = 0
|
|
}
|
|
if v1 != v2 {
|
|
return v1 > v2
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func ComparePanelVersion(version1, version2 string) bool {
|
|
if version1 == version2 {
|
|
return false
|
|
}
|
|
version1s := SplitStr(version1, ".", "-")
|
|
version2s := SplitStr(version2, ".", "-")
|
|
|
|
if len(version2s) > len(version1s) {
|
|
for i := 0; i < len(version2s)-len(version1s); i++ {
|
|
version1s = append(version1s, "0")
|
|
}
|
|
}
|
|
if len(version1s) > len(version2s) {
|
|
for i := 0; i < len(version1s)-len(version2s); i++ {
|
|
version2s = append(version2s, "0")
|
|
}
|
|
}
|
|
|
|
n := min(len(version1s), len(version2s))
|
|
for i := 0; i < n; i++ {
|
|
if version1s[i] == version2s[i] {
|
|
continue
|
|
} else {
|
|
v1, err1 := strconv.Atoi(version1s[i])
|
|
if err1 != nil {
|
|
return version1s[i] > version2s[i]
|
|
}
|
|
v2, err2 := strconv.Atoi(version2s[i])
|
|
if err2 != nil {
|
|
return version1s[i] > version2s[i]
|
|
}
|
|
return v1 > v2
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func extractNumbers(version string) []string {
|
|
var numbers []string
|
|
start := -1
|
|
for i, r := range version {
|
|
if isDigit(r) {
|
|
if start == -1 {
|
|
start = i
|
|
}
|
|
} else {
|
|
if start != -1 {
|
|
numbers = append(numbers, version[start:i])
|
|
start = -1
|
|
}
|
|
}
|
|
}
|
|
if start != -1 {
|
|
numbers = append(numbers, version[start:])
|
|
}
|
|
return numbers
|
|
}
|
|
|
|
func isDigit(r rune) bool {
|
|
return r >= '0' && r <= '9'
|
|
}
|
|
|
|
func max(x, y int) int {
|
|
if x > y {
|
|
return x
|
|
}
|
|
return y
|
|
}
|
|
|
|
func GetSortedVersions(versions []string) []string {
|
|
sort.Slice(versions, func(i, j int) bool {
|
|
return CompareVersion(versions[i], versions[j])
|
|
})
|
|
return versions
|
|
}
|
|
|
|
func CopyFile(src, dst string) error {
|
|
if fi, err := os.Stat(src); err == nil && fi.IsDir() {
|
|
return fmt.Errorf("src is a directory, use CopyDirs instead")
|
|
}
|
|
source, err := os.Open(src)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer source.Close()
|
|
|
|
if path.Base(src) != path.Base(dst) {
|
|
dst = path.Join(dst, path.Base(src))
|
|
}
|
|
if _, err := os.Stat(path.Dir(dst)); err != nil {
|
|
if os.IsNotExist(err) {
|
|
_ = os.MkdirAll(path.Dir(dst), os.ModePerm)
|
|
}
|
|
}
|
|
target, err := os.OpenFile(dst+"_temp", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer target.Close()
|
|
|
|
if _, err = io.Copy(target, source); err != nil {
|
|
return err
|
|
}
|
|
if err = os.Rename(dst+"_temp", dst); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
func CopyDirs(srcDir, dstDir string) error {
|
|
srcInfo, err := os.Stat(srcDir)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !srcInfo.IsDir() {
|
|
return fmt.Errorf("%s is not a directory", srcDir)
|
|
}
|
|
if err := os.MkdirAll(dstDir, srcInfo.Mode()); err != nil {
|
|
return err
|
|
}
|
|
return filepath.Walk(srcDir, func(path string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
relPath, err := filepath.Rel(srcDir, path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
dstPath := filepath.Join(dstDir, relPath)
|
|
if info.IsDir() {
|
|
return os.MkdirAll(dstPath, info.Mode())
|
|
}
|
|
if info.Mode()&os.ModeSymlink != 0 {
|
|
return copySymlink(path, dstPath)
|
|
}
|
|
return CopyFile(path, dstPath)
|
|
})
|
|
}
|
|
|
|
func copySymlink(src, dst string) error {
|
|
link, err := os.Readlink(src)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return os.Symlink(link, dst)
|
|
}
|
|
func Copy(src, dst string) error {
|
|
fi, err := os.Stat(src)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
switch mode := fi.Mode(); {
|
|
case mode.IsDir():
|
|
return CopyDirs(src, dst)
|
|
case mode.IsRegular():
|
|
return CopyFile(src, dst)
|
|
default:
|
|
return fmt.Errorf("unsupported file type: %s", mode)
|
|
}
|
|
}
|
|
func IsCrossVersion(version1, version2 string) bool {
|
|
version1s := strings.Split(version1, ".")
|
|
version2s := strings.Split(version2, ".")
|
|
v1num, _ := strconv.Atoi(version1s[0])
|
|
v2num, _ := strconv.Atoi(version2s[0])
|
|
return v2num > v1num
|
|
}
|
|
|
|
func GetUuid() string {
|
|
b := make([]byte, 16)
|
|
_, _ = io.ReadFull(rand.Reader, b)
|
|
b[6] = (b[6] & 0x0f) | 0x40
|
|
b[8] = (b[8] & 0x3f) | 0x80
|
|
return fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:])
|
|
}
|
|
|
|
var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890")
|
|
|
|
func RandStr(n int) string {
|
|
b := make([]rune, n)
|
|
for i := range b {
|
|
b[i] = letters[mathRand.Intn(len(letters))]
|
|
}
|
|
return string(b)
|
|
}
|
|
|
|
func RandStrAndNum(n int) string {
|
|
source := mathRand.NewSource(time.Now().UnixNano())
|
|
randGen := mathRand.New(source)
|
|
const charset = "abcdefghijklmnopqrstuvwxyz0123456789"
|
|
b := make([]byte, n)
|
|
for i := range b {
|
|
b[i] = charset[randGen.Intn(len(charset)-1)]
|
|
}
|
|
return (string(b))
|
|
}
|
|
|
|
func ScanPort(port int) bool {
|
|
ln, err := net.Listen("tcp", ":"+strconv.Itoa(port))
|
|
if err != nil {
|
|
return true
|
|
}
|
|
defer ln.Close()
|
|
return false
|
|
}
|
|
|
|
func ScanUDPPort(port int) bool {
|
|
ln, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: port})
|
|
if err != nil {
|
|
return true
|
|
}
|
|
defer ln.Close()
|
|
return false
|
|
}
|
|
|
|
func ScanPortWithProto(port int, proto string) bool {
|
|
if proto == "udp" {
|
|
return ScanUDPPort(port)
|
|
}
|
|
return ScanPort(port)
|
|
}
|
|
|
|
func IsNum(s string) bool {
|
|
_, err := strconv.ParseFloat(s, 64)
|
|
return err == nil
|
|
}
|
|
|
|
func RemoveRepeatElement(a interface{}) (ret []interface{}) {
|
|
va := reflect.ValueOf(a)
|
|
for i := 0; i < va.Len(); i++ {
|
|
if i > 0 && reflect.DeepEqual(va.Index(i-1).Interface(), va.Index(i).Interface()) {
|
|
continue
|
|
}
|
|
ret = append(ret, va.Index(i).Interface())
|
|
}
|
|
return ret
|
|
}
|
|
|
|
func LoadSizeUnit(value float64) string {
|
|
val := int64(value)
|
|
if val%1024 != 0 {
|
|
return fmt.Sprintf("%v", val)
|
|
}
|
|
if val > 1048576 {
|
|
return fmt.Sprintf("%vM", val/1048576)
|
|
}
|
|
if val > 1024 {
|
|
return fmt.Sprintf("%vK", val/1024)
|
|
}
|
|
return fmt.Sprintf("%v", val)
|
|
}
|
|
|
|
func LoadSizeUnit2F(value float64) string {
|
|
if value > 1073741824 {
|
|
return fmt.Sprintf("%.2fG", value/1073741824)
|
|
}
|
|
if value > 1048576 {
|
|
return fmt.Sprintf("%.2fM", value/1048576)
|
|
}
|
|
if value > 1024 {
|
|
return fmt.Sprintf("%.2fK", value/1024)
|
|
}
|
|
return fmt.Sprintf("%.2f", value)
|
|
}
|
|
|
|
func LoadTimeZoneByCmd() string {
|
|
loc := time.Now().Location().String()
|
|
if _, err := time.LoadLocation(loc); err != nil {
|
|
loc = "Asia/Shanghai"
|
|
}
|
|
std, err := cmd.Exec("timedatectl | grep 'Time zone'")
|
|
if err != nil {
|
|
return loc
|
|
}
|
|
fields := strings.Fields(string(std))
|
|
if len(fields) != 5 {
|
|
return loc
|
|
}
|
|
if _, err := time.LoadLocation(fields[2]); err != nil {
|
|
return loc
|
|
}
|
|
return fields[2]
|
|
}
|
|
|
|
func IsValidDomain(domain string) bool {
|
|
pattern := `^([\w\p{Han}\-\*]{1,100}\.){1,10}([\w\p{Han}\-]{1,24}|[\w\p{Han}\-]{1,24}\.[\w\p{Han}\-]{1,24})(:\d{1,5})?$`
|
|
match, err := regexp.MatchString(pattern, domain)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
return match
|
|
}
|
|
|
|
func ContainsChinese(text string) bool {
|
|
for _, char := range text {
|
|
if unicode.Is(unicode.Han, char) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func PunycodeEncode(text string) (string, error) {
|
|
encoder := idna.New()
|
|
ascii, err := encoder.ToASCII(text)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return ascii, nil
|
|
}
|
|
|
|
func SplitStr(str string, spi ...string) []string {
|
|
lists := []string{str}
|
|
var results []string
|
|
for _, s := range spi {
|
|
results = []string{}
|
|
for _, list := range lists {
|
|
results = append(results, strings.Split(list, s)...)
|
|
}
|
|
lists = results
|
|
}
|
|
return results
|
|
}
|
|
|
|
func IsValidIP(ip string) bool {
|
|
return net.ParseIP(ip) != nil
|
|
}
|
|
|
|
const (
|
|
b = uint64(1)
|
|
kb = 1024 * b
|
|
mb = 1024 * kb
|
|
gb = 1024 * mb
|
|
)
|
|
|
|
func FormatBytes(bytes uint64) string {
|
|
switch {
|
|
case bytes < kb:
|
|
return fmt.Sprintf("%dB", bytes)
|
|
case bytes < mb:
|
|
return fmt.Sprintf("%.2fKB", float64(bytes)/float64(kb))
|
|
case bytes < gb:
|
|
return fmt.Sprintf("%.2fMB", float64(bytes)/float64(mb))
|
|
default:
|
|
return fmt.Sprintf("%.2fGB", float64(bytes)/float64(gb))
|
|
}
|
|
}
|
|
|
|
func FormatPercent(percent float64) string {
|
|
return fmt.Sprintf("%.2f%%", percent)
|
|
}
|
|
|
|
func GetLang(c *gin.Context) string {
|
|
lang := c.GetHeader("Accept-Language")
|
|
if lang == "" {
|
|
lang = "en"
|
|
}
|
|
return lang
|
|
}
|
|
|
|
func HandleIPList(content string) ([]string, error) {
|
|
ipList := strings.Split(content, "\n")
|
|
var res []string
|
|
for _, ip := range ipList {
|
|
if ip == "" {
|
|
continue
|
|
}
|
|
if net.ParseIP(ip) != nil {
|
|
res = append(res, ip)
|
|
continue
|
|
}
|
|
if _, _, err := net.ParseCIDR(ip); err != nil {
|
|
return nil, buserr.New("ErrParseIP")
|
|
}
|
|
res = append(res, ip)
|
|
}
|
|
return res, nil
|
|
}
|