feat: migrate to golang
This commit is contained in:
147
internal/service/service_darwin.go
Normal file
147
internal/service/service_darwin.go
Normal file
@ -0,0 +1,147 @@
|
||||
//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(" <string>%s</string>\n", xmlEscape(arg))
|
||||
}
|
||||
|
||||
plistContent := 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>com.%s</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
%s </array>
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
<key>KeepAlive</key>
|
||||
<true/>
|
||||
<key>StandardOutPath</key>
|
||||
<string>/var/log/%s.log</string>
|
||||
<key>StandardErrorPath</key>
|
||||
<string>/var/log/%s.error.log</string>
|
||||
</dict>
|
||||
</plist>
|
||||
`, 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
|
||||
}
|
||||
Reference in New Issue
Block a user