hostSync/service/darwin.go

249 lines
6.1 KiB
Go

//go:build darwin
package service
import (
"fmt"
"os"
"os/exec"
"os/user"
"path/filepath"
"strings"
)
// NewServiceManager 创建macOS服务管理器实例
func NewServiceManager() ServiceManager {
return &DarwinServiceManager{}
}
// DarwinServiceManager macOS LaunchD服务管理器
type DarwinServiceManager struct{}
func (m *DarwinServiceManager) Install(execPath string) error {
// 检查是否有足够权限操作系统服务
if err := m.checkPermissions(); err != nil {
return err
}
if err := ensureLogDir(); err != nil {
return fmt.Errorf("创建日志目录失败: %v", err)
}
plistContent := m.generateLaunchDPlist(execPath)
// 获取plist文件路径
plistPath, err := m.getPlistPath()
if err != nil {
return fmt.Errorf("获取plist路径失败: %v", err)
}
// 创建目录
if err := os.MkdirAll(filepath.Dir(plistPath), 0755); err != nil {
return fmt.Errorf("创建plist目录失败: %v", err)
}
// 写入plist文件
if err := os.WriteFile(plistPath, []byte(plistContent), 0644); err != nil {
return fmt.Errorf("写入plist文件失败: %v", err)
}
// 加载服务
cmd := exec.Command("launchctl", "load", plistPath)
if output, err := cmd.CombinedOutput(); err != nil {
return fmt.Errorf("加载服务失败: %v, 输出: %s", err, string(output))
}
return nil
}
func (m *DarwinServiceManager) Uninstall() error {
// 停止服务
m.Stop()
// 获取plist文件路径
plistPath, err := m.getPlistPath()
if err != nil {
return fmt.Errorf("获取plist路径失败: %v", err)
}
// 卸载服务
cmd := exec.Command("launchctl", "unload", plistPath)
cmd.CombinedOutput() // 忽略错误
// 删除plist文件
if err := os.Remove(plistPath); err != nil && !os.IsNotExist(err) {
return fmt.Errorf("删除plist文件失败: %v", err)
}
return nil
}
func (m *DarwinServiceManager) Start() error {
serviceName := m.getServiceIdentifier()
cmd := exec.Command("launchctl", "start", serviceName)
output, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("启动服务失败: %v, 输出: %s", err, string(output))
}
return nil
}
func (m *DarwinServiceManager) Stop() error {
serviceName := m.getServiceIdentifier()
cmd := exec.Command("launchctl", "stop", serviceName)
output, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("停止服务失败: %v, 输出: %s", err, string(output))
}
return nil
}
func (m *DarwinServiceManager) Restart() error {
if err := m.Stop(); err != nil {
return err
}
return m.Start()
}
func (m *DarwinServiceManager) Status() (ServiceStatus, error) {
serviceName := m.getServiceIdentifier()
// 检查plist文件是否存在
plistPath, err := m.getPlistPath()
if err != nil {
return StatusUnknown, err
}
if _, err := os.Stat(plistPath); os.IsNotExist(err) {
return StatusNotInstalled, nil
}
cmd := exec.Command("launchctl", "list", serviceName)
output, err := cmd.CombinedOutput()
if err != nil {
// 如果服务不在列表中,可能是未运行
return StatusStopped, nil
}
// 简单检查输出中是否包含PID
outputStr := string(output)
if strings.Contains(outputStr, serviceName) && !strings.Contains(outputStr, "-") {
return StatusRunning, nil
}
return StatusStopped, nil
}
func (m *DarwinServiceManager) getServiceIdentifier() string {
return fmt.Sprintf("com.deepwn.%s", strings.ToLower(getServiceName()))
}
func (m *DarwinServiceManager) getPlistPath() (string, error) {
serviceName := m.getServiceIdentifier()
// 检查是否以root权限运行
currentUser, err := user.Current()
if err != nil {
return "", err
}
if currentUser.Uid == "0" {
// 系统级服务
return fmt.Sprintf("/Library/LaunchDaemons/%s.plist", serviceName), nil
} else {
// 用户级服务
homeDir, err := os.UserHomeDir()
if err != nil {
return "", err
}
return fmt.Sprintf("%s/Library/LaunchAgents/%s.plist", homeDir, serviceName), nil
}
}
func (m *DarwinServiceManager) generateLaunchDPlist(execPath string) string {
serviceName := m.getServiceIdentifier()
logPath := getLogPath()
// 获取用户配置目录
userConfigDir, err := getUserConfigDir()
if err != nil {
// 如果获取失败,使用默认路径
currentUser, _ := user.Current()
if currentUser != nil {
userConfigDir = filepath.Join(currentUser.HomeDir, ".hostsync")
} else {
userConfigDir = "/Users/Shared/.hostsync"
}
}
return fmt.Sprintf(`<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>%s</string>
<key>ProgramArguments</key>
<array>
<string>%s</string>
<string>service</string>
<string>run</string>
<string>--config</string>
<string>%s</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>%s</string>
<key>StandardErrorPath</key>
<string>%s</string>
</dict>
</plist>
`, serviceName, execPath, userConfigDir, logPath, logPath)
}
// checkPermissions 检查是否有足够权限操作系统服务
func (m *DarwinServiceManager) checkPermissions() error {
// 检查是否为 root 用户
if os.Getuid() != 0 {
return fmt.Errorf("安装系统服务需要 root 权限,请使用 sudo 运行")
}
// 检查 launchctl 命令是否可用
if _, err := exec.LookPath("launchctl"); err != nil {
return fmt.Errorf("launchctl 命令不可用: %v", err)
}
// 获取 plist 文件路径并检查目录权限
plistPath, err := m.getPlistPath()
if err != nil {
return fmt.Errorf("获取 plist 路径失败: %v", err)
}
plistDir := filepath.Dir(plistPath)
// 检查目录是否存在,如果不存在尝试创建来测试权限
if _, err := os.Stat(plistDir); os.IsNotExist(err) {
if err := os.MkdirAll(plistDir, 0755); err != nil {
return fmt.Errorf("无法创建 plist 目录 %s: %v", plistDir, err)
}
}
// 尝试创建一个测试文件来验证写权限
testFile := filepath.Join(plistDir, ".hostsync-permission-test")
if file, err := os.Create(testFile); err != nil {
return fmt.Errorf("无法写入 plist 目录 %s: %v", plistDir, err)
} else {
file.Close()
os.Remove(testFile) // 清理测试文件
}
return nil
}