diff --git a/Makefile b/Makefile index 39be2c76e..78b8fca34 100644 --- a/Makefile +++ b/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) \ diff --git a/backend/app/service/clam.go b/backend/app/service/clam.go index cb1b82a15..f312454d5 100644 --- a/backend/app/service/clam.go +++ b/backend/app/service/clam.go @@ -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 "" } diff --git a/backend/app/service/device.go b/backend/app/service/device.go index 8fcf8a8d7..33fcb79c3 100644 --- a/backend/app/service/device.go +++ b/backend/app/service/device.go @@ -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) } diff --git a/backend/app/service/device_clean.go b/backend/app/service/device_clean.go index f6dfd53a4..1a62235f1 100644 --- a/backend/app/service/device_clean.go +++ b/backend/app/service/device_clean.go @@ -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) } diff --git a/backend/app/service/docker.go b/backend/app/service/docker.go index 29257da9d..55d4e847e 100644 --- a/backend/app/service/docker.go +++ b/backend/app/service/docker.go @@ -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") } diff --git a/backend/app/service/host_tool.go b/backend/app/service/host_tool.go index b40bb038c..618207b29 100644 --- a/backend/app/service/host_tool.go +++ b/backend/app/service/host_tool.go @@ -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) { diff --git a/backend/app/service/setting.go b/backend/app/service/setting.go index d0a1f51f6..9ddbc9fa1 100644 --- a/backend/app/service/setting.go +++ b/backend/app/service/setting.go @@ -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 diff --git a/backend/app/service/snapshot_create.go b/backend/app/service/snapshot_create.go index c5979c77d..7deb2a637 100644 --- a/backend/app/service/snapshot_create.go +++ b/backend/app/service/snapshot_create.go @@ -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) diff --git a/backend/app/service/snapshot_recover.go b/backend/app/service/snapshot_recover.go index 6d835a888..131be4a65 100644 --- a/backend/app/service/snapshot_recover.go +++ b/backend/app/service/snapshot_recover.go @@ -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 { diff --git a/backend/app/service/ssh.go b/backend/app/service/ssh.go index d8f7a9db0..df7df89a7 100644 --- a/backend/app/service/ssh.go +++ b/backend/app/service/ssh.go @@ -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 { diff --git a/backend/app/service/upgrade.go b/backend/app/service/upgrade.go index 144d11ad4..e6f833da6 100644 --- a/backend/app/service/upgrade.go +++ b/backend/app/service/upgrade.go @@ -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) } } diff --git a/backend/utils/common/common.go b/backend/utils/common/common.go index 9ce53df07..35c486316 100644 --- a/backend/utils/common/common.go +++ b/backend/utils/common/common.go @@ -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, ".") diff --git a/backend/utils/systemctl/configloade.go b/backend/utils/systemctl/configloade.go new file mode 100644 index 000000000..022bb569c --- /dev/null +++ b/backend/utils/systemctl/configloade.go @@ -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 +} diff --git a/backend/utils/systemctl/handle.go b/backend/utils/systemctl/handle.go new file mode 100644 index 000000000..47c81b7ae --- /dev/null +++ b/backend/utils/systemctl/handle.go @@ -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 +) diff --git a/backend/utils/systemctl/managers.go b/backend/utils/systemctl/managers.go new file mode 100644 index 000000000..2c6b3f778 --- /dev/null +++ b/backend/utils/systemctl/managers.go @@ -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 +} diff --git a/backend/utils/systemctl/systemctl.go b/backend/utils/systemctl/systemctl.go index 0f7ef19db..6f1ea2aa2 100644 --- a/backend/utils/systemctl/systemctl.go +++ b/backend/utils/systemctl/systemctl.go @@ -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 } diff --git a/backend/utils/toolbox/fail2ban.go b/backend/utils/toolbox/fail2ban.go index ce7f9c715..08848780a 100644 --- a/backend/utils/toolbox/fail2ban.go +++ b/backend/utils/toolbox/fail2ban.go @@ -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": diff --git a/backend/utils/toolbox/pure-ftpd.go b/backend/utils/toolbox/pure-ftpd.go index e4056c85b..0aeed6bea 100644 --- a/backend/utils/toolbox/pure-ftpd.go +++ b/backend/utils/toolbox/pure-ftpd.go @@ -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: diff --git a/cmd/server/cmd/restore.go b/cmd/server/cmd/restore.go index b41c98eef..ef9f7769c 100644 --- a/cmd/server/cmd/restore.go +++ b/cmd/server/cmd/restore.go @@ -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 +}