mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-09-24 08:00:57 +00:00
* feat(systemctl): 实现服务管理器初始化和命令执行 - 新增 systemctl 包,实现对 systemd、openrc 和 sysvinit 三种服务管理器的支持 - 添加服务状态检查、启动、停止、重启和启用/禁用功能 - 实现服务发现和智能服务名处理 - 添加配置文件查看功能 - 优化错误处理和日志记录 * refactor(system): 重构系统服务管理逻辑 - 引入 systemctl 工具包以统一处理系统服务 - 优化服务状态获取、配置文件路径解析等逻辑 - 重构 HostToolService 中的 GetToolStatus 方法 - 更新 DockerService、SettingService 等相关服务的处理方式 - 调整快照创建和恢复过程中的服务处理逻辑 * feat(utils): 添加目录复制功能并优化文件复制逻辑 - 新增 CopyDirs 函数,用于复制整个目录及其内容 - 添加对符号链接的复制支持 - 实现通用的 Copy 函数,根据文件类型自动选择 CopyFile 或 CopyDirs - 在 CopyFile 函数中增加对源文件是目录的检查和错误提示 * refactortoolbox: 重构 Fail2ban 和 Pure-FTPd 的管理逻辑 - 优化了 Fail2ban 和 Pure-FTPd 的启动、停止、重启等操作的实现 - 改进了 Fail2ban 版本信息的获取方法 - 统一了错误处理和日志记录的格式 - 调整了部分导入的包,提高了代码的可维护性 * build: 禁用 CGO 以提高构建性能和兼容性 - 在 Linux 后端构建命令中添加 CGO_ENABLED=0 环境变量 - 此修改可以提高构建速度,并确保生成的二进制文件在没有 C 库依赖的环境中也能运行 * refactor(docker): 重构 Docker 服务的重启和操作逻辑 - 添加 isDockerSnapInstalled 函数来判断 Docker 是否通过 Snap 安装 - 在 OperateDocker 和 restartDocker 函数中增加对 Snap 安装的处理 - 移除未使用的 getDockerRestartCommand 函数 * fix(service): 优化快照恢复后的服务重启逻辑 - 在使用 systemd 管理服务时,增加 daemon-reload 操作以确保服务配置更新 - 重启 1panel 服务,以应用快照恢复的更改 * refactor(server): 支持非 systemd 系统的恢复操作 - 增加 isSystemd 函数判断系统是否为 systemd 类型 - 根据系统类型选择性地恢复服务文件 - 兼容 systemd 和非 systemd 系统的恢复流程 * fix(upgrade): 优化升级过程中的服务重启逻辑 - 移动服务重启逻辑到版本号更新之后,修复因提前重启导致的版本号未更新BUG。 - 在 systemctl 重启之前添加 daemon-reload 命令 --------- Co-authored-by: gcsong023 <gcsong023@users.noreply.github.com>
410 lines
11 KiB
Go
410 lines
11 KiB
Go
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
|
|
)
|