1Panel/backend/utils/systemctl/handle.go
巴山夜语 79020abb1c
feat(systemctl): implement service manager initialization and command execution (#8380)
* feat(systemctl): 实现服务管理器初始化和命令执行

- 新增 systemctl 包,实现对 systemd、openrc 和 sysvinit 三种服务管理器的支持
- 添加服务状态检查、启动、停止、重启和启用/禁用功能
- 实现服务发现和智能服务名处理
- 添加配置文件查看功能
- 优化错误处理和日志记录

* refactor(system): 重构系统服务管理逻辑

- 引入 systemctl 工具包以统一处理系统服务
- 优化服务状态获取、配置文件路径解析等逻辑
- 重构 HostToolService 中的 GetToolStatus 方法
- 更新 DockerService、SettingService 等相关服务的处理方式
- 调整快照创建和恢复过程中的服务处理逻辑

* feat(utils): 添加目录复制功能并优化文件复制逻辑

- 新增 CopyDirs 函数,用于复制整个目录及其内容
- 添加对符号链接的复制支持
- 实现通用的 Copy 函数,根据文件类型自动选择 CopyFile 或 CopyDirs
- 在 CopyFile 函数中增加对源文件是目录的检查和错误提示

* refactortoolbox: 重构 Fail2ban 和 Pure-FTPd 的管理逻辑

- 优化了 Fail2ban 和 Pure-FTPd 的启动、停止、重启等操作的实现
- 改进了 Fail2ban 版本信息的获取方法
- 统一了错误处理和日志记录的格式
- 调整了部分导入的包,提高了代码的可维护性

* build: 禁用 CGO 以提高构建性能和兼容性

- 在 Linux 后端构建命令中添加 CGO_ENABLED=0 环境变量
- 此修改可以提高构建速度,并确保生成的二进制文件在没有 C 库依赖的环境中也能运行

* refactor(docker): 重构 Docker 服务的重启和操作逻辑

- 添加 isDockerSnapInstalled 函数来判断 Docker 是否通过 Snap 安装
- 在 OperateDocker 和 restartDocker 函数中增加对 Snap 安装的处理
- 移除未使用的 getDockerRestartCommand 函数

* fix(service): 优化快照恢复后的服务重启逻辑

- 在使用 systemd 管理服务时,增加 daemon-reload 操作以确保服务配置更新
- 重启 1panel 服务,以应用快照恢复的更改

* refactor(server): 支持非 systemd 系统的恢复操作

- 增加 isSystemd 函数判断系统是否为 systemd 类型
- 根据系统类型选择性地恢复服务文件
- 兼容 systemd 和非 systemd 系统的恢复流程

* fix(upgrade): 优化升级过程中的服务重启逻辑

- 移动服务重启逻辑到版本号更新之后,修复因提前重启导致的版本号未更新BUG。
- 在 systemctl 重启之前添加 daemon-reload 命令

---------

Co-authored-by: gcsong023 <gcsong023@users.noreply.github.com>
2025-04-17 10:26:13 +08: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(), 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
)