hostSync/service/linux.go

290 lines
8.2 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.

//go:build linux
package service
import (
"fmt"
"os"
"os/exec"
"strings"
)
// NewServiceManager 创建服务管理器 (Linux)
func NewServiceManager() ServiceManager {
return &LinuxServiceManager{}
}
// LinuxServiceManager Linux systemd服务管理器
type LinuxServiceManager struct{}
func (m *LinuxServiceManager) Install(execPath string) error {
// 检查是否有足够权限操作系统服务
if err := m.checkPermissions(); err != nil {
return err
}
if err := ensureLogDir(); err != nil {
return fmt.Errorf("创建日志目录失败: %v", err)
}
serviceName := getServiceName()
serviceContent := m.generateSystemdUnit(execPath)
// 写入systemd服务文件
servicePath := fmt.Sprintf("/etc/systemd/system/%s.service", strings.ToLower(serviceName))
if err := os.WriteFile(servicePath, []byte(serviceContent), 0644); err != nil {
return fmt.Errorf("写入服务文件失败: %v", err)
}
// 处理 SELinux 上下文,确保可执行文件可以被 systemd 执行
if err := m.handleSELinuxContext(execPath); err != nil {
// SELinux 处理失败不算致命错误,只记录警告
fmt.Printf("警告: SELinux 上下文设置失败: %v\n", err)
}
// 重新加载systemd配置
cmd := exec.Command("systemctl", "daemon-reload")
if output, err := cmd.CombinedOutput(); err != nil {
return fmt.Errorf("重新加载systemd配置失败: %v, 输出: %s", err, string(output))
}
// 启用服务自动启动
cmd = exec.Command("systemctl", "enable", strings.ToLower(serviceName))
if output, err := cmd.CombinedOutput(); err != nil {
return fmt.Errorf("启用服务自动启动失败: %v, 输出: %s", err, string(output))
}
return nil
}
func (m *LinuxServiceManager) Uninstall() error {
serviceName := strings.ToLower(getServiceName())
// 停止服务
m.Stop()
// 禁用服务
cmd := exec.Command("systemctl", "disable", serviceName)
cmd.CombinedOutput() // 忽略错误
// 删除服务文件
servicePath := fmt.Sprintf("/etc/systemd/system/%s.service", serviceName)
if err := os.Remove(servicePath); err != nil && !os.IsNotExist(err) {
return fmt.Errorf("删除服务文件失败: %v", err)
}
// 重新加载systemd配置
cmd = exec.Command("systemctl", "daemon-reload")
cmd.CombinedOutput() // 忽略错误
return nil
}
func (m *LinuxServiceManager) Start() error {
serviceName := strings.ToLower(getServiceName())
cmd := exec.Command("systemctl", "start", serviceName)
output, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("启动服务失败: %v, 输出: %s", err, string(output))
}
return nil
}
func (m *LinuxServiceManager) Stop() error {
serviceName := strings.ToLower(getServiceName())
cmd := exec.Command("systemctl", "stop", serviceName)
output, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("停止服务失败: %v, 输出: %s", err, string(output))
}
return nil
}
func (m *LinuxServiceManager) Restart() error {
serviceName := strings.ToLower(getServiceName())
cmd := exec.Command("systemctl", "restart", serviceName)
output, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("重启服务失败: %v, 输出: %s", err, string(output))
}
return nil
}
func (m *LinuxServiceManager) Status() (ServiceStatus, error) {
serviceName := strings.ToLower(getServiceName())
// 检查服务是否存在
servicePath := fmt.Sprintf("/etc/systemd/system/%s.service", serviceName)
if _, err := os.Stat(servicePath); os.IsNotExist(err) {
return StatusNotInstalled, nil
}
cmd := exec.Command("systemctl", "is-active", serviceName)
output, err := cmd.CombinedOutput()
outputStr := strings.TrimSpace(string(output))
// 处理不同的退出状态
if err != nil {
if exitError, ok := err.(*exec.ExitError); ok {
// systemctl is-active 的退出码含义:
// 0: active, 1: inactive, 3: activating/deactivating
switch exitError.ExitCode() {
case 1:
// 服务未运行
switch outputStr {
case "inactive":
return StatusStopped, nil
case "failed":
return StatusStopped, nil
default:
return StatusStopped, nil
}
case 3:
// 服务正在启动或停止中
switch outputStr {
case "activating":
return StatusRunning, nil // 启动中视为运行状态
case "deactivating":
return StatusStopped, nil // 停止中视为停止状态
default:
return StatusStopped, nil // 其他转换状态默认为停止
}
default:
if strings.Contains(outputStr, "could not be found") {
return StatusNotInstalled, nil
}
return StatusUnknown, fmt.Errorf("获取服务状态失败: %v, 输出: %s", err, outputStr)
}
} else {
return StatusUnknown, fmt.Errorf("执行systemctl命令失败: %v", err)
}
}
// 没有错误的情况下服务应该是active状态
switch outputStr {
case "active":
return StatusRunning, nil
case "activating":
return StatusRunning, nil // 正在启动中,视为运行状态
case "inactive":
return StatusStopped, nil
case "failed":
return StatusStopped, nil
case "deactivating":
return StatusStopped, nil // 正在停止中,视为停止状态
default:
return StatusUnknown, fmt.Errorf("未知的服务状态: %s", outputStr)
}
}
func (m *LinuxServiceManager) generateSystemdUnit(execPath string) string {
description := getServiceDescription()
// 获取用户配置目录
userConfigDir, err := getUserConfigDir()
if err != nil {
// 如果获取失败,使用默认路径
userConfigDir = "/root/.hostsync"
}
return fmt.Sprintf(`[Unit]
Description=%s
After=network.target network-online.target
Wants=network-online.target
[Service]
Type=simple
ExecStart=%s service run -c %s
Restart=always
RestartSec=10
User=root
Group=root
StandardOutput=journal
StandardError=journal
SyslogIdentifier=hostsync
Environment=PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
[Install]
WantedBy=multi-user.target
`, description, execPath, userConfigDir)
}
// checkPermissions 检查是否有足够权限操作系统服务
func (m *LinuxServiceManager) checkPermissions() error {
// 检查是否为 root 用户
if os.Getuid() != 0 {
return fmt.Errorf("安装系统服务需要 root 权限,请使用 sudo 运行")
}
// 检查 /etc/systemd/system 目录是否可写
testPath := "/etc/systemd/system"
if _, err := os.Stat(testPath); os.IsNotExist(err) {
return fmt.Errorf("systemd 系统目录不存在: %s", testPath)
}
// 尝试创建一个测试文件来验证写权限
testFile := fmt.Sprintf("%s/.hostsync-permission-test", testPath)
if file, err := os.Create(testFile); err != nil {
return fmt.Errorf("无法写入系统服务目录 %s: %v", testPath, err)
} else {
file.Close()
os.Remove(testFile) // 清理测试文件
}
// 检查 systemctl 命令是否可用
if _, err := exec.LookPath("systemctl"); err != nil {
return fmt.Errorf("系统未安装 systemd 或 systemctl 命令不可用: %v", err)
}
return nil
}
// handleSELinuxContext 处理 SELinux 上下文设置
func (m *LinuxServiceManager) handleSELinuxContext(execPath string) error {
// 检查是否启用了 SELinux
if !m.isSELinuxEnabled() {
return nil // SELinux 未启用,无需处理
}
// 检查 chcon 命令是否可用
if _, err := exec.LookPath("chcon"); err != nil {
return fmt.Errorf("chcon 命令不可用: %v", err)
}
// 为可执行文件设置正确的 SELinux 上下文
// bin_t 类型允许程序被 systemd 执行
cmd := exec.Command("chcon", "-t", "bin_t", execPath)
if output, err := cmd.CombinedOutput(); err != nil {
return fmt.Errorf("设置 SELinux 上下文失败: %v, 输出: %s", err, string(output))
}
return nil
}
// isSELinuxEnabled 检查 SELinux 是否启用
func (m *LinuxServiceManager) isSELinuxEnabled() bool {
// 检查 /selinux/enforce 文件是否存在
if _, err := os.Stat("/selinux/enforce"); err == nil {
return true
}
// 检查 /sys/fs/selinux/enforce 文件是否存在
if _, err := os.Stat("/sys/fs/selinux/enforce"); err == nil {
return true
}
// 尝试运行 getenforce 命令
if _, err := exec.LookPath("getenforce"); err == nil {
cmd := exec.Command("getenforce")
if output, err := cmd.CombinedOutput(); err == nil {
status := strings.TrimSpace(string(output))
return status == "Enforcing" || status == "Permissive"
}
}
return false
}