mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-01-10 17:15:13 +08:00
feat(systemctl): implement service manager initialization and command execution (#8380)
* 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>
This commit is contained in:
parent
83ef41cf1a
commit
79020abb1c
19 changed files with 2058 additions and 438 deletions
2
Makefile
2
Makefile
|
|
@ -23,7 +23,7 @@ build_frontend:
|
|||
|
||||
build_backend_on_linux:
|
||||
cd $(SERVER_PATH) \
|
||||
&& GOOS=$(GOOS) GOARCH=$(GOARCH) $(GOBUILD) -trimpath -ldflags '-s -w' -o $(BUILD_PATH)/$(APP_NAME) $(MAIN)
|
||||
&& CGO_ENABLED=0 GOOS=$(GOOS) GOARCH=$(GOARCH) $(GOBUILD) -trimpath -ldflags '-s -w' -o $(BUILD_PATH)/$(APP_NAME) $(MAIN)
|
||||
|
||||
build_backend_on_darwin:
|
||||
cd $(SERVER_PATH) \
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
|
@ -28,14 +27,14 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
clamServiceNameCentOs = "clamd@scan.service"
|
||||
clamServiceNameUbuntu = "clamav-daemon.service"
|
||||
freshClamService = "clamav-freshclam.service"
|
||||
resultDir = "clamav"
|
||||
clamServiceKey = "clam"
|
||||
freshClamServiceKey = "freshclam"
|
||||
resultDir = "clamav"
|
||||
)
|
||||
|
||||
type ClamService struct {
|
||||
serviceName string
|
||||
serviceName string
|
||||
freshClamService string
|
||||
}
|
||||
|
||||
type IClamService interface {
|
||||
|
|
@ -63,23 +62,32 @@ func (c *ClamService) LoadBaseInfo() (dto.ClamBaseInfo, error) {
|
|||
var baseInfo dto.ClamBaseInfo
|
||||
baseInfo.Version = "-"
|
||||
baseInfo.FreshVersion = "-"
|
||||
exist1, _ := systemctl.IsExist(clamServiceNameCentOs)
|
||||
if exist1 {
|
||||
c.serviceName = clamServiceNameCentOs
|
||||
baseInfo.IsExist = true
|
||||
baseInfo.IsActive, _ = systemctl.IsActive(clamServiceNameCentOs)
|
||||
clamSvc, err := systemctl.GetServiceName(clamServiceKey)
|
||||
if err != nil {
|
||||
baseInfo.IsExist = false
|
||||
return baseInfo, nil
|
||||
}
|
||||
exist2, _ := systemctl.IsExist(clamServiceNameUbuntu)
|
||||
if exist2 {
|
||||
c.serviceName = clamServiceNameUbuntu
|
||||
baseInfo.IsExist = true
|
||||
baseInfo.IsActive, _ = systemctl.IsActive(clamServiceNameUbuntu)
|
||||
c.serviceName = clamSvc
|
||||
isExist, err := systemctl.IsExist(c.serviceName)
|
||||
if err != nil {
|
||||
baseInfo.IsExist = false
|
||||
}
|
||||
freshExist, _ := systemctl.IsExist(freshClamService)
|
||||
if freshExist {
|
||||
baseInfo.FreshIsExist = true
|
||||
baseInfo.FreshIsActive, _ = systemctl.IsActive(freshClamService)
|
||||
baseInfo.IsExist = isExist
|
||||
baseInfo.IsActive, _ = systemctl.IsActive(clamSvc)
|
||||
|
||||
freshSvc, err := systemctl.GetServiceName(freshClamServiceKey)
|
||||
if err != nil {
|
||||
baseInfo.FreshIsExist = false
|
||||
return baseInfo, nil
|
||||
}
|
||||
c.freshClamService = freshSvc
|
||||
freshisExist, err := systemctl.IsExist(c.freshClamService)
|
||||
if err != nil {
|
||||
baseInfo.FreshIsExist = false
|
||||
}
|
||||
baseInfo.FreshIsExist = freshisExist
|
||||
baseInfo.FreshIsActive, _ = systemctl.IsActive(freshSvc)
|
||||
|
||||
if !cmd.Which("clamdscan") {
|
||||
baseInfo.IsActive = false
|
||||
}
|
||||
|
|
@ -110,22 +118,27 @@ func (c *ClamService) LoadBaseInfo() (dto.ClamBaseInfo, error) {
|
|||
}
|
||||
|
||||
func (c *ClamService) Operate(operate string) error {
|
||||
var err error
|
||||
switch operate {
|
||||
case "start", "restart", "stop":
|
||||
stdout, err := cmd.Execf("systemctl %s %s", operate, c.serviceName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s the %s failed, err: %s", operate, c.serviceName, stdout)
|
||||
}
|
||||
return nil
|
||||
case "fresh-start", "fresh-restart", "fresh-stop":
|
||||
stdout, err := cmd.Execf("systemctl %s %s", strings.TrimPrefix(operate, "fresh-"), freshClamService)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s the %s failed, err: %s", operate, c.serviceName, stdout)
|
||||
}
|
||||
return nil
|
||||
case "start":
|
||||
err = systemctl.Start(c.serviceName)
|
||||
case "stop":
|
||||
err = systemctl.Stop(c.serviceName)
|
||||
case "restart":
|
||||
err = systemctl.Restart(c.serviceName)
|
||||
case "fresh-start":
|
||||
err = systemctl.Start(c.freshClamService)
|
||||
case "fresh-stop":
|
||||
err = systemctl.Stop(c.freshClamService)
|
||||
case "fresh-restart":
|
||||
err = systemctl.Restart(c.freshClamService)
|
||||
default:
|
||||
return fmt.Errorf("not support such operation: %v", operate)
|
||||
return fmt.Errorf("unsupported operation: %s", operate)
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s %s failed: %v", operate, c.serviceName, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ClamService) SearchWithPage(req dto.SearchClamWithPage) (int64, interface{}, error) {
|
||||
|
|
@ -432,102 +445,80 @@ func (c *ClamService) LoadFile(req dto.ClamFileReq) (string, error) {
|
|||
filePath := ""
|
||||
switch req.Name {
|
||||
case "clamd":
|
||||
if c.serviceName == clamServiceNameUbuntu {
|
||||
filePath = "/etc/clamav/clamd.conf"
|
||||
} else {
|
||||
filePath = "/etc/clamd.d/scan.conf"
|
||||
}
|
||||
filePath = c.getConfigPath("clamd")
|
||||
case "clamd-log":
|
||||
filePath = c.loadLogPath("clamd-log")
|
||||
if len(filePath) != 0 {
|
||||
break
|
||||
}
|
||||
if c.serviceName == clamServiceNameUbuntu {
|
||||
filePath = "/var/log/clamav/clamav.log"
|
||||
} else {
|
||||
filePath = "/var/log/clamd.scan"
|
||||
}
|
||||
case "freshclam":
|
||||
if c.serviceName == clamServiceNameUbuntu {
|
||||
filePath = "/etc/clamav/freshclam.conf"
|
||||
} else {
|
||||
filePath = "/etc/freshclam.conf"
|
||||
}
|
||||
filePath = c.getConfigPath("freshclam")
|
||||
case "freshclam-log":
|
||||
filePath = c.loadLogPath("freshclam-log")
|
||||
if len(filePath) != 0 {
|
||||
break
|
||||
}
|
||||
if c.serviceName == clamServiceNameUbuntu {
|
||||
filePath = "/var/log/clamav/freshclam.log"
|
||||
} else {
|
||||
filePath = "/var/log/freshclam.log"
|
||||
}
|
||||
default:
|
||||
return "", fmt.Errorf("not support such type")
|
||||
return "", fmt.Errorf("unsupported file type")
|
||||
}
|
||||
if _, err := os.Stat(filePath); err != nil {
|
||||
|
||||
content, err := systemctl.ViewConfig(filePath, systemctl.ConfigOption{TailLines: req.Tail})
|
||||
if err != nil {
|
||||
return "", buserr.New("ErrHttpReqNotFound")
|
||||
}
|
||||
var tail string
|
||||
if req.Tail != "0" {
|
||||
tail = req.Tail
|
||||
} else {
|
||||
tail = "+1"
|
||||
}
|
||||
cmd := exec.Command("tail", "-n", tail, filePath)
|
||||
stdout, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("tail -n %v failed, err: %v", req.Tail, err)
|
||||
}
|
||||
return string(stdout), nil
|
||||
return content, nil
|
||||
}
|
||||
|
||||
func (c *ClamService) UpdateFile(req dto.UpdateByNameAndFile) error {
|
||||
filePath := ""
|
||||
service := ""
|
||||
var (
|
||||
filePath string
|
||||
service string
|
||||
)
|
||||
|
||||
switch req.Name {
|
||||
case "clamd":
|
||||
if c.serviceName == clamServiceNameUbuntu {
|
||||
service = clamServiceNameUbuntu
|
||||
filePath = "/etc/clamav/clamd.conf"
|
||||
} else {
|
||||
service = clamServiceNameCentOs
|
||||
filePath = "/etc/clamd.d/scan.conf"
|
||||
}
|
||||
filePath = c.getConfigPath("clamd")
|
||||
service = c.serviceName
|
||||
case "freshclam":
|
||||
if c.serviceName == clamServiceNameUbuntu {
|
||||
filePath = "/etc/clamav/freshclam.conf"
|
||||
} else {
|
||||
filePath = "/etc/freshclam.conf"
|
||||
}
|
||||
service = "clamav-freshclam.service"
|
||||
filePath = c.getConfigPath("freshclam")
|
||||
service = c.freshClamService
|
||||
default:
|
||||
return fmt.Errorf("not support such type")
|
||||
return fmt.Errorf("unsupported file type")
|
||||
}
|
||||
|
||||
file, err := os.OpenFile(filePath, os.O_WRONLY|os.O_TRUNC, 0640)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
write := bufio.NewWriter(file)
|
||||
_, _ = write.WriteString(req.File)
|
||||
write.Flush()
|
||||
|
||||
_ = systemctl.Restart(service)
|
||||
if _, err := file.WriteString(req.File); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := systemctl.Restart(service); err != nil {
|
||||
return fmt.Errorf("restart %s failed: %v", service, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ClamService) getConfigPath(confType string) string {
|
||||
switch confType {
|
||||
case "clamd":
|
||||
if _, err := os.Stat("/etc/clamav/clamd.conf"); err == nil {
|
||||
return "/etc/clamav/clamd.conf"
|
||||
}
|
||||
return "/etc/clamd.d/scan.conf"
|
||||
case "freshclam":
|
||||
if _, err := os.Stat("/etc/clamav/freshclam.conf"); err == nil {
|
||||
return "/etc/clamav/freshclam.conf"
|
||||
}
|
||||
return "/etc/freshclam.conf"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func StopAllCronJob(withCheck bool) bool {
|
||||
if withCheck {
|
||||
isActive := false
|
||||
exist1, _ := systemctl.IsExist(clamServiceNameCentOs)
|
||||
if exist1 {
|
||||
isActive, _ = systemctl.IsActive(clamServiceNameCentOs)
|
||||
}
|
||||
exist2, _ := systemctl.IsExist(clamServiceNameUbuntu)
|
||||
if exist2 {
|
||||
isActive, _ = systemctl.IsActive(clamServiceNameUbuntu)
|
||||
isexist, _ := systemctl.IsExist(clamServiceKey)
|
||||
if isexist {
|
||||
isActive, _ = systemctl.IsActive(clamServiceKey)
|
||||
}
|
||||
if isActive {
|
||||
return false
|
||||
|
|
@ -590,42 +581,25 @@ func loadResultFromLog(pathItem string) dto.ClamLog {
|
|||
return data
|
||||
}
|
||||
func (c *ClamService) loadLogPath(name string) string {
|
||||
confPath := ""
|
||||
if name == "clamd-log" {
|
||||
if c.serviceName == clamServiceNameUbuntu {
|
||||
confPath = "/etc/clamav/clamd.conf"
|
||||
} else {
|
||||
confPath = "/etc/clamd.d/scan.conf"
|
||||
}
|
||||
} else {
|
||||
if c.serviceName == clamServiceNameUbuntu {
|
||||
confPath = "/etc/clamav/freshclam.conf"
|
||||
} else {
|
||||
confPath = "/etc/freshclam.conf"
|
||||
}
|
||||
}
|
||||
if _, err := os.Stat(confPath); err != nil {
|
||||
return ""
|
||||
configKey := "clamd"
|
||||
searchPrefix := "LogFile "
|
||||
if name != "clamd-log" {
|
||||
configKey = "freshclam"
|
||||
searchPrefix = "UpdateLogFile "
|
||||
}
|
||||
confPath := c.getConfigPath(configKey)
|
||||
|
||||
content, err := os.ReadFile(confPath)
|
||||
if err != nil {
|
||||
global.LOG.Debugf("Failed to read %s config: %v", configKey, err)
|
||||
return ""
|
||||
}
|
||||
lines := strings.Split(string(content), "\n")
|
||||
if name == "clamd-log" {
|
||||
for _, line := range lines {
|
||||
if strings.HasPrefix(line, "LogFile ") {
|
||||
return strings.Trim(strings.ReplaceAll(line, "LogFile ", ""), " ")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for _, line := range lines {
|
||||
if strings.HasPrefix(line, "UpdateLogFile ") {
|
||||
return strings.Trim(strings.ReplaceAll(line, "UpdateLogFile ", ""), " ")
|
||||
}
|
||||
|
||||
for _, line := range strings.Split(string(content), "\n") {
|
||||
if strings.HasPrefix(line, searchPrefix) {
|
||||
return strings.TrimSpace(strings.TrimPrefix(line, searchPrefix))
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import (
|
|||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/common"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/ntp"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/systemctl"
|
||||
"github.com/shirou/gopsutil/v3/mem"
|
||||
)
|
||||
|
||||
|
|
@ -121,7 +122,7 @@ func (u *DeviceService) Update(key, value string) error {
|
|||
return err
|
||||
}
|
||||
go func() {
|
||||
_, err := cmd.Exec("systemctl restart 1panel.service")
|
||||
err := systemctl.Restart("1panel")
|
||||
if err != nil {
|
||||
global.LOG.Errorf("restart system for new time zone failed, err: %v", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,12 +11,12 @@ import (
|
|||
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/docker"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/systemctl"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/common"
|
||||
fileUtils "github.com/1Panel-dev/1Panel/backend/utils/files"
|
||||
"github.com/google/uuid"
|
||||
|
|
@ -285,7 +285,7 @@ func (u *DeviceService) Clean(req []dto.Clean) {
|
|||
|
||||
if restart {
|
||||
go func() {
|
||||
_, err := cmd.Exec("systemctl restart 1panel.service")
|
||||
err := systemctl.Restart("1panel")
|
||||
if err != nil {
|
||||
global.LOG.Errorf("restart system port failed, err: %v", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -365,17 +365,18 @@ func (u *DockerService) UpdateConfByFile(req dto.DaemonJsonUpdateByFile) error {
|
|||
|
||||
func (u *DockerService) OperateDocker(req dto.DockerOperation) error {
|
||||
service := "docker"
|
||||
sudo := cmd.SudoHandleCmd()
|
||||
dockerCmd, err := getDockerRestartCommand()
|
||||
h, err := systemctl.DefaultHandler(service)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if req.Operation == "stop" {
|
||||
isSocketActive, _ := systemctl.IsActive("docker.socket")
|
||||
if isSocketActive {
|
||||
std, err := cmd.Execf("%s systemctl stop docker.socket", sudo)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("handle systemctl stop docker.socket failed, err: %v", std)
|
||||
socketHandle, err := systemctl.DefaultHandler("docker.socket")
|
||||
if err == nil {
|
||||
status, err := socketHandle.CheckStatus()
|
||||
if err == nil && status.IsActive {
|
||||
if std, err := socketHandle.ExecuteAction("stop"); err != nil {
|
||||
global.LOG.Errorf("handle stop docker.socket failed, err: %v", std)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -386,13 +387,20 @@ func (u *DockerService) OperateDocker(req dto.DockerOperation) error {
|
|||
}
|
||||
}
|
||||
|
||||
stdout, err := cmd.Execf("%s %s %s", dockerCmd, req.Operation, service)
|
||||
if isDockerSnapInstalled() {
|
||||
command := fmt.Sprintf("snap %s docker", req.Operation)
|
||||
stdout, err := cmd.Exec(command)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to restart docker: %v", stdout)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
result, err := h.ExecuteAction(req.Operation)
|
||||
if err != nil {
|
||||
return errors.New(stdout)
|
||||
return errors.New(result.Output)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func changeLogOption(daemonMap map[string]interface{}, logMaxFile, logMaxSize string) {
|
||||
if opts, ok := daemonMap["log-opts"]; ok {
|
||||
if len(logMaxFile) != 0 || len(logMaxSize) != 0 {
|
||||
|
|
@ -451,31 +459,27 @@ func validateDockerConfig() error {
|
|||
return nil
|
||||
}
|
||||
if err != nil || (stdout != "" && strings.TrimSpace(stdout) != "configuration OK") {
|
||||
return fmt.Errorf("Docker configuration validation failed, err: %v", stdout)
|
||||
return fmt.Errorf("docker configuration validation failed, err: %v", stdout)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getDockerRestartCommand() (string, error) {
|
||||
func isDockerSnapInstalled() bool {
|
||||
stdout, err := cmd.Exec("which docker")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to find docker: %v", err)
|
||||
return false
|
||||
}
|
||||
dockerPath := stdout
|
||||
if strings.Contains(dockerPath, "snap") {
|
||||
return "snap", nil
|
||||
}
|
||||
return "systemctl", nil
|
||||
stdout = strings.TrimSpace(stdout)
|
||||
return strings.Contains(stdout, "snap")
|
||||
}
|
||||
|
||||
func restartDocker() error {
|
||||
restartCmd, err := getDockerRestartCommand()
|
||||
if err != nil {
|
||||
return err
|
||||
if isDockerSnapInstalled() {
|
||||
stdout, err := cmd.Exec("snap restart docker")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to restart docker: %v", stdout)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
stdout, err := cmd.Execf("%s restart docker", restartCmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to restart Docker: %s", stdout)
|
||||
}
|
||||
return nil
|
||||
return systemctl.Restart("docker")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,12 @@ package service
|
|||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto/response"
|
||||
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||
|
|
@ -14,11 +20,6 @@ import (
|
|||
"github.com/1Panel-dev/1Panel/backend/utils/systemctl"
|
||||
"github.com/pkg/errors"
|
||||
"gopkg.in/ini.v1"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type HostToolService struct{}
|
||||
|
|
@ -39,93 +40,122 @@ func NewIHostToolService() IHostToolService {
|
|||
}
|
||||
|
||||
func (h *HostToolService) GetToolStatus(req request.HostToolReq) (*response.HostToolRes, error) {
|
||||
res := &response.HostToolRes{}
|
||||
res.Type = req.Type
|
||||
res := &response.HostToolRes{Type: req.Type}
|
||||
switch req.Type {
|
||||
case constant.Supervisord:
|
||||
supervisorConfig := &response.Supervisor{}
|
||||
if !cmd.Which(constant.Supervisord) {
|
||||
supervisorConfig.IsExist = false
|
||||
res.Config = supervisorConfig
|
||||
return res, nil
|
||||
}
|
||||
supervisorConfig.IsExist = true
|
||||
serviceExist, _ := systemctl.IsExist(constant.Supervisord)
|
||||
if !serviceExist {
|
||||
serviceExist, _ = systemctl.IsExist(constant.Supervisor)
|
||||
if !serviceExist {
|
||||
supervisorConfig.IsExist = false
|
||||
res.Config = supervisorConfig
|
||||
return res, nil
|
||||
} else {
|
||||
supervisorConfig.ServiceName = constant.Supervisor
|
||||
}
|
||||
} else {
|
||||
supervisorConfig.ServiceName = constant.Supervisord
|
||||
}
|
||||
|
||||
serviceNameSet, _ := settingRepo.Get(settingRepo.WithByKey(constant.SupervisorServiceName))
|
||||
if serviceNameSet.ID != 0 || serviceNameSet.Value != "" {
|
||||
supervisorConfig.ServiceName = serviceNameSet.Value
|
||||
}
|
||||
|
||||
versionRes, _ := cmd.Exec("supervisord -v")
|
||||
supervisorConfig.Version = strings.TrimSuffix(versionRes, "\n")
|
||||
_, ctlRrr := exec.LookPath("supervisorctl")
|
||||
supervisorConfig.CtlExist = ctlRrr == nil
|
||||
|
||||
active, _ := systemctl.IsActive(supervisorConfig.ServiceName)
|
||||
if active {
|
||||
supervisorConfig.Status = "running"
|
||||
} else {
|
||||
supervisorConfig.Status = "stopped"
|
||||
}
|
||||
|
||||
pathSet, _ := settingRepo.Get(settingRepo.WithByKey(constant.SupervisorConfigPath))
|
||||
if pathSet.ID != 0 || pathSet.Value != "" {
|
||||
supervisorConfig.ConfigPath = pathSet.Value
|
||||
res.Config = supervisorConfig
|
||||
return res, nil
|
||||
} else {
|
||||
supervisorConfig.Init = true
|
||||
}
|
||||
|
||||
servicePath := "/usr/lib/systemd/system/supervisor.service"
|
||||
fileOp := files.NewFileOp()
|
||||
if !fileOp.Stat(servicePath) {
|
||||
servicePath = "/usr/lib/systemd/system/supervisord.service"
|
||||
}
|
||||
if fileOp.Stat(servicePath) {
|
||||
startCmd, _ := ini_conf.GetIniValue(servicePath, "Service", "ExecStart")
|
||||
if startCmd != "" {
|
||||
args := strings.Fields(startCmd)
|
||||
cIndex := -1
|
||||
for i, arg := range args {
|
||||
if arg == "-c" {
|
||||
cIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if cIndex != -1 && cIndex+1 < len(args) {
|
||||
supervisorConfig.ConfigPath = args[cIndex+1]
|
||||
}
|
||||
}
|
||||
}
|
||||
if supervisorConfig.ConfigPath == "" {
|
||||
configPath := "/etc/supervisord.conf"
|
||||
if !fileOp.Stat(configPath) {
|
||||
configPath = "/etc/supervisor/supervisord.conf"
|
||||
if fileOp.Stat(configPath) {
|
||||
supervisorConfig.ConfigPath = configPath
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
res.Config = supervisorConfig
|
||||
return h.getSupervisorStatus(res)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (h *HostToolService) getSupervisorStatus(res *response.HostToolRes) (*response.HostToolRes, error) {
|
||||
supervisorConfig := &response.Supervisor{}
|
||||
|
||||
// 1. 检查supervisord是否安装
|
||||
if !cmd.Which(constant.Supervisord) {
|
||||
supervisorConfig.IsExist = false
|
||||
res.Config = supervisorConfig
|
||||
return res, nil
|
||||
}
|
||||
supervisorConfig.IsExist = true
|
||||
|
||||
// 2. 获取服务名称(兼容不同平台)
|
||||
serviceName, err := h.determineServiceName()
|
||||
if err != nil || serviceName == "" {
|
||||
supervisorConfig.IsExist = false
|
||||
res.Config = supervisorConfig
|
||||
return res, nil
|
||||
}
|
||||
supervisorConfig.ServiceName = serviceName
|
||||
|
||||
// 3. 从数据库获取自定义服务名
|
||||
if nameSetting, _ := settingRepo.Get(settingRepo.WithByKey(constant.SupervisorServiceName)); nameSetting.Value != "" {
|
||||
supervisorConfig.ServiceName = nameSetting.Value
|
||||
}
|
||||
|
||||
// 4. 获取版本信息
|
||||
if version, err := cmd.Exec("supervisord -v"); err == nil {
|
||||
supervisorConfig.Version = strings.TrimSpace(version)
|
||||
}
|
||||
|
||||
// 5. 检查supervisorctl是否存在
|
||||
_, errCtl := exec.LookPath("supervisorctl")
|
||||
supervisorConfig.CtlExist = errCtl == nil
|
||||
|
||||
// 6. 检查服务状态
|
||||
if active, err := systemctl.IsActive(supervisorConfig.ServiceName); err == nil && active {
|
||||
supervisorConfig.Status = "running"
|
||||
} else {
|
||||
supervisorConfig.Status = "stopped"
|
||||
}
|
||||
|
||||
// 7. 获取配置文件路径
|
||||
h.resolveConfigPath(supervisorConfig)
|
||||
|
||||
res.Config = supervisorConfig
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (h *HostToolService) determineServiceName() (string, error) {
|
||||
// 优先级 1: 数据库配置
|
||||
if setting, _ := settingRepo.Get(settingRepo.WithByKey(constant.SupervisorServiceName)); setting.Value != "" {
|
||||
serviceName, err := systemctl.GetServiceName(setting.Value)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("get service name error: %v", err)
|
||||
}
|
||||
return serviceName, nil
|
||||
}
|
||||
// 优先级 2: 自动检测服务名
|
||||
if serviceName, err := systemctl.GetServiceName(constant.Supervisord); err == nil {
|
||||
return serviceName, nil
|
||||
}
|
||||
return systemctl.GetServiceName(constant.Supervisor)
|
||||
}
|
||||
|
||||
func (h *HostToolService) resolveConfigPath(config *response.Supervisor) {
|
||||
// 1. 数据库配置优先
|
||||
if pathSetting, _ := settingRepo.Get(settingRepo.WithByKey(constant.SupervisorConfigPath)); pathSetting.Value != "" {
|
||||
config.ConfigPath = pathSetting.Value
|
||||
return
|
||||
}
|
||||
|
||||
// 2. 尝试获取服务文件路径
|
||||
if servicePath, err := systemctl.GetServicePath(config.ServiceName); err == nil {
|
||||
if startCmd, _ := ini_conf.GetIniValue(servicePath, "Service", "ExecStart"); startCmd != "" {
|
||||
if path := parseConfigPathFromCommand(startCmd); path != "" {
|
||||
config.ConfigPath = path
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 尝试默认路径
|
||||
defaultPaths := []string{
|
||||
"/etc/supervisord.conf",
|
||||
"/etc/supervisor/supervisord.conf",
|
||||
"/usr/local/etc/supervisord.conf",
|
||||
}
|
||||
for _, path := range defaultPaths {
|
||||
if systemctl.FileExist(path) {
|
||||
config.ConfigPath = path
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 标记需要初始化配置
|
||||
config.Init = true
|
||||
}
|
||||
|
||||
func parseConfigPathFromCommand(cmd string) string {
|
||||
parts := strings.Fields(cmd)
|
||||
for i, part := range parts {
|
||||
if part == "-c" && i+1 < len(parts) {
|
||||
return parts[i+1]
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (h *HostToolService) CreateToolConfig(req request.HostToolCreate) error {
|
||||
switch req.Type {
|
||||
case constant.Supervisord:
|
||||
|
|
@ -200,14 +230,28 @@ func (h *HostToolService) CreateToolConfig(req request.HostToolCreate) error {
|
|||
}
|
||||
|
||||
func (h *HostToolService) OperateTool(req request.HostToolReq) error {
|
||||
serviceName := req.Type
|
||||
if req.Type == constant.Supervisord {
|
||||
var serviceName string
|
||||
var err error
|
||||
switch req.Type {
|
||||
case constant.Supervisord:
|
||||
serviceNameSet, _ := settingRepo.Get(settingRepo.WithByKey(constant.SupervisorServiceName))
|
||||
if serviceNameSet.ID != 0 || serviceNameSet.Value != "" {
|
||||
serviceName = serviceNameSet.Value
|
||||
} else {
|
||||
serviceName = req.Type
|
||||
}
|
||||
default:
|
||||
serviceName = req.Type
|
||||
}
|
||||
return systemctl.Operate(req.Operate, serviceName)
|
||||
serviceName, err = systemctl.GetServiceName(serviceName)
|
||||
if err != nil {
|
||||
if errors.Is(err, systemctl.ErrServiceNotFound) {
|
||||
return fmt.Errorf("%s service unavailable. Please ensure supervisor server is installed and configured", serviceName)
|
||||
}
|
||||
return err
|
||||
}
|
||||
_, err = systemctl.CustomAction(req.Operate, serviceName)
|
||||
return err
|
||||
}
|
||||
|
||||
func (h *HostToolService) OperateToolConfig(req request.HostToolConfig) (*response.HostToolConfig, error) {
|
||||
|
|
|
|||
|
|
@ -21,10 +21,10 @@ import (
|
|||
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/common"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/encrypt"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/files"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/systemctl"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/robfig/cron/v3"
|
||||
)
|
||||
|
|
@ -166,7 +166,7 @@ func (u *SettingService) UpdateBindInfo(req dto.BindInfo) error {
|
|||
}
|
||||
go func() {
|
||||
time.Sleep(1 * time.Second)
|
||||
_, err := cmd.Exec("systemctl restart 1panel.service")
|
||||
err := systemctl.Restart("1panel")
|
||||
if err != nil {
|
||||
global.LOG.Errorf("restart system with new bind info failed, err: %v", err)
|
||||
}
|
||||
|
|
@ -214,7 +214,7 @@ func (u *SettingService) UpdatePort(port uint) error {
|
|||
}
|
||||
go func() {
|
||||
time.Sleep(1 * time.Second)
|
||||
_, err := cmd.Exec("systemctl restart 1panel.service")
|
||||
err := systemctl.Restart("1panel")
|
||||
if err != nil {
|
||||
global.LOG.Errorf("restart system port failed, err: %v", err)
|
||||
}
|
||||
|
|
@ -237,7 +237,7 @@ func (u *SettingService) UpdateSSL(c *gin.Context, req dto.SSLUpdate) error {
|
|||
c.SetCookie(constant.SessionName, sID, 0, "", "", false, true)
|
||||
|
||||
go func() {
|
||||
_, err := cmd.Exec("systemctl restart 1panel.service")
|
||||
err := systemctl.Restart("1panel")
|
||||
if err != nil {
|
||||
global.LOG.Errorf("restart system failed, err: %v", err)
|
||||
}
|
||||
|
|
@ -335,7 +335,7 @@ func (u *SettingService) UpdateSSL(c *gin.Context, req dto.SSLUpdate) error {
|
|||
c.SetCookie(constant.SessionName, sID, 0, "", "", true, true)
|
||||
go func() {
|
||||
time.Sleep(1 * time.Second)
|
||||
_, err := cmd.Exec("systemctl restart 1panel.service")
|
||||
err := systemctl.Restart("1panel")
|
||||
if err != nil {
|
||||
global.LOG.Errorf("restart system failed, err: %v", err)
|
||||
}
|
||||
|
|
@ -552,7 +552,7 @@ func (u *SettingService) GenerateRSAKey() error {
|
|||
return err
|
||||
}
|
||||
privateKeyPEM := exportPrivateKeyToPEM(privateKey)
|
||||
publicKeyPEM, err := exportPublicKeyToPEM(&privateKey.PublicKey)
|
||||
publicKeyPEM, _ := exportPublicKeyToPEM(&privateKey.PublicKey)
|
||||
err = settingRepo.UpdateOrCreate("PASSWORD_PRIVATE_KEY", privateKeyPEM)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import (
|
|||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/common"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/files"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/systemctl"
|
||||
)
|
||||
|
||||
type snapHelper struct {
|
||||
|
|
@ -43,16 +44,34 @@ func snapPanel(snap snapHelper, targetDir string) {
|
|||
defer snap.Wg.Done()
|
||||
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"panel": constant.Running})
|
||||
status := constant.StatusDone
|
||||
if err := common.CopyFile("/usr/local/bin/1panel", path.Join(targetDir, "1panel")); err != nil {
|
||||
status = err.Error()
|
||||
h, err := systemctl.DefaultHandler("1panel")
|
||||
|
||||
if err != nil {
|
||||
status = fmt.Sprintf("initialize service handle failed: %v", err)
|
||||
snap.Status.Panel = status
|
||||
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"panel": status})
|
||||
return
|
||||
}
|
||||
|
||||
if err := common.CopyFile("/usr/local/bin/1pctl", targetDir); err != nil {
|
||||
binDir := systemctl.BinaryPath
|
||||
servicePath, err := h.GetServicePath()
|
||||
if err != nil {
|
||||
status = fmt.Sprintf("get service path failed: %v", err)
|
||||
snap.Status.Panel = status
|
||||
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"panel": status})
|
||||
return
|
||||
}
|
||||
|
||||
if err := common.CopyFile(path.Join(binDir, "1panel"), path.Join(targetDir, "1panel")); err != nil {
|
||||
status = err.Error()
|
||||
}
|
||||
_, _ = cmd.Execf("cp -r /usr/local/bin/lang %s", targetDir)
|
||||
|
||||
if err := common.CopyFile("/etc/systemd/system/1panel.service", targetDir); err != nil {
|
||||
if err := common.CopyFile(path.Join(binDir, "1pctl"), targetDir); err != nil {
|
||||
status = err.Error()
|
||||
}
|
||||
if _, err := cmd.Execf("cp -r %s/lang %s", binDir, targetDir); err != nil {
|
||||
status = err.Error()
|
||||
}
|
||||
if err := common.CopyFile(servicePath, targetDir); err != nil {
|
||||
status = err.Error()
|
||||
}
|
||||
snap.Status.Panel = status
|
||||
|
|
@ -260,7 +279,7 @@ func handleSnapTar(sourceDir, targetDir, name, exclusionRules string, secret str
|
|||
commands = fmt.Sprintf("tar --warning=no-file-changed --ignore-failed-read --exclude-from=<(find %s -type s -print) -zcf %s %s %s %s", sourceDir, " -"+exStr, path, extraCmd, targetDir+"/"+name)
|
||||
global.LOG.Debug(strings.ReplaceAll(commands, fmt.Sprintf(" %s ", secret), "******"))
|
||||
} else {
|
||||
commands = fmt.Sprintf("tar --warning=no-file-changed --ignore-failed-read --exclude-from=<(find %s -type s -printf '%s' | sed 's|^|./|') -zcf %s %s -C %s .", sourceDir, "%P\n", targetDir+"/"+name, exStr, sourceDir)
|
||||
commands = fmt.Sprintf("tar --warning=no-file-changed --ignore-failed-read --exclude-from=<(find %s -type s -printf '%%P\n' | sed 's|^|./|') -zcf %s %s -C %s .", sourceDir, targetDir+"/"+name, exStr, sourceDir)
|
||||
global.LOG.Debug(commands)
|
||||
}
|
||||
stdout, err := cmd.ExecWithTimeOut(commands, 30*time.Minute)
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import (
|
|||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
|
|
@ -15,6 +16,7 @@ import (
|
|||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/common"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/files"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/systemctl"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
|
|
@ -91,8 +93,19 @@ func (u *SnapshotService) HandleSnapshotRecover(snap model.Snapshot, isRecover b
|
|||
req.IsNew = true
|
||||
}
|
||||
|
||||
h, err := systemctl.DefaultHandler("1panel")
|
||||
if err != nil {
|
||||
updateRecoverStatus(snap.ID, isRecover, "ServiceHandle", constant.StatusFailed, fmt.Sprintf("initialize service handle failed: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
if req.IsNew || snap.InterruptStep == "1PanelBinary" {
|
||||
if err := recoverPanel(path.Join(snapFileDir, "1panel/1panel"), "/usr/local/bin"); err != nil {
|
||||
binDir := systemctl.BinaryPath
|
||||
// if err != nil {
|
||||
// updateRecoverStatus(snap.ID, isRecover, "GetBinaryPath", constant.StatusFailed, fmt.Sprintf("get binary path failed: %v", err))
|
||||
// return
|
||||
// }
|
||||
if err := recoverPanel(path.Join(snapFileDir, "1panel/1panel"), binDir); err != nil {
|
||||
updateRecoverStatus(snap.ID, isRecover, "1PanelBinary", constant.StatusFailed, err.Error())
|
||||
return
|
||||
}
|
||||
|
|
@ -100,16 +113,34 @@ func (u *SnapshotService) HandleSnapshotRecover(snap model.Snapshot, isRecover b
|
|||
req.IsNew = true
|
||||
}
|
||||
if req.IsNew || snap.InterruptStep == "1PctlBinary" {
|
||||
if err := recoverPanel(path.Join(snapFileDir, "1panel/1pctl"), "/usr/local/bin"); err != nil {
|
||||
binDir := systemctl.BinaryPath
|
||||
// if err != nil {
|
||||
// updateRecoverStatus(snap.ID, isRecover, "GetBinaryPath", constant.StatusFailed, fmt.Sprintf("get binary path failed: %v", err))
|
||||
// return
|
||||
// }
|
||||
if err := recoverPanel(path.Join(snapFileDir, "1panel/1pctl"), binDir); err != nil {
|
||||
updateRecoverStatus(snap.ID, isRecover, "1PctlBinary", constant.StatusFailed, err.Error())
|
||||
return
|
||||
}
|
||||
_, _ = cmd.Execf("cp -r %s %s", path.Join(snapFileDir, "1panel/lang"), "/usr/local/bin/")
|
||||
langDir := path.Join(binDir, "lang")
|
||||
if err := os.RemoveAll(langDir); err != nil {
|
||||
updateRecoverStatus(snap.ID, isRecover, "RemoveLang", constant.StatusFailed, fmt.Sprintf("remove lang dir failed: %v", err))
|
||||
return
|
||||
}
|
||||
if err := common.CopyDirs(path.Join(snapFileDir, "1panel/lang"), langDir); err != nil {
|
||||
updateRecoverStatus(snap.ID, isRecover, "CopyLang", constant.StatusFailed, fmt.Sprintf("copy lang files failed: %v", err))
|
||||
return
|
||||
}
|
||||
global.LOG.Debug("recover 1pctl from snapshot file successful!")
|
||||
req.IsNew = true
|
||||
}
|
||||
if req.IsNew || snap.InterruptStep == "1PanelService" {
|
||||
if err := recoverPanel(path.Join(snapFileDir, "1panel/1panel.service"), "/etc/systemd/system"); err != nil {
|
||||
servicePath, err := h.GetServicePath()
|
||||
if err != nil {
|
||||
updateRecoverStatus(snap.ID, isRecover, "GetServicePath", constant.StatusFailed, fmt.Sprintf("get service path failed: %v", err))
|
||||
return
|
||||
}
|
||||
if err := recoverPanel(path.Join(snapFileDir, "1panel/"+filepath.Base(servicePath)), filepath.Dir(servicePath)); err != nil {
|
||||
updateRecoverStatus(snap.ID, isRecover, "1PanelService", constant.StatusFailed, err.Error())
|
||||
return
|
||||
}
|
||||
|
|
@ -147,7 +178,12 @@ func (u *SnapshotService) HandleSnapshotRecover(snap model.Snapshot, isRecover b
|
|||
global.LOG.Debugf("remove the file %s after the operation is successful", path.Dir(snapFileDir))
|
||||
_ = os.RemoveAll(path.Dir(snapFileDir))
|
||||
}
|
||||
_, _ = cmd.Exec("systemctl daemon-reload && systemctl restart 1panel.service")
|
||||
if h.ManagerName() == "systemd" {
|
||||
_, _ = cmd.Exec("systemctl daemon-reload")
|
||||
}
|
||||
if err := systemctl.Restart("1panel"); err != nil {
|
||||
global.LOG.Errorf("restart 1panel service failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func backupBeforeRecover(snap model.Snapshot) error {
|
||||
|
|
|
|||
|
|
@ -2,8 +2,6 @@ package service
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/geo"
|
||||
"github.com/gin-gonic/gin"
|
||||
"os"
|
||||
"os/user"
|
||||
"path"
|
||||
|
|
@ -12,6 +10,9 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/geo"
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
|
|
@ -59,26 +60,16 @@ func (u *SSHService) GetSSHInfo() (*dto.SSHInfo, error) {
|
|||
if err != nil {
|
||||
data.Status = constant.StatusDisable
|
||||
data.Message = err.Error()
|
||||
} else {
|
||||
active, err := systemctl.IsActive(serviceName)
|
||||
if !active {
|
||||
data.Status = constant.StatusDisable
|
||||
if err != nil {
|
||||
data.Message = err.Error()
|
||||
}
|
||||
} else {
|
||||
data.Status = constant.StatusEnable
|
||||
}
|
||||
}
|
||||
|
||||
out, err := systemctl.RunSystemCtl("is-enabled", serviceName)
|
||||
if err != nil {
|
||||
data.AutoStart = false
|
||||
isEnabled, _ := systemctl.IsEnable(serviceName)
|
||||
data.AutoStart = isEnabled
|
||||
isActive, err := systemctl.IsActive(serviceName)
|
||||
if isActive {
|
||||
data.Status = constant.StatusEnable
|
||||
} else {
|
||||
if out == "alias\n" {
|
||||
data.AutoStart, _ = systemctl.IsEnable("ssh")
|
||||
} else {
|
||||
data.AutoStart = out == "enabled\n"
|
||||
data.Status = constant.StatusDisable
|
||||
if err != nil {
|
||||
data.Message = err.Error()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -121,29 +112,26 @@ func (u *SSHService) OperateSSH(operation string) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sudo := cmd.SudoHandleCmd()
|
||||
if operation == "enable" || operation == "disable" {
|
||||
serviceName += ".service"
|
||||
}
|
||||
if operation == "stop" {
|
||||
switch operation {
|
||||
case "enable":
|
||||
return systemctl.Enable(serviceName)
|
||||
case "disable":
|
||||
return systemctl.Disable(serviceName)
|
||||
case "start":
|
||||
return systemctl.Start(serviceName)
|
||||
case "stop":
|
||||
isSocketActive, _ := systemctl.IsActive(serviceName + ".socket")
|
||||
if isSocketActive {
|
||||
std, err := cmd.Execf("%s systemctl stop %s", sudo, serviceName+".socket")
|
||||
if err != nil {
|
||||
global.LOG.Errorf("handle systemctl stop %s.socket failed, err: %v", serviceName, std)
|
||||
if err := systemctl.Stop(serviceName + ".socket"); err != nil {
|
||||
global.LOG.Errorf("Failed to stop %s.socket: %v", serviceName, err)
|
||||
}
|
||||
}
|
||||
return systemctl.Stop(serviceName)
|
||||
case "restart":
|
||||
return systemctl.Restart(serviceName)
|
||||
}
|
||||
|
||||
stdout, err := cmd.Execf("%s systemctl %s %s", sudo, operation, serviceName)
|
||||
if err != nil {
|
||||
if strings.Contains(stdout, "alias name or linked unit file") {
|
||||
stdout, err := cmd.Execf("%s systemctl %s ssh", sudo, operation)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s ssh(alias name or linked unit file) failed, stdout: %s, err: %v", operation, stdout, err)
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("%s %s failed, stdout: %s, err: %v", operation, serviceName, stdout, err)
|
||||
if result, err := systemctl.CustomAction(operation, serviceName); err != nil {
|
||||
return fmt.Errorf("failed to execute custom action: %v", result.Output)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -198,7 +186,10 @@ func (u *SSHService) Update(req dto.SSHUpdate) error {
|
|||
}
|
||||
}
|
||||
|
||||
_, _ = cmd.Execf("%s systemctl restart %s", sudo, serviceName)
|
||||
err = systemctl.Restart(serviceName)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("handle restart %s failed, err: %v", serviceName, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -216,8 +207,10 @@ func (u *SSHService) UpdateByFile(value string) error {
|
|||
if _, err = file.WriteString(value); err != nil {
|
||||
return err
|
||||
}
|
||||
sudo := cmd.SudoHandleCmd()
|
||||
_, _ = cmd.Execf("%s systemctl restart %s", sudo, serviceName)
|
||||
err = systemctl.Restart(serviceName)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("handle restart %s failed, err: %v", serviceName, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -541,12 +534,14 @@ func handleGunzip(path string) error {
|
|||
}
|
||||
|
||||
func loadServiceName() (string, error) {
|
||||
if exist, _ := systemctl.IsExist("sshd"); exist {
|
||||
return "sshd", nil
|
||||
} else if exist, _ := systemctl.IsExist("ssh"); exist {
|
||||
return "ssh", nil
|
||||
serviceName, err := systemctl.GetServiceName("ssh")
|
||||
if err != nil {
|
||||
if errors.Is(err, systemctl.ErrServiceNotFound) {
|
||||
return "", fmt.Errorf("SSH service unavailable. Please ensure OpenSSH server is installed and configured")
|
||||
}
|
||||
return "", fmt.Errorf("failed to load SSH service name: %w", err)
|
||||
}
|
||||
return "", errors.New("The ssh or sshd service is unavailable")
|
||||
return serviceName, nil
|
||||
}
|
||||
|
||||
func loadDate(currentYear int, DateStr string, nyc *time.Location) time.Time {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
|
@ -17,6 +18,7 @@ import (
|
|||
"github.com/1Panel-dev/1Panel/backend/utils/common"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/files"
|
||||
httpUtil "github.com/1Panel-dev/1Panel/backend/utils/http"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/systemctl"
|
||||
)
|
||||
|
||||
type UpgradeService struct{}
|
||||
|
|
@ -103,123 +105,159 @@ func (u *UpgradeService) Upgrade(req dto.Upgrade) error {
|
|||
}
|
||||
downloadPath := fmt.Sprintf("%s/%s/%s/release", global.CONF.System.RepoUrl, mode, req.Version)
|
||||
fileName := fmt.Sprintf("1panel-%s-%s-%s.tar.gz", req.Version, "linux", itemArch)
|
||||
_ = settingRepo.Update("SystemStatus", "Upgrading")
|
||||
serviceHandle, _ := systemctl.DefaultHandler("1panel")
|
||||
currentServiceName := serviceHandle.GetServiceName()
|
||||
if err := settingRepo.Update("SystemStatus", "Upgrading"); err != nil {
|
||||
return fmt.Errorf("update system status failed: %w", err)
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer func() {
|
||||
if err := settingRepo.Update("SystemStatus", "Free"); err != nil {
|
||||
global.LOG.Errorf("Reset system status failed: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
_ = global.Cron.Stop()
|
||||
defer func() {
|
||||
global.Cron.Start()
|
||||
}()
|
||||
if err := fileOp.DownloadFileWithProxy(downloadPath+"/"+fileName, rootDir+"/"+fileName); err != nil {
|
||||
global.LOG.Errorf("download service file failed, err: %v", err)
|
||||
_ = settingRepo.Update("SystemStatus", "Free")
|
||||
defer global.Cron.Start()
|
||||
|
||||
if err := fileOp.DownloadFileWithProxy(
|
||||
fmt.Sprintf("%s/%s", downloadPath, fileName),
|
||||
path.Join(rootDir, fileName),
|
||||
); err != nil {
|
||||
global.LOG.Errorf("Failed to download upgrade package: %v", err)
|
||||
return
|
||||
}
|
||||
global.LOG.Info("download all file successful!")
|
||||
defer func() {
|
||||
_ = os.Remove(rootDir)
|
||||
}()
|
||||
if err := handleUnTar(rootDir+"/"+fileName, rootDir, ""); err != nil {
|
||||
global.LOG.Errorf("decompress file failed, err: %v", err)
|
||||
_ = settingRepo.Update("SystemStatus", "Free")
|
||||
defer os.RemoveAll(rootDir)
|
||||
|
||||
if err := handleUnTar(path.Join(rootDir, fileName), rootDir, ""); err != nil {
|
||||
global.LOG.Errorf("Failed to extract package: %v", err)
|
||||
return
|
||||
}
|
||||
tmpDir := rootDir + "/" + strings.ReplaceAll(fileName, ".tar.gz", "")
|
||||
|
||||
tmpDir := path.Join(rootDir, strings.TrimSuffix(fileName, ".tar.gz"))
|
||||
|
||||
if err := u.handleBackup(fileOp, originalDir); err != nil {
|
||||
global.LOG.Errorf("handle backup original file failed, err: %v", err)
|
||||
_ = settingRepo.Update("SystemStatus", "Free")
|
||||
return
|
||||
}
|
||||
global.LOG.Info("backup original data successful, now start to upgrade!")
|
||||
|
||||
if err := common.CopyFile(path.Join(tmpDir, "1panel"), "/usr/local/bin"); err != nil {
|
||||
global.LOG.Errorf("upgrade 1panel failed, err: %v", err)
|
||||
u.handleRollback(originalDir, 1)
|
||||
global.LOG.Errorf("Backup failed: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := common.CopyFile(path.Join(tmpDir, "1pctl"), "/usr/local/bin"); err != nil {
|
||||
global.LOG.Errorf("upgrade 1pctl failed, err: %v", err)
|
||||
u.handleRollback(originalDir, 2)
|
||||
return
|
||||
}
|
||||
_, _ = cmd.Execf("cp -r %s /usr/local/bin", path.Join(tmpDir, "lang"))
|
||||
geoPath := path.Join(global.CONF.System.BaseDir, "1panel/geo")
|
||||
_, _ = cmd.Execf("mkdir %s && cp %s %s/", geoPath, path.Join(tmpDir, "GeoIP.mmdb"), geoPath)
|
||||
binDir := systemctl.BinaryPath
|
||||
servicePath, _ := serviceHandle.GetServicePath()
|
||||
geoPath := path.Join(global.CONF.System.BaseDir, "1panel/geo/GeoIP.mmdb")
|
||||
|
||||
if _, err := cmd.Execf("sed -i -e 's#BASE_DIR=.*#BASE_DIR=%s#g' /usr/local/bin/1pctl", global.CONF.System.BaseDir); err != nil {
|
||||
global.LOG.Errorf("upgrade basedir in 1pctl failed, err: %v", err)
|
||||
criticalUpdates := []struct {
|
||||
src string
|
||||
dest string
|
||||
step int
|
||||
}{
|
||||
{path.Join(tmpDir, "1panel"), path.Join(binDir, "1panel"), 1},
|
||||
{path.Join(tmpDir, "1pctl"), path.Join(binDir, "1pctl"), 2},
|
||||
{path.Join(tmpDir, currentServiceName), servicePath, 3},
|
||||
}
|
||||
|
||||
for _, update := range criticalUpdates {
|
||||
if err := common.Copy(update.src, update.dest); err != nil {
|
||||
global.LOG.Errorf("Update %s failed: %v", path.Base(update.dest), err)
|
||||
u.handleRollback(originalDir, update.step)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := cmd.Execf("sed -i -e 's#BASE_DIR=.*#BASE_DIR=%s#g' /usr/local/bin/1pctl",
|
||||
global.CONF.System.BaseDir); err != nil {
|
||||
global.LOG.Errorf("Update base directory failed: %v", err)
|
||||
u.handleRollback(originalDir, 2)
|
||||
return
|
||||
}
|
||||
|
||||
if err := common.CopyFile(path.Join(tmpDir, "1panel.service"), "/etc/systemd/system"); err != nil {
|
||||
global.LOG.Errorf("upgrade 1panel.service failed, err: %v", err)
|
||||
u.handleRollback(originalDir, 3)
|
||||
return
|
||||
langDir := path.Join(binDir, "lang")
|
||||
if err := common.Copy(path.Join(tmpDir, "lang"), langDir); err != nil {
|
||||
global.LOG.Errorf("Update language files failed: %v", err)
|
||||
}
|
||||
if err := common.Copy(path.Join(tmpDir, "GeoIP.mmdb"), geoPath); err != nil {
|
||||
global.LOG.Warnf("Update GeoIP database failed: %v", err)
|
||||
}
|
||||
|
||||
global.LOG.Info("upgrade successful!")
|
||||
go writeLogs(req.Version)
|
||||
_ = settingRepo.Update("SystemVersion", req.Version)
|
||||
_ = settingRepo.Update("SystemStatus", "Free")
|
||||
checkPointOfWal()
|
||||
_, _ = cmd.ExecWithTimeOut("systemctl daemon-reload && systemctl restart 1panel.service", 1*time.Minute)
|
||||
if err := settingRepo.Update("SystemVersion", req.Version); err != nil {
|
||||
global.LOG.Errorf("Update system version failed: %v", err)
|
||||
}
|
||||
if serviceHandle.ManagerName() == "systemd" {
|
||||
_, _ = cmd.Exec("systemctl daemon-reload")
|
||||
}
|
||||
if err := systemctl.Restart("1panel"); err != nil {
|
||||
global.LOG.Errorf("Service restart failed: %v", err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UpgradeService) handleBackup(fileOp files.FileOp, originalDir string) error {
|
||||
if err := fileOp.Copy("/usr/local/bin/1panel", originalDir); err != nil {
|
||||
return err
|
||||
global.LOG.Info("Initiating backup procedure...")
|
||||
h, _ := systemctl.DefaultHandler("1panel")
|
||||
binDir := systemctl.BinaryPath
|
||||
servicePath, _ := h.GetServicePath()
|
||||
geoPath := path.Join(global.CONF.System.BaseDir, "1panel/geo/GeoIP.mmdb")
|
||||
|
||||
backupItems := []struct {
|
||||
src string
|
||||
dest string
|
||||
}{
|
||||
{path.Join(binDir, "1panel"), originalDir},
|
||||
{path.Join(binDir, "1pctl"), originalDir},
|
||||
{servicePath, originalDir},
|
||||
{path.Join(binDir, "lang"), originalDir},
|
||||
{geoPath, originalDir},
|
||||
}
|
||||
if err := fileOp.Copy("/usr/local/bin/1pctl", originalDir); err != nil {
|
||||
return err
|
||||
|
||||
for _, item := range backupItems {
|
||||
if err := fileOp.Copy(item.src, item.dest); err != nil {
|
||||
return fmt.Errorf("backup %s failed: %w", path.Base(item.src), err)
|
||||
}
|
||||
}
|
||||
if err := fileOp.Copy("/etc/systemd/system/1panel.service", originalDir); err != nil {
|
||||
return err
|
||||
}
|
||||
_, _ = cmd.Execf("cp -r /usr/local/bin/lang %s", originalDir)
|
||||
_, _ = cmd.Execf("cp %s %s", path.Join(global.CONF.System.BaseDir, "1panel/geo/GeoIP.mmdb"), originalDir)
|
||||
checkPointOfWal()
|
||||
if err := handleTar(path.Join(global.CONF.System.BaseDir, "1panel/db"), originalDir, "db.tar.gz", "db/1Panel.db-*", ""); err != nil {
|
||||
return err
|
||||
|
||||
if err := handleTar(
|
||||
path.Join(global.CONF.System.BaseDir, "1panel/db"),
|
||||
originalDir,
|
||||
"db.tar.gz",
|
||||
"db/1Panel.db-*",
|
||||
"",
|
||||
); err != nil {
|
||||
return fmt.Errorf("database backup failed: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UpgradeService) handleRollback(originalDir string, errStep int) {
|
||||
_ = settingRepo.Update("SystemStatus", "Free")
|
||||
global.LOG.Info("Initiating rollback procedure...")
|
||||
h, _ := systemctl.DefaultHandler("1panel")
|
||||
binDir := systemctl.BinaryPath
|
||||
servicePath, _ := h.GetServicePath()
|
||||
geoPath := path.Join(global.CONF.System.BaseDir, "1panel/geo/GeoIP.mmdb")
|
||||
|
||||
checkPointOfWal()
|
||||
if _, err := os.Stat(path.Join(originalDir, "1Panel.db")); err == nil {
|
||||
if err := common.CopyFile(path.Join(originalDir, "1Panel.db"), global.CONF.System.DbPath); err != nil {
|
||||
global.LOG.Errorf("rollback 1panel db failed, err: %v", err)
|
||||
rollbackSteps := []struct {
|
||||
src string
|
||||
dest string
|
||||
}{
|
||||
{path.Join(originalDir, "1panel"), path.Join(binDir, "1panel")},
|
||||
{path.Join(originalDir, "1pctl"), path.Join(binDir, "1pctl")},
|
||||
{path.Join(originalDir, filepath.Base(servicePath)), servicePath},
|
||||
{path.Join(originalDir, "lang"), path.Join(binDir, "lang")},
|
||||
{path.Join(originalDir, "GeoIP.mmdb"), geoPath},
|
||||
}
|
||||
|
||||
for _, step := range rollbackSteps[:errStep] {
|
||||
if err := common.CopyFile(step.src, step.dest); err != nil {
|
||||
global.LOG.Errorf("Rollback %s failed: %v", path.Base(step.src), err)
|
||||
}
|
||||
}
|
||||
if _, err := os.Stat(path.Join(originalDir, "db.tar.gz")); err == nil {
|
||||
if err := handleUnTar(path.Join(originalDir, "db.tar.gz"), global.CONF.System.DbPath, ""); err != nil {
|
||||
global.LOG.Errorf("rollback 1panel db failed, err: %v", err)
|
||||
}
|
||||
}
|
||||
if err := common.CopyFile(path.Join(originalDir, "1panel"), "/usr/local/bin"); err != nil {
|
||||
global.LOG.Errorf("rollback 1pctl failed, err: %v", err)
|
||||
}
|
||||
if errStep == 1 {
|
||||
return
|
||||
}
|
||||
if err := common.CopyFile(path.Join(originalDir, "1pctl"), "/usr/local/bin"); err != nil {
|
||||
global.LOG.Errorf("rollback 1panel failed, err: %v", err)
|
||||
}
|
||||
_, _ = cmd.Execf("cp -r %s /usr/local/bin", path.Join(originalDir, "lang"))
|
||||
geoPath := path.Join(global.CONF.System.BaseDir, "1panel/geo")
|
||||
_, _ = cmd.Execf("mkdir %s && cp %s %s/", geoPath, path.Join(originalDir, "GeoIP.mmdb"), geoPath)
|
||||
|
||||
if errStep == 2 {
|
||||
return
|
||||
}
|
||||
if err := common.CopyFile(path.Join(originalDir, "1panel.service"), "/etc/systemd/system"); err != nil {
|
||||
global.LOG.Errorf("rollback 1panel failed, err: %v", err)
|
||||
if err := systemctl.Restart("1panel"); err != nil {
|
||||
global.LOG.Errorf("Service restart during rollback failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,13 +3,12 @@ package common
|
|||
import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||
"github.com/gin-gonic/gin"
|
||||
"io"
|
||||
mathRand "math/rand"
|
||||
"net"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"sort"
|
||||
|
|
@ -18,6 +17,9 @@ import (
|
|||
"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"
|
||||
|
|
@ -124,6 +126,9 @@ func GetSortedVersions(versions []string) []string {
|
|||
}
|
||||
|
||||
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
|
||||
|
|
@ -152,7 +157,57 @@ func CopyFile(src, dst string) error {
|
|||
}
|
||||
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, ".")
|
||||
|
|
|
|||
401
backend/utils/systemctl/configloade.go
Normal file
401
backend/utils/systemctl/configloade.go
Normal file
|
|
@ -0,0 +1,401 @@
|
|||
package systemctl
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"go4.org/syncutil/singleflight"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
var (
|
||||
aliasFile string
|
||||
serviceAliases sync.Map
|
||||
saveTimer *time.Timer
|
||||
saveMutex sync.Mutex
|
||||
afterSaveTime = 20 * time.Second
|
||||
)
|
||||
var (
|
||||
ErrServiceNotFound = errors.New("service not found")
|
||||
ErrDiscoveryTimeout = errors.New("service discovery timeout for: %w")
|
||||
ErrServiceDiscovery = errors.New("service discovery failed for: %w")
|
||||
ErrNoValidService = errors.New("no valid service found for: %w")
|
||||
)
|
||||
|
||||
func loadPredefinedAliases() map[string][]string {
|
||||
return map[string][]string{
|
||||
"clam": {"clamav-daemon.service", "clamd@scan.service", "clamd"},
|
||||
"freshclam": {"clamav-freshclam.service", "freshclam.service"},
|
||||
"fail2ban": {"fail2ban.service", "fail2ban"},
|
||||
"supervisor": {"supervisord.service", "supervisor.service", "supervisord", "supervisor"},
|
||||
"ssh": {"sshd.service", "ssh.service", "sshd", "ssh"},
|
||||
"1panel": {"1panel.service", "1paneld"},
|
||||
"docker": {"docker.service", "dockerd"},
|
||||
}
|
||||
}
|
||||
|
||||
func InitializeServiceDiscovery() {
|
||||
svcName := loadAliasesFromConfig()
|
||||
if len(svcName) > 0 {
|
||||
RegisterServiceAliases(svcName)
|
||||
}
|
||||
}
|
||||
|
||||
func RegisterServiceAliases(aliases map[string][]string) {
|
||||
for key, values := range aliases {
|
||||
existing, loaded := serviceAliases.LoadOrStore(key, values)
|
||||
if loaded {
|
||||
merged := append(existing.([]string), values...)
|
||||
serviceAliases.Store(key, merged)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func loadAliasesFromConfig() map[string][]string {
|
||||
data, err := os.ReadFile(aliasFile)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
var rawAliases map[string][]string
|
||||
json.Unmarshal(data, &rawAliases)
|
||||
|
||||
validAliases := make(map[string][]string)
|
||||
for key, aliases := range rawAliases {
|
||||
valid := []string{}
|
||||
for _, alias := range aliases {
|
||||
confirmed, _ := confirmServiceExists(alias)
|
||||
if confirmed {
|
||||
valid = append(valid, alias)
|
||||
}
|
||||
}
|
||||
if len(valid) > 0 {
|
||||
validAliases[key] = valid
|
||||
}
|
||||
}
|
||||
return validAliases
|
||||
}
|
||||
|
||||
func cleanupKeywordAliases(keyword string) {
|
||||
serviceAliases.Range(func(k, v interface{}) bool {
|
||||
if k.(string) != keyword {
|
||||
return true
|
||||
}
|
||||
aliases := v.([]string)
|
||||
valid := make([]string, 0)
|
||||
for _, alias := range aliases {
|
||||
confirmed, _ := confirmServiceExists(alias)
|
||||
if confirmed {
|
||||
valid = append(valid, alias)
|
||||
}
|
||||
}
|
||||
if len(valid) == 0 {
|
||||
serviceAliases.Delete(k)
|
||||
serviceExistenceCache.Delete(k)
|
||||
} else {
|
||||
serviceAliases.Store(k, valid)
|
||||
}
|
||||
return true
|
||||
})
|
||||
go scheduleSave()
|
||||
}
|
||||
|
||||
func smartServiceName(keyword string) (string, error) {
|
||||
mgr := GetGlobalManager()
|
||||
processedName := handleServiceNaming(mgr, keyword)
|
||||
|
||||
confirmed, _ := confirmServiceExists(processedName)
|
||||
if confirmed {
|
||||
updateAliases(keyword, processedName)
|
||||
return processedName, nil
|
||||
}
|
||||
|
||||
candidates := append([]string{processedName}, getAliases(keyword)...)
|
||||
if name, err := validateCandidatesConcurrently(candidates); err == nil {
|
||||
updateAliases(keyword, name)
|
||||
return name, nil
|
||||
}
|
||||
|
||||
discoveredName, err := discoverAndSelectService(keyword)
|
||||
if err != nil {
|
||||
cleanupKeywordAliases(keyword)
|
||||
return "", ErrServiceNotFound
|
||||
}
|
||||
updateAliases(keyword, discoveredName)
|
||||
return discoveredName, nil
|
||||
}
|
||||
func handleServiceNaming(mgr ServiceManager, keyword string) string {
|
||||
keyword = strings.ToLower(keyword)
|
||||
// 处理 .service.socket 后缀
|
||||
if strings.HasSuffix(keyword, ".service.socket") {
|
||||
keyword = strings.TrimSuffix(keyword, ".service.socket") + ".socket"
|
||||
}
|
||||
if mgr.Name() != "systemd" {
|
||||
keyword = strings.TrimSuffix(keyword, ".service")
|
||||
return keyword
|
||||
}
|
||||
// 自动补全 .service 后缀
|
||||
if !strings.HasSuffix(keyword, ".service") &&
|
||||
!strings.HasSuffix(keyword, ".socket") {
|
||||
keyword += ".service"
|
||||
}
|
||||
return keyword
|
||||
}
|
||||
func validateCandidatesConcurrently(candidates []string) (string, error) {
|
||||
var (
|
||||
g errgroup.Group
|
||||
found = make(chan string, 1) // 缓冲确保首个结果不阻塞
|
||||
)
|
||||
|
||||
// 启动并发检查
|
||||
for _, candidate := range candidates {
|
||||
cand := candidate // 避免闭包循环引用
|
||||
g.Go(func() error {
|
||||
confirmed, _ := confirmServiceExists(cand)
|
||||
if confirmed {
|
||||
select {
|
||||
case found <- cand: // 发送首个成功结果
|
||||
default: // 如果已有结果,忽略后续
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return ErrServiceNotFound
|
||||
})
|
||||
}
|
||||
|
||||
// 处理结果
|
||||
resultErr := make(chan error, 1)
|
||||
go func() {
|
||||
defer close(found)
|
||||
resultErr <- g.Wait()
|
||||
}()
|
||||
|
||||
select {
|
||||
case name := <-found:
|
||||
return name, nil
|
||||
case <-time.After(1000 * time.Millisecond):
|
||||
return "", fmt.Errorf(ErrDiscoveryTimeout.Error(), candidates[0])
|
||||
case err := <-resultErr:
|
||||
if err != nil {
|
||||
return "", ErrServiceNotFound
|
||||
}
|
||||
return "", ErrServiceNotFound
|
||||
}
|
||||
}
|
||||
func discoverAndSelectService(keyword string) (string, error) {
|
||||
discovered, err := discoverServices(keyword)
|
||||
if err != nil {
|
||||
return "", ErrServiceNotFound
|
||||
}
|
||||
|
||||
if len(discovered) == 0 {
|
||||
return "", ErrServiceNotFound
|
||||
}
|
||||
selected, err := selectBestMatch(keyword, discovered)
|
||||
if err != nil {
|
||||
return "", ErrServiceNotFound
|
||||
}
|
||||
|
||||
confirmed, err := confirmServiceExists(selected)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("service existence check failed: %w", err)
|
||||
}
|
||||
if confirmed {
|
||||
return selected, nil
|
||||
}
|
||||
return "", ErrServiceNotFound
|
||||
}
|
||||
func selectBestMatch(keyword string, candidates []string) (string, error) {
|
||||
if len(candidates) == 0 {
|
||||
return "", ErrServiceNotFound
|
||||
}
|
||||
lowerKeyword := strings.ToLower(keyword)
|
||||
var exactMatch string
|
||||
var firstContainMatch string
|
||||
// 第一轮遍历:严格匹配完全一致的名称(不区分大小写)
|
||||
for _, name := range candidates {
|
||||
if strings.EqualFold(name, keyword) {
|
||||
exactMatch = name
|
||||
break // 完全匹配直接终止循环
|
||||
}
|
||||
}
|
||||
if exactMatch != "" {
|
||||
return exactMatch, nil
|
||||
}
|
||||
// 第二轮遍历:寻找首个包含关键字的名称(不区分大小写)
|
||||
for _, name := range candidates {
|
||||
if strings.Contains(strings.ToLower(name), lowerKeyword) {
|
||||
firstContainMatch = name
|
||||
global.LOG.Debugf("[%s] [keyword: %s] Found first contain match: %s", getManagerName(), keyword, firstContainMatch)
|
||||
break
|
||||
}
|
||||
}
|
||||
if firstContainMatch != "" {
|
||||
return firstContainMatch, nil
|
||||
}
|
||||
// 无任何匹配项时返回明确错误
|
||||
return "", fmt.Errorf("%w: %q (no exact or partial match)", ErrNoValidService, keyword)
|
||||
}
|
||||
|
||||
type cacheItem struct {
|
||||
services []string
|
||||
expires time.Time
|
||||
exists bool
|
||||
}
|
||||
|
||||
var (
|
||||
discoveryCache sync.Map
|
||||
discoveryGroup singleflight.Group
|
||||
)
|
||||
|
||||
func discoverServices(keyword string) ([]string, error) {
|
||||
result, err := discoveryGroup.Do(keyword, func() (interface{}, error) {
|
||||
if cached, ok := discoveryCache.Load(keyword); ok {
|
||||
item := cached.(cacheItem)
|
||||
if time.Now().Before(item.expires) {
|
||||
return item.services, nil
|
||||
}
|
||||
discoveryCache.Delete(keyword)
|
||||
}
|
||||
manager := GetGlobalManager()
|
||||
results, err := manager.FindServices(keyword)
|
||||
|
||||
if err != nil {
|
||||
global.LOG.Errorf("Find services failed for %s: %v", keyword, err)
|
||||
return nil, fmt.Errorf("%w: %q (%v)", ErrServiceDiscovery, keyword, err)
|
||||
} else {
|
||||
discoveryCache.Store(keyword, cacheItem{
|
||||
services: results,
|
||||
expires: time.Now().Add(5 * time.Minute),
|
||||
})
|
||||
}
|
||||
return results, err
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result.([]string), nil
|
||||
}
|
||||
|
||||
func updateAliases(keyword, alias string) {
|
||||
if keyword == alias {
|
||||
return
|
||||
}
|
||||
existing, _ := serviceAliases.LoadOrStore(keyword, []string{})
|
||||
aliases := existing.([]string)
|
||||
if contains(aliases, alias) {
|
||||
return
|
||||
}
|
||||
serviceAliases.Store(keyword, append(aliases, alias))
|
||||
go scheduleSave()
|
||||
}
|
||||
|
||||
func scheduleSave() {
|
||||
saveMutex.Lock()
|
||||
defer saveMutex.Unlock()
|
||||
|
||||
if saveTimer != nil {
|
||||
saveTimer.Stop()
|
||||
}
|
||||
|
||||
dataSnapshot := make(map[string][]string)
|
||||
serviceAliases.Range(func(k, v interface{}) bool {
|
||||
dataSnapshot[k.(string)] = append([]string{}, v.([]string)...)
|
||||
return true
|
||||
})
|
||||
aliasFile = filepath.Join(constant.ResourceDir, "svcaliases.json")
|
||||
saveTimer = time.AfterFunc(afterSaveTime, func() {
|
||||
tmpFile := aliasFile + ".tmp"
|
||||
if err := saveAliasesToFile(dataSnapshot, tmpFile); err == nil {
|
||||
os.Rename(tmpFile, aliasFile)
|
||||
}
|
||||
})
|
||||
}
|
||||
func saveAliasesToFile(data map[string][]string, path string) error {
|
||||
fileData, err := json.MarshalIndent(data, "", " ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("serialization failed: %w", err)
|
||||
}
|
||||
|
||||
if err := os.WriteFile(path, fileData, 0644); err != nil {
|
||||
return fmt.Errorf("file write failed: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func contains(slice []string, item string) bool {
|
||||
for _, s := range slice {
|
||||
if s == item {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var serviceExistenceCache sync.Map
|
||||
|
||||
func confirmServiceExists(serviceName string) (bool, error) {
|
||||
if val, ok := serviceExistenceCache.Load(serviceName); ok {
|
||||
if item, ok := val.(cacheItem); ok && time.Now().Before(item.expires) {
|
||||
return item.exists, nil
|
||||
}
|
||||
serviceExistenceCache.Delete(serviceName)
|
||||
}
|
||||
handler := NewServiceHandler(defaultServiceConfig(serviceName))
|
||||
isExist, err := handler.IsExists()
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("check service existence failed: %w", err)
|
||||
}
|
||||
serviceExistenceCache.Store(serviceName, cacheItem{
|
||||
exists: isExist.IsExists,
|
||||
expires: time.Now().Add(30 * time.Second),
|
||||
})
|
||||
return isExist.IsExists, nil
|
||||
}
|
||||
|
||||
func getAliases(keyword string) []string {
|
||||
predefined := loadPredefinedAliases()[keyword]
|
||||
runtimeAliases, _ := serviceAliases.LoadOrStore(keyword, []string{})
|
||||
merged := make(map[string]struct{})
|
||||
for _, alias := range predefined {
|
||||
merged[alias] = struct{}{}
|
||||
}
|
||||
for _, alias := range runtimeAliases.([]string) {
|
||||
merged[alias] = struct{}{}
|
||||
}
|
||||
result := make([]string, 0, len(merged))
|
||||
for k := range merged {
|
||||
result = append(result, k)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
type ConfigOption struct {
|
||||
TailLines string
|
||||
}
|
||||
|
||||
func ViewConfig(path string, opt ConfigOption) (string, error) {
|
||||
var cmd []string
|
||||
if opt.TailLines != "" && opt.TailLines != "0" {
|
||||
cmd = []string{"tail", "-n", opt.TailLines, path}
|
||||
} else {
|
||||
cmd = []string{"cat", path}
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
output, err := executeCommand(ctx, cmd[0], cmd[1:]...)
|
||||
if err != nil {
|
||||
// global.LOG.Errorf("View config command failed: %v", err)
|
||||
return "", fmt.Errorf("view config failed: %w", err)
|
||||
}
|
||||
return string(output), nil
|
||||
}
|
||||
409
backend/utils/systemctl/handle.go
Normal file
409
backend/utils/systemctl/handle.go
Normal file
|
|
@ -0,0 +1,409 @@
|
|||
package systemctl
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
)
|
||||
|
||||
// ServiceConfig 服务配置结构
|
||||
type ServiceConfig struct {
|
||||
ServiceName map[string]string
|
||||
}
|
||||
|
||||
// ServiceHandler 服务操作处理器
|
||||
type ServiceHandler struct {
|
||||
config *ServiceConfig
|
||||
manager ServiceManager
|
||||
}
|
||||
|
||||
// NewServiceHandler 创建服务处理器
|
||||
func NewServiceHandler(serviceNames map[string]string) *ServiceHandler {
|
||||
mgr := GetGlobalManager()
|
||||
if mgr == nil {
|
||||
global.LOG.Error("failed to get global service manager when creating ServiceHandler")
|
||||
return nil
|
||||
}
|
||||
return &ServiceHandler{
|
||||
config: &ServiceConfig{
|
||||
ServiceName: serviceNames,
|
||||
},
|
||||
manager: mgr,
|
||||
}
|
||||
}
|
||||
|
||||
// ServiceStatus 服务状态返回结构
|
||||
type ServiceStatus struct {
|
||||
IsActive bool `json:"isActive"`
|
||||
IsEnabled bool `json:"isEnabled"`
|
||||
IsExists bool `json:"isExists"`
|
||||
Output string `json:"output"`
|
||||
}
|
||||
type ServiceIsActive struct {
|
||||
IsActive bool `json:"isActive"`
|
||||
Output string `json:"output"`
|
||||
}
|
||||
|
||||
type ServiceIsEnabled struct {
|
||||
IsEnabled bool `json:"isEnabled"`
|
||||
Output string `json:"output"`
|
||||
}
|
||||
|
||||
// ServiceResult 通用操作结果
|
||||
type ServiceResult struct {
|
||||
Success bool `json:"success"`
|
||||
Message string `json:"message"`
|
||||
Output string `json:"output"`
|
||||
}
|
||||
|
||||
var (
|
||||
BinaryPath = "/usr/local/bin" // 1panl service default path
|
||||
ErrServiceNotExist = errors.New("service does not exist")
|
||||
)
|
||||
|
||||
// 默认服务配置生成器(自动映射服务名到当前管理器)
|
||||
func defaultServiceConfig(serviceName string) map[string]string {
|
||||
mgr := getManagerName()
|
||||
if mgr == "" {
|
||||
global.LOG.Error("failed to get manager name for default service config")
|
||||
return nil
|
||||
}
|
||||
return map[string]string{
|
||||
mgr: serviceName,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *ServiceHandler) ManagerName() string { return h.manager.Name() }
|
||||
func getManagerName() string {
|
||||
if mgr := GetGlobalManager(); mgr != nil {
|
||||
return mgr.Name()
|
||||
}
|
||||
global.LOG.Error("failed to get global service manager")
|
||||
return ""
|
||||
}
|
||||
|
||||
func (h *ServiceHandler) GetServiceName() string {
|
||||
manager := h.ManagerName()
|
||||
if manager == "" {
|
||||
global.LOG.Error("manager name is empty when getting service name")
|
||||
return ""
|
||||
}
|
||||
return h.config.ServiceName[manager]
|
||||
}
|
||||
|
||||
// GetServicePath 获取服务路径
|
||||
func (h *ServiceHandler) GetServicePath() (string, error) {
|
||||
manager := h.ManagerName()
|
||||
serviceName := h.config.ServiceName[manager]
|
||||
|
||||
if serviceName == "" {
|
||||
err := fmt.Errorf("service name not found for %s", manager)
|
||||
global.LOG.Errorf("GetServicePath error: %v", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
cleanPath := filepath.Clean(serviceName)
|
||||
if strings.Contains(cleanPath, "..") {
|
||||
err := fmt.Errorf("invalid path: %q", cleanPath)
|
||||
global.LOG.Errorf("GetServicePath security check failed: %v", err)
|
||||
return "", err
|
||||
}
|
||||
switch manager {
|
||||
case "systemd":
|
||||
return findSystemdPath(cleanPath)
|
||||
case "openrc", "sysvinit":
|
||||
return checkInitDPath(cleanPath)
|
||||
default:
|
||||
err := fmt.Errorf("unsupported init system: %s", manager)
|
||||
global.LOG.Errorf("GetServicePath error: %v", err)
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
func findSystemdPath(name string) (string, error) {
|
||||
paths := []string{"/etc/systemd/system", "/usr/lib/systemd/system",
|
||||
"/usr/share/systemd/system", "/usr/local/lib/systemd/system"}
|
||||
|
||||
for _, p := range paths {
|
||||
if path := filepath.Join(p, name); FileExist(path) {
|
||||
return path, nil
|
||||
}
|
||||
}
|
||||
err := fmt.Errorf("service path not found for %s", name)
|
||||
global.LOG.Errorf("findSystemdPath error: %v", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
func checkInitDPath(name string) (string, error) {
|
||||
path := filepath.Join("/etc/init.d", name)
|
||||
if !FileExist(path) {
|
||||
err := fmt.Errorf("service path not found for %s", name)
|
||||
global.LOG.Errorf("checkInitDPath error: %v", err)
|
||||
return "", err
|
||||
}
|
||||
return path, nil
|
||||
}
|
||||
|
||||
func (h *ServiceHandler) ExecuteAction(action string) (ServiceResult, error) {
|
||||
successMsg := fmt.Sprintf("%s : %s completed", action, h.GetServiceName())
|
||||
return h.executeAction(action, successMsg)
|
||||
}
|
||||
|
||||
// CheckStatus 检查服务状态
|
||||
func (h *ServiceHandler) CheckStatus() (ServiceStatus, error) {
|
||||
manager := GetGlobalManager()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
type result struct {
|
||||
isActive bool
|
||||
isEnabled bool
|
||||
output string
|
||||
err error
|
||||
}
|
||||
var status ServiceStatus
|
||||
var errs []error
|
||||
|
||||
results := make(chan result, 2)
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
|
||||
// 任务1:检查服务是否活跃(status)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
res := result{}
|
||||
cmd, err := manager.BuildCommand("status", h.config)
|
||||
if err != nil {
|
||||
res.err = fmt.Errorf("build status command failed: %w", err)
|
||||
results <- res
|
||||
return
|
||||
}
|
||||
|
||||
output, err := executeCommand(ctx, cmd[0], cmd[1:]...)
|
||||
if err != nil {
|
||||
res.err = fmt.Errorf("status check failed: %w", err)
|
||||
results <- res
|
||||
return
|
||||
}
|
||||
|
||||
isActive, err := manager.ParseStatus(string(output), h.config, "active")
|
||||
if err != nil {
|
||||
res.err = fmt.Errorf("parse status failed: %w", err)
|
||||
results <- res
|
||||
return
|
||||
}
|
||||
res.isActive = isActive
|
||||
res.output = string(output)
|
||||
results <- res
|
||||
}()
|
||||
|
||||
// 任务2:检查服务是否启用(is-enabled)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
res := result{}
|
||||
cmd, err := manager.BuildCommand("is-enabled", h.config)
|
||||
if err != nil {
|
||||
res.err = fmt.Errorf("build is-enabled command failed: %w", err)
|
||||
results <- res
|
||||
return
|
||||
}
|
||||
|
||||
output, err := executeCommand(ctx, cmd[0], cmd[1:]...)
|
||||
if err != nil {
|
||||
res.err = fmt.Errorf("enabled check failed: %w", err)
|
||||
results <- res
|
||||
return
|
||||
}
|
||||
|
||||
isEnabled, err := manager.ParseStatus(string(output), h.config, "enabled")
|
||||
if err != nil {
|
||||
res.err = fmt.Errorf("parse enabled status failed: %w", err)
|
||||
results <- res
|
||||
return
|
||||
}
|
||||
res.isEnabled = isEnabled
|
||||
results <- res
|
||||
}()
|
||||
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(results)
|
||||
}()
|
||||
|
||||
for res := range results {
|
||||
if res.err != nil {
|
||||
errs = append(errs, res.err)
|
||||
continue
|
||||
}
|
||||
status.IsActive = res.isActive
|
||||
status.IsEnabled = res.isEnabled
|
||||
if res.output != "" {
|
||||
status.Output = res.output
|
||||
}
|
||||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
return status, errors.Join(errs...)
|
||||
}
|
||||
return status, nil
|
||||
}
|
||||
|
||||
func (h *ServiceHandler) IsExists() (ServiceStatus, error) {
|
||||
manager := GetGlobalManager()
|
||||
isExist, _ := manager.ServiceExists(h.config)
|
||||
return ServiceStatus{
|
||||
IsExists: isExist,
|
||||
}, nil
|
||||
}
|
||||
func (h *ServiceHandler) IsActive() (ServiceStatus, error) {
|
||||
manager := GetGlobalManager()
|
||||
if manager == nil {
|
||||
global.LOG.Error("service manager not initialized during active check")
|
||||
return ServiceStatus{}, fmt.Errorf("service manager not initialized")
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
activeCmd, err := manager.BuildCommand("status", h.config)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("Build status command failed: %v", err)
|
||||
return ServiceStatus{}, fmt.Errorf("build status command failed: %w", err)
|
||||
}
|
||||
|
||||
output, err := executeCommand(ctx, activeCmd[0], activeCmd[1:]...)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "inactive") {
|
||||
return ServiceStatus{
|
||||
IsExists: false,
|
||||
}, nil
|
||||
}
|
||||
global.LOG.Errorf("Active check execution failed: %v", err)
|
||||
return ServiceStatus{}, fmt.Errorf("status check failed: %w", err)
|
||||
}
|
||||
|
||||
isActive, err := manager.ParseStatus(string(output), h.config, "active")
|
||||
if err != nil {
|
||||
global.LOG.Warnf("Status parse error: %v", err)
|
||||
}
|
||||
return ServiceStatus{
|
||||
IsActive: isActive,
|
||||
Output: string(output),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (h *ServiceHandler) IsEnabled() (ServiceStatus, error) {
|
||||
manager := GetGlobalManager()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
enabledCmd, err := manager.BuildCommand("is-enabled", h.config)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "not found") {
|
||||
return ServiceStatus{
|
||||
IsEnabled: false,
|
||||
}, nil
|
||||
}
|
||||
global.LOG.Errorf("Build is-enabled command failed: %v", err)
|
||||
return ServiceStatus{}, fmt.Errorf("build enabled check command failed: %w", err)
|
||||
}
|
||||
|
||||
output, err := executeCommand(ctx, enabledCmd[0], enabledCmd[1:]...)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "disabled") {
|
||||
return ServiceStatus{
|
||||
IsEnabled: false,
|
||||
}, nil
|
||||
}
|
||||
// // isEnabled, err := h.ParseStatus(string(output), h.config, "enabled")
|
||||
// global.LOG.Errorf("Enabled check execution failed: %v", err)
|
||||
// return ServiceStatus{}, fmt.Errorf("enabled check failed: %w", err)
|
||||
}
|
||||
|
||||
isEnabled, err := manager.ParseStatus(string(output), h.config, "enabled")
|
||||
if err != nil {
|
||||
global.LOG.Warnf("Enabled status parse error: %v", err)
|
||||
}
|
||||
return ServiceStatus{
|
||||
IsEnabled: isEnabled,
|
||||
Output: string(output),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// StartService 启动服务
|
||||
func (h *ServiceHandler) StartService() (ServiceResult, error) {
|
||||
return h.ExecuteAction("start")
|
||||
}
|
||||
|
||||
// StopService 停止服务
|
||||
func (h *ServiceHandler) StopService() (ServiceResult, error) {
|
||||
return h.ExecuteAction("stop")
|
||||
}
|
||||
|
||||
// RestartService 重启服务
|
||||
func (h *ServiceHandler) RestartService() (ServiceResult, error) {
|
||||
return h.ExecuteAction("restart")
|
||||
}
|
||||
|
||||
// EnableService 启用开机启动
|
||||
func (h *ServiceHandler) EnableService() (ServiceResult, error) {
|
||||
return h.ExecuteAction("enable")
|
||||
}
|
||||
|
||||
// DisableService 禁用开机启动
|
||||
func (h *ServiceHandler) DisableService() (ServiceResult, error) {
|
||||
return h.ExecuteAction("disable")
|
||||
}
|
||||
|
||||
func (h *ServiceHandler) executeAction(action, successMsg string) (ServiceResult, error) {
|
||||
manager := GetGlobalManager()
|
||||
if manager == nil {
|
||||
global.LOG.Error("service manager not initialized during action execution")
|
||||
return ServiceResult{}, fmt.Errorf("service manager not initialized")
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
cmdArgs, err := manager.BuildCommand(action, h.config)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("Build command failed for action %s: %v", action, err)
|
||||
return ServiceResult{}, fmt.Errorf("build command failed: %w", err)
|
||||
}
|
||||
|
||||
output, err := executeCommand(ctx, cmdArgs[0], cmdArgs[1:]...)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("%s operation failed: %v", action, err)
|
||||
return ServiceResult{
|
||||
Success: false,
|
||||
Message: fmt.Sprintf("%s failed", action),
|
||||
Output: string(output),
|
||||
}, fmt.Errorf("%s operation failed: %w", action, err)
|
||||
}
|
||||
|
||||
global.LOG.Infof("[%s]: %s", manager.Name(), successMsg)
|
||||
return ServiceResult{
|
||||
Success: true,
|
||||
Message: successMsg,
|
||||
Output: string(output),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ReloadManager 重新加载服务管理器(仅用于测试/调试)
|
||||
func (h *ServiceHandler) ReloadManager() error {
|
||||
if err := ReinitializeManager(); err != nil {
|
||||
global.LOG.Errorf("Failed to reload service manager: %v", err)
|
||||
return fmt.Errorf("failed to reload service manager: %w", err)
|
||||
}
|
||||
global.LOG.Info("Service manager reloaded successfully")
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
ExecuteCommand = executeCommand
|
||||
)
|
||||
466
backend/utils/systemctl/managers.go
Normal file
466
backend/utils/systemctl/managers.go
Normal file
|
|
@ -0,0 +1,466 @@
|
|||
package systemctl
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
)
|
||||
|
||||
var (
|
||||
managers = make(map[string]ServiceManager)
|
||||
mu sync.RWMutex
|
||||
globalManager ServiceManager
|
||||
managerPriority = []string{"systemd", "openrc", "sysvinit"}
|
||||
)
|
||||
|
||||
const (
|
||||
defaultCommandTimeout = 30 * time.Second
|
||||
serviceCheckTimeout = 5 * time.Second
|
||||
)
|
||||
|
||||
type ServiceManager interface {
|
||||
Name() string
|
||||
IsAvailable() bool
|
||||
ServiceExists(*ServiceConfig) (bool, error)
|
||||
BuildCommand(string, *ServiceConfig) ([]string, error)
|
||||
ParseStatus(string, *ServiceConfig, string) (bool, error)
|
||||
FindServices(string) ([]string, error)
|
||||
}
|
||||
|
||||
type baseManager struct {
|
||||
name string
|
||||
cmdTool string
|
||||
activeRegex *regexp.Regexp
|
||||
enabledRegex *regexp.Regexp
|
||||
}
|
||||
|
||||
func (b *baseManager) Name() string { return b.name }
|
||||
|
||||
func isRootUser() bool {
|
||||
return os.Geteuid() == 0
|
||||
}
|
||||
func (b *baseManager) buildBaseCommand() []string {
|
||||
var cmdArgs []string
|
||||
if !isRootUser() {
|
||||
cmdArgs = append(cmdArgs, "sudo")
|
||||
}
|
||||
cmdArgs = append(cmdArgs, b.cmdTool)
|
||||
return cmdArgs
|
||||
}
|
||||
func (b *baseManager) commonServiceExists(config *ServiceConfig, checkFn func(string) (bool, error)) (bool, error) {
|
||||
if name := config.ServiceName[b.name]; name != "" {
|
||||
exists, checkErr := checkFn(name)
|
||||
if checkErr != nil {
|
||||
// global.LOG.Warnf("Service existence check failed %s: %v", b.name, checkErr)
|
||||
return false, nil
|
||||
}
|
||||
return exists, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
func (b *baseManager) ParseStatus(output string, _ *ServiceConfig, statusType string) (bool, error) {
|
||||
if output == "" {
|
||||
return false, nil
|
||||
}
|
||||
switch statusType {
|
||||
case "active":
|
||||
if b.activeRegex == nil {
|
||||
return false, nil
|
||||
}
|
||||
return b.activeRegex.MatchString(output), nil
|
||||
case "enabled":
|
||||
if b.enabledRegex == nil {
|
||||
return false, nil
|
||||
}
|
||||
return b.enabledRegex.MatchString(output), nil
|
||||
default:
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
func registerManager(m ServiceManager) {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
managers[m.Name()] = m
|
||||
}
|
||||
func init() {
|
||||
for _, mgr := range []ServiceManager{
|
||||
newSystemdManager(),
|
||||
newOpenrcManager(),
|
||||
newSysvinitManager(),
|
||||
} {
|
||||
registerManager(mgr)
|
||||
}
|
||||
}
|
||||
func InitializeGlobalManager() (err error) {
|
||||
|
||||
for _, name := range managerPriority {
|
||||
if mgr, ok := managers[name]; ok && mgr.IsAvailable() {
|
||||
if testManager(mgr) {
|
||||
globalManager = mgr
|
||||
global.LOG.Infof("Initialized service manager: %s", name)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
err = fmt.Errorf("no available service manager found (tried: %v)", managerPriority)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func testManager(mgr ServiceManager) bool {
|
||||
_, err := mgr.BuildCommand("status", &ServiceConfig{
|
||||
ServiceName: map[string]string{mgr.Name(): "test-service"},
|
||||
})
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func GetGlobalManager() ServiceManager {
|
||||
if globalManager == nil {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
if globalManager == nil {
|
||||
return initializeWithRetry()
|
||||
}
|
||||
}
|
||||
return globalManager
|
||||
}
|
||||
func initializeWithRetry() ServiceManager {
|
||||
const (
|
||||
maxRetries = 5
|
||||
initialWait = 1 * time.Second
|
||||
)
|
||||
backoff := initialWait
|
||||
for attempt := 1; attempt <= maxRetries; attempt++ {
|
||||
if err := InitializeGlobalManager(); err == nil {
|
||||
return globalManager
|
||||
}
|
||||
|
||||
logMessage := fmt.Sprintf("Manager init attempt %d/%d failed", attempt, maxRetries)
|
||||
if global.LOG != nil {
|
||||
global.LOG.Warn(logMessage)
|
||||
} else {
|
||||
fmt.Printf("[WARN] %s\n", logMessage)
|
||||
}
|
||||
|
||||
if attempt < maxRetries {
|
||||
time.Sleep(backoff)
|
||||
backoff *= 2
|
||||
}
|
||||
}
|
||||
|
||||
if global.LOG != nil {
|
||||
global.LOG.Error("All manager initialization attempts failed")
|
||||
} else {
|
||||
fmt.Println("[FATAL] All manager initialization attempts failed")
|
||||
}
|
||||
panic("unable to initialize service manager")
|
||||
}
|
||||
func executeCommand(ctx context.Context, command string, args ...string) ([]byte, error) {
|
||||
if _, ok := ctx.Deadline(); !ok {
|
||||
var cancel context.CancelFunc
|
||||
ctx, cancel = context.WithTimeout(ctx, defaultCommandTimeout)
|
||||
defer cancel()
|
||||
}
|
||||
|
||||
cmd := exec.CommandContext(ctx, command, args...)
|
||||
var buf bytes.Buffer
|
||||
cmd.Stdout = &buf
|
||||
cmd.Stderr = &buf
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
return nil, &CommandError{
|
||||
Cmd: cmd.String(),
|
||||
Output: buf.String(),
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
type systemdManager struct{ baseManager }
|
||||
|
||||
func newSystemdManager() ServiceManager {
|
||||
return &systemdManager{baseManager{
|
||||
name: "systemd",
|
||||
cmdTool: "systemctl",
|
||||
activeRegex: regexp.MustCompile(`(?i)Active:\s+active\b`),
|
||||
enabledRegex: regexp.MustCompile(`(?i)^\s*enabled\s*$`),
|
||||
}}
|
||||
}
|
||||
|
||||
func (m *systemdManager) IsAvailable() bool {
|
||||
_, err := exec.LookPath(m.cmdTool)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func (m *systemdManager) ServiceExists(config *ServiceConfig) (bool, error) {
|
||||
return m.commonServiceExists(config, func(name string) (bool, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), serviceCheckTimeout)
|
||||
defer cancel()
|
||||
out, cmdErr := executeCommand(ctx, m.cmdTool, "list-unit-files", name)
|
||||
if cmdErr != nil {
|
||||
return false, fmt.Errorf("systemctl list-unit-files failed: %w", cmdErr)
|
||||
}
|
||||
return bytes.Contains(out, []byte(name)), nil
|
||||
})
|
||||
}
|
||||
|
||||
func (m *systemdManager) BuildCommand(action string, config *ServiceConfig) ([]string, error) {
|
||||
cmdArgs := m.buildBaseCommand()
|
||||
service := config.ServiceName[m.name]
|
||||
switch action {
|
||||
case "is-enabled":
|
||||
cmdArgs = append(cmdArgs, "is-enabled", service)
|
||||
default:
|
||||
cmdArgs = append(cmdArgs, action, service)
|
||||
}
|
||||
return cmdArgs, nil
|
||||
}
|
||||
|
||||
func (m *systemdManager) ParseStatus(output string, config *ServiceConfig, statusType string) (bool, error) {
|
||||
if strings.Contains(output, "could not be found") {
|
||||
return false, nil
|
||||
}
|
||||
result, err := m.baseManager.ParseStatus(output, config, statusType)
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
func (m *systemdManager) FindServices(keyword string) ([]string, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
out, err := executeCommand(ctx, m.cmdTool, "list-unit-files", "--type=service", "--no-legend")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to list systemd services: %w", err)
|
||||
}
|
||||
|
||||
var services []string
|
||||
lines := strings.Split(string(out), "\n")
|
||||
for _, line := range lines {
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) < 1 {
|
||||
continue
|
||||
}
|
||||
serviceName := fields[0]
|
||||
if strings.Contains(serviceName, keyword) {
|
||||
services = append(services, serviceName)
|
||||
}
|
||||
}
|
||||
return services, nil
|
||||
}
|
||||
|
||||
type sysvinitManager struct{ baseManager }
|
||||
|
||||
func newSysvinitManager() ServiceManager {
|
||||
return &sysvinitManager{baseManager{
|
||||
name: "sysvinit",
|
||||
cmdTool: "service",
|
||||
activeRegex: regexp.MustCompile(`(?i)(?:^|\s)\b(running|active)\b(?:$|\s)`),
|
||||
enabledRegex: regexp.MustCompile(`(?i)(?:^|\s)\b(enabled)\b(?:$|\s)`),
|
||||
}}
|
||||
}
|
||||
|
||||
func (m *sysvinitManager) IsAvailable() bool {
|
||||
_, err := exec.LookPath(m.cmdTool)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func (m *sysvinitManager) ServiceExists(config *ServiceConfig) (bool, error) {
|
||||
return m.commonServiceExists(config, func(name string) (bool, error) {
|
||||
_, err := os.Stat(filepath.Join("/etc/init.d", name))
|
||||
if os.IsNotExist(err) {
|
||||
return false, nil
|
||||
} else if err != nil {
|
||||
return false, fmt.Errorf("stat /etc/init.d/%s failed: %w", name, err)
|
||||
}
|
||||
return true, nil
|
||||
})
|
||||
}
|
||||
|
||||
func (m *sysvinitManager) BuildCommand(action string, config *ServiceConfig) ([]string, error) {
|
||||
service := config.ServiceName[m.name]
|
||||
switch action {
|
||||
case "is-enabled":
|
||||
return []string{
|
||||
"sh",
|
||||
"-c",
|
||||
fmt.Sprintf("if ls /etc/rc*.d/S*%s >/dev/null 2>&1; then echo 'enabled'; else echo 'disabled'; fi", service)}, nil
|
||||
case "is-active":
|
||||
return []string{
|
||||
"sh",
|
||||
"-c",
|
||||
fmt.Sprintf("if service %s status >/dev/null 2>&1; then echo 'active'; else echo 'inactive'; fi", service),
|
||||
}, nil
|
||||
default:
|
||||
cmdArgs := m.buildBaseCommand()
|
||||
cmdArgs = append(cmdArgs, service, action)
|
||||
return cmdArgs, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (m *sysvinitManager) ParseStatus(output string, config *ServiceConfig, statusType string) (bool, error) {
|
||||
serviceName := config.ServiceName[m.name]
|
||||
switch statusType {
|
||||
case "enabled":
|
||||
if strings.Contains(output, "no such file or directory") {
|
||||
return false, nil
|
||||
}
|
||||
// 关键逻辑:如果 find 命令有输出(找到符号链接),则服务已启用
|
||||
return strings.TrimSpace(output) != "", nil
|
||||
case "active":
|
||||
// 关键逻辑:如果输出包含 "running" 或 "active",则服务处于活动状态
|
||||
if strings.Contains(output, "not found") {
|
||||
return false, nil
|
||||
}
|
||||
if strings.Contains(output, "running") || strings.Contains(output, "active") {
|
||||
return true, nil
|
||||
}
|
||||
default:
|
||||
result, err := m.baseManager.ParseStatus(output, config, statusType)
|
||||
if err != nil {
|
||||
global.LOG.Debugf("[sysvinit] Status parse failed. [ServiceName:%s] Type: %s, Output: %q", serviceName, statusType, output)
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
return false, fmt.Errorf("unsupported status type: %s", statusType)
|
||||
// service := config.ServiceName[m.name]
|
||||
// serviceRegex := regexp.MustCompile(fmt.Sprintf(`\b%s\b`, regexp.QuoteMeta(service)))
|
||||
// lines := strings.Split(output, "\n")
|
||||
// for _, line := range lines {
|
||||
// if serviceRegex.MatchString(line) {
|
||||
// if strings.Contains(line, "not found") {
|
||||
// return false, nil
|
||||
// }
|
||||
// result, err := m.baseManager.ParseStatus(line, config, statusType)
|
||||
// if err != nil {
|
||||
// return false, err
|
||||
// }
|
||||
// return result, nil
|
||||
// }
|
||||
// }
|
||||
|
||||
// return false, nil
|
||||
}
|
||||
|
||||
func (m *sysvinitManager) FindServices(keyword string) ([]string, error) {
|
||||
files, err := os.ReadDir("/etc/init.d/")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read init.d directory: %w", err)
|
||||
}
|
||||
|
||||
var services []string
|
||||
for _, file := range files {
|
||||
if strings.Contains(file.Name(), keyword) {
|
||||
services = append(services, file.Name())
|
||||
}
|
||||
}
|
||||
return services, nil
|
||||
}
|
||||
|
||||
type openrcManager struct{ baseManager }
|
||||
|
||||
func newOpenrcManager() ServiceManager {
|
||||
return &openrcManager{baseManager{
|
||||
name: "openrc",
|
||||
cmdTool: "rc-service",
|
||||
activeRegex: regexp.MustCompile(`(?i)^\s*status:\s+(started|running|active)\s*$`),
|
||||
enabledRegex: regexp.MustCompile(`(?i)^[^\|]+\|\s*(default|enabled)\b.*$`),
|
||||
}}
|
||||
}
|
||||
func (m *openrcManager) IsAvailable() bool {
|
||||
_, err := exec.LookPath(m.cmdTool)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func (m *openrcManager) ServiceExists(config *ServiceConfig) (bool, error) {
|
||||
return m.commonServiceExists(config, func(name string) (bool, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), serviceCheckTimeout)
|
||||
defer cancel()
|
||||
out, err := executeCommand(ctx, m.cmdTool, "-l")
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("rc-service -l failed: %w", err)
|
||||
}
|
||||
return bytes.Contains(out, []byte(name)), nil
|
||||
})
|
||||
}
|
||||
|
||||
func (m *openrcManager) BuildCommand(action string, config *ServiceConfig) ([]string, error) {
|
||||
cmdArgs := m.buildBaseCommand()
|
||||
service := config.ServiceName[m.name]
|
||||
if action == "is-enabled" {
|
||||
cmdArgs = []string{"rc-update", "check", service}
|
||||
return cmdArgs, nil
|
||||
}
|
||||
cmdArgs = append(cmdArgs, service, action)
|
||||
return cmdArgs, nil
|
||||
}
|
||||
|
||||
func (m *openrcManager) ParseStatus(output string, config *ServiceConfig, statusType string) (bool, error) {
|
||||
if strings.Contains(output, "does not exist") {
|
||||
return false, nil
|
||||
}
|
||||
result, err := m.baseManager.ParseStatus(output, config, statusType)
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
func (m *openrcManager) FindServices(keyword string) ([]string, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
out, err := executeCommand(ctx, m.cmdTool, "-l")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to list openrc services: %w", err)
|
||||
}
|
||||
|
||||
var services []string
|
||||
lines := strings.Split(string(out), "\n")
|
||||
for _, line := range lines {
|
||||
if strings.Contains(line, keyword) {
|
||||
services = append(services, strings.TrimSpace(line))
|
||||
}
|
||||
}
|
||||
return services, nil
|
||||
}
|
||||
|
||||
type CommandError struct {
|
||||
Cmd string
|
||||
Err error
|
||||
Output string
|
||||
}
|
||||
|
||||
func (e CommandError) Error() string {
|
||||
return fmt.Sprintf("command %q failed: %v \nOutput: %s",
|
||||
e.Cmd, e.Err, e.Output)
|
||||
}
|
||||
|
||||
func (e CommandError) Unwrap() error { return e.Err }
|
||||
|
||||
// ReinitializeManager for test
|
||||
func ReinitializeManager() error {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
// initOnce = sync.Once{}
|
||||
globalManager = nil
|
||||
return InitializeGlobalManager()
|
||||
}
|
||||
|
||||
// SetManagerPriority for test
|
||||
func SetManagerPriority(order []string) {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
managerPriority = order
|
||||
}
|
||||
|
|
@ -1,64 +1,221 @@
|
|||
package systemctl
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/pkg/errors"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
)
|
||||
|
||||
func RunSystemCtl(args ...string) (string, error) {
|
||||
cmd := exec.Command("systemctl", args...)
|
||||
output, err := cmd.CombinedOutput()
|
||||
func DefaultHandler(serviceName string) (*ServiceHandler, error) {
|
||||
svcName, err := smartServiceName(serviceName)
|
||||
if err != nil {
|
||||
return string(output), fmt.Errorf("failed to run command: %w", err)
|
||||
// global.LOG.Errorf("SmartServiceName failed for %s: %v", serviceName, err)
|
||||
return nil, ErrServiceNotFound
|
||||
}
|
||||
return string(output), nil
|
||||
return NewServiceHandler(defaultServiceConfig(svcName)), nil
|
||||
}
|
||||
|
||||
func IsActive(serviceName string) (bool, error) {
|
||||
out, err := RunSystemCtl("is-active", serviceName)
|
||||
func GetServiceName(serviceName string) (string, error) {
|
||||
serviceName, err := smartServiceName(serviceName)
|
||||
if err != nil {
|
||||
return false, err
|
||||
// global.LOG.Errorf("GetServiceName validation failed: %v", err)
|
||||
return "", ErrServiceNotFound
|
||||
}
|
||||
return out == "active\n", nil
|
||||
return serviceName, nil
|
||||
}
|
||||
|
||||
func IsEnable(serviceName string) (bool, error) {
|
||||
out, err := RunSystemCtl("is-enabled", serviceName)
|
||||
func GetServicePath(serviceName string) (string, error) {
|
||||
handler, err := DefaultHandler(serviceName)
|
||||
if err != nil {
|
||||
return false, err
|
||||
// global.LOG.Errorf("GetServicePath handler init failed: %v", err)
|
||||
return "", ErrServiceNotFound
|
||||
}
|
||||
return out == "enabled\n", nil
|
||||
return handler.GetServicePath()
|
||||
}
|
||||
|
||||
func CustomAction(action string, serviceName string) (ServiceResult, error) {
|
||||
handler, err := DefaultHandler(serviceName)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("CustomAction handler init failed: %v", err)
|
||||
return ServiceResult{}, ErrServiceNotFound
|
||||
}
|
||||
result, err := handler.ExecuteAction(action)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("CustomAction %s failed: %v", action, err)
|
||||
return result, fmt.Errorf("%s operation failed: %w | Output: %s", action, err, result.Output)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func IsExist(serviceName string) (bool, error) {
|
||||
out, err := RunSystemCtl("is-enabled", serviceName)
|
||||
handler, err := DefaultHandler(serviceName)
|
||||
if err != nil {
|
||||
if strings.Contains(out, "disabled") {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
result, _ := handler.IsExists()
|
||||
if result.IsExists {
|
||||
return true, nil
|
||||
} else {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
func handlerErr(out string, err error) error {
|
||||
func Start(serviceName string) error {
|
||||
handler, _ := DefaultHandler(serviceName)
|
||||
result, err := handler.StartService()
|
||||
if err != nil {
|
||||
if out != "" {
|
||||
return errors.New(out)
|
||||
}
|
||||
return err
|
||||
global.LOG.Errorf("Service start failed: %v | Output: %s", err, result.Output)
|
||||
return fmt.Errorf("start failed: %v | Output: %s", err, result.Output)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Stop(serviceName string) error {
|
||||
handler, err := DefaultHandler(serviceName)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("Stop handler init failed: %v", err)
|
||||
return fmt.Errorf("%s is not exist", serviceName)
|
||||
}
|
||||
result, err := handler.StopService()
|
||||
if err != nil {
|
||||
global.LOG.Errorf("Service stop failed: %v", err)
|
||||
return fmt.Errorf("stop failed: %v | Output: %s", err, result.Output)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Restart(serviceName string) error {
|
||||
out, err := RunSystemCtl("restart", serviceName)
|
||||
return handlerErr(out, err)
|
||||
handler, err := DefaultHandler(serviceName)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("Restart handler init failed: %v", err)
|
||||
return fmt.Errorf("%s is not exist", serviceName)
|
||||
}
|
||||
result, err := handler.RestartService()
|
||||
if err != nil {
|
||||
global.LOG.Errorf("Service restart failed: %v", err)
|
||||
return fmt.Errorf("restart failed: %v | Output: %s", err, result.Output)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Operate(operate, serviceName string) error {
|
||||
out, err := RunSystemCtl(operate, serviceName)
|
||||
return handlerErr(out, err)
|
||||
func SafeRestart(service string, configPaths []string) error {
|
||||
for _, path := range configPaths {
|
||||
if !FileExist(path) {
|
||||
global.LOG.Errorf("Config file missing: %s", path)
|
||||
return fmt.Errorf("config file missing: %s", path)
|
||||
}
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if _, err := executeCommand(ctx, "check", service); err != nil {
|
||||
global.LOG.Errorf("Config test failed: %v", err)
|
||||
return fmt.Errorf("config test failed: %w", err)
|
||||
}
|
||||
|
||||
if err := Restart(service); err != nil {
|
||||
global.LOG.Errorf("SafeRestart failed: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
isActive, _, err := Status(service)
|
||||
if err != nil || !isActive {
|
||||
global.LOG.Error("Service not active after safe restart")
|
||||
return fmt.Errorf("service not active after restart")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func Enable(serviceName string) error {
|
||||
handler, err := DefaultHandler(serviceName)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("Enable handler init failed: %v", err)
|
||||
return fmt.Errorf("%s is not exist", serviceName)
|
||||
}
|
||||
result, err := handler.EnableService()
|
||||
if err != nil {
|
||||
global.LOG.Errorf("Service enable failed: %v | Output: %s", err, result.Output)
|
||||
return fmt.Errorf("%s enable failed: %v ", serviceName, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Disable(serviceName string) error {
|
||||
handler, _ := DefaultHandler(serviceName)
|
||||
result, err := handler.DisableService()
|
||||
if err != nil {
|
||||
global.LOG.Errorf("Service disable failed: %v", err)
|
||||
return fmt.Errorf("disable failed: %v | Output: %s", err, result.Output)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Status(serviceName string) (isActive bool, isEnabled bool, err error) {
|
||||
handler, err := DefaultHandler(serviceName)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("Status handler init failed: %v", err)
|
||||
return false, false, fmt.Errorf("%s is not exist", serviceName)
|
||||
}
|
||||
status, err := handler.CheckStatus()
|
||||
if err != nil {
|
||||
global.LOG.Errorf("Status check failed: %v", err)
|
||||
return false, false, fmt.Errorf("status check failed: %v | Output: %s", err, status.Output)
|
||||
}
|
||||
return status.IsActive, status.IsEnabled, nil
|
||||
}
|
||||
|
||||
func IsActive(serviceName string) (bool, error) {
|
||||
handler, err := DefaultHandler(serviceName)
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
status, err := handler.IsActive()
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
return status.IsActive, nil
|
||||
}
|
||||
|
||||
func IsEnable(serviceName string) (bool, error) {
|
||||
handler, err := DefaultHandler(serviceName)
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
status, err := handler.IsEnabled()
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
return status.IsEnabled, nil
|
||||
}
|
||||
|
||||
type LogOption struct {
|
||||
TailLines string
|
||||
}
|
||||
|
||||
func ViewLog(path string, opt LogOption) (string, error) {
|
||||
if !FileExist(path) {
|
||||
return "", fmt.Errorf("log file not found: %s", path)
|
||||
}
|
||||
args := []string{"-n", opt.TailLines, path}
|
||||
if opt.TailLines == "+1" {
|
||||
args = []string{"-n", "1", path}
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
output, err := executeCommand(ctx, "tail", args...)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("tail failed: %w | Output: %s", err, string(output))
|
||||
}
|
||||
return string(output), nil
|
||||
}
|
||||
|
||||
func FileExist(path string) bool {
|
||||
_, err := os.Stat(path)
|
||||
return err == nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package toolbox
|
|||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
|
|
@ -22,15 +23,15 @@ type FirewallClient interface {
|
|||
}
|
||||
|
||||
func NewFail2Ban() (*Fail2ban, error) {
|
||||
isExist, _ := systemctl.IsExist("fail2ban.service")
|
||||
isExist, _ := systemctl.IsExist("fail2ban")
|
||||
if isExist {
|
||||
if _, err := os.Stat(defaultPath); err != nil {
|
||||
if err := initLocalFile(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stdout, err := cmd.Exec("systemctl restart fail2ban.service")
|
||||
err := systemctl.Restart("fail2ban")
|
||||
if err != nil {
|
||||
global.LOG.Errorf("restart fail2ban failed, err: %s", stdout)
|
||||
global.LOG.Errorf("restart fail2ban failed, err: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
|
@ -47,20 +48,25 @@ func (f *Fail2ban) Status() (bool, bool, bool) {
|
|||
}
|
||||
|
||||
func (f *Fail2ban) Version() string {
|
||||
stdout, err := cmd.Exec("fail2ban-client version")
|
||||
stdout, err := cmd.Exec("fail2ban-client --version")
|
||||
if err != nil {
|
||||
global.LOG.Errorf("load the fail2ban version failed, err: %s", stdout)
|
||||
return "-"
|
||||
}
|
||||
return strings.ReplaceAll(stdout, "\n", "")
|
||||
versionRe := regexp.MustCompile(`(?i)fail2ban[:\s-]*v?(\d+\.\d+\.\d+)`)
|
||||
matches := versionRe.FindStringSubmatch(stdout)
|
||||
if len(matches) > 1 {
|
||||
return matches[1]
|
||||
}
|
||||
global.LOG.Errorf("Version regex failed to match output: %s", stdout)
|
||||
return "-"
|
||||
}
|
||||
|
||||
func (f *Fail2ban) Operate(operate string) error {
|
||||
switch operate {
|
||||
case "start", "restart", "stop", "enable", "disable":
|
||||
stdout, err := cmd.Execf("systemctl %s fail2ban.service", operate)
|
||||
stdout, err := systemctl.CustomAction(operate, "fail2ban")
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s the fail2ban.service failed, err: %s", operate, stdout)
|
||||
return fmt.Errorf("%s the fail2ban failed, err: %s", operate, stdout.Output)
|
||||
}
|
||||
return nil
|
||||
case "reload":
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import (
|
|||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"os"
|
||||
"os/user"
|
||||
"path"
|
||||
|
|
@ -12,6 +11,8 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
|
|
@ -94,9 +95,9 @@ func (f *Ftp) Status() (bool, bool) {
|
|||
func (f *Ftp) Operate(operate string) error {
|
||||
switch operate {
|
||||
case "start", "restart", "stop":
|
||||
stdout, err := cmd.Execf("systemctl %s pure-ftpd.service", operate)
|
||||
stdout, err := systemctl.CustomAction(operate, "pure-ftpd")
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s the pure-ftpd.service failed, err: %s", operate, stdout)
|
||||
return fmt.Errorf("%s the pure-ftpd service failed, err: %s", operate, stdout.Output)
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -58,8 +58,15 @@ var restoreCmd = &cobra.Command{
|
|||
geoPath := path.Join(global.CONF.System.BaseDir, "1panel/geo")
|
||||
_, _ = cmdUtils.Execf("mkdir %s && cp %s %s/", geoPath, path.Join(tmpPath, "GeoIP.mmdb"), geoPath)
|
||||
fmt.Println(i18n.GetMsgByKeyForCmd("RestoreStep3"))
|
||||
if err := common.CopyFile(path.Join(tmpPath, "1panel.service"), "/etc/systemd/system"); err != nil {
|
||||
return err
|
||||
if isSystemd() {
|
||||
if err := common.CopyFile(path.Join(tmpPath, "1panel.service"), "/etc/systemd/system"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
} else {
|
||||
if err := common.CopyFile(path.Join(tmpPath, "1paneld"), "/etc/init.d"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
fmt.Println(i18n.GetMsgByKeyForCmd("RestoreStep4"))
|
||||
checkPointOfWal()
|
||||
|
|
@ -124,3 +131,10 @@ func handleUnTar(sourceFile, targetDir string) error {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func isSystemd() bool {
|
||||
if _, err := os.Stat("/etc/systemd/system"); err == nil {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue