1Panel/backend/utils/systemctl/handle.go
ssongliu 6aaf61cde4
Some checks failed
SonarCloud Scan / SonarCloud (push) Failing after 4s
fix: Increase systemctl operation timeout (#9281)
2025-06-25 08:21:22 +00:00

409 lines
11 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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(), defaultCommandTimeout)
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
)