//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(` Label %s ProgramArguments %s service run --config %s RunAtLoad KeepAlive StandardOutPath %s StandardErrorPath %s `, 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 }