//go:build darwin package service import ( "fmt" "os" "os/exec" "path/filepath" "strings" ) func newPlatformManager(configDir string) (platformManager, error) { return &macosServiceManager{configDir: configDir}, nil } // macosServiceManager manages services via launchd. Direct port of // MacOSServiceManager in service_manager.py. Unlike the Python version // (which shells out to a separate macos_service_wrapper.py for DNS // handling), the generated plist invokes this same ss binary in // "service run-macos" mode (see wrapper_darwin.go) — no second script file // is needed. type macosServiceManager struct { configDir string } func (d *macosServiceManager) launchdPath(name string) string { return filepath.Join("/Library/LaunchDaemons", "com."+name+".plist") } func (d *macosServiceManager) createLaunchdPlist(cfg Config) error { selfPath, err := os.Executable() if err != nil { return fmt.Errorf("failed to determine ss executable path: %w", err) } workingDir := filepath.Dir(cfg.ExecutablePath) programArgs := []string{ selfPath, "service", "run-macos", cfg.ExecutablePath, workingDir, } if cfg.Args != "" { extra, err := shlexSplit(cfg.Args) if err != nil { return fmt.Errorf("failed to parse service args: %w", err) } programArgs = append(programArgs, extra...) } programArgsXML := "" for _, arg := range programArgs { programArgsXML += fmt.Sprintf(" %s\n", xmlEscape(arg)) } plistContent := fmt.Sprintf(` Label com.%s ProgramArguments %s RunAtLoad KeepAlive StandardOutPath /var/log/%s.log StandardErrorPath /var/log/%s.error.log `, cfg.Name, programArgsXML, cfg.Name, cfg.Name) plistPath := d.launchdPath(cfg.Name) if err := os.WriteFile(plistPath, []byte(plistContent), 0o644); err != nil { return fmt.Errorf("failed to create launchd plist: %w", err) } return nil } var xmlReplacer = strings.NewReplacer( "&", "&", "<", "<", ">", ">", `"`, """, "'", "'", ) func xmlEscape(s string) string { return xmlReplacer.Replace(s) } func (d *macosServiceManager) Install(cfg Config) error { if err := d.createLaunchdPlist(cfg); err != nil { return err } plistPath := d.launchdPath(cfg.Name) if out, err := exec.Command("launchctl", "load", plistPath).CombinedOutput(); err != nil { return fmt.Errorf("failed to install service: %s", out) } if out, err := exec.Command("launchctl", "start", "com."+cfg.Name).CombinedOutput(); err != nil { return fmt.Errorf("failed to install service: %s", out) } return nil } func (d *macosServiceManager) Uninstall(name string) error { exec.Command("launchctl", "stop", "com."+name).Run() plistPath := d.launchdPath(name) if _, err := os.Stat(plistPath); err == nil { if out, err := exec.Command("launchctl", "unload", plistPath).CombinedOutput(); err != nil { return fmt.Errorf("failed to uninstall service: %s", out) } os.Remove(plistPath) } return nil } func (d *macosServiceManager) Start(name string) error { if out, err := exec.Command("launchctl", "start", "com."+name).CombinedOutput(); err != nil { return fmt.Errorf("failed to start service: %s", out) } return nil } func (d *macosServiceManager) Stop(name string) error { if out, err := exec.Command("launchctl", "stop", "com."+name).CombinedOutput(); err != nil { return fmt.Errorf("failed to stop service: %s", out) } return nil } func (d *macosServiceManager) Restart(name string) error { exec.Command("launchctl", "stop", "com."+name).Run() if out, err := exec.Command("launchctl", "start", "com."+name).CombinedOutput(); err != nil { return fmt.Errorf("failed to restart service: %s", out) } return nil }