// Package service provides a cross-platform interface for installing, // starting, stopping, and uninstalling mihomo as a system service — // systemd on Linux, launchd on macOS, and a native Windows Service (via // golang.org/x/sys/windows/svc) on Windows. package service import ( "fmt" "os" "strings" ) // Config describes a service to install. Mirrors service_manager.py's // pydantic ServiceConfig model. type Config struct { Name string ExecutablePath string Description string Args string } func validateConfig(cfg Config) error { name := strings.TrimSpace(cfg.Name) if name == "" { return fmt.Errorf("service name cannot be empty") } if strings.Contains(name, " ") { return fmt.Errorf("service name cannot contain spaces") } info, err := os.Stat(cfg.ExecutablePath) if err != nil { return fmt.Errorf("executable path does not exist: %s", cfg.ExecutablePath) } if info.IsDir() { return fmt.Errorf("path is not a file: %s", cfg.ExecutablePath) } if !isExecutable(info) { return fmt.Errorf("file is not executable: %s", cfg.ExecutablePath) } return nil } // platformManager is implemented once per OS (service_linux.go, // service_darwin.go, service_windows.go). type platformManager interface { Install(cfg Config) error Uninstall(name string) error Start(name string) error Stop(name string) error Restart(name string) error } // Manager is the main service manager that delegates to a platform-specific // implementation, mirroring service_manager.py's ServiceManager class. type Manager struct { configDir string impl platformManager } // New creates a Manager using the appropriate platform implementation. func New(configDir string) (*Manager, error) { impl, err := newPlatformManager(configDir) if err != nil { return nil, err } return &Manager{configDir: configDir, impl: impl}, nil } // Install installs a service with the given name and executable path. func (m *Manager) Install(name, executablePath, description, args string) error { cfg := Config{ Name: name, ExecutablePath: executablePath, Description: description, Args: args, } if err := validateConfig(cfg); err != nil { return err } return m.impl.Install(cfg) } // Uninstall uninstalls a service by name. func (m *Manager) Uninstall(name string) error { if err := requireName(name); err != nil { return err } return m.impl.Uninstall(strings.TrimSpace(name)) } // Start starts a service by name. func (m *Manager) Start(name string) error { if err := requireName(name); err != nil { return err } return m.impl.Start(strings.TrimSpace(name)) } // Stop stops a service by name. func (m *Manager) Stop(name string) error { if err := requireName(name); err != nil { return err } return m.impl.Stop(strings.TrimSpace(name)) } // Restart restarts a service by name. func (m *Manager) Restart(name string) error { if err := requireName(name); err != nil { return err } return m.impl.Restart(strings.TrimSpace(name)) } func requireName(name string) error { if strings.TrimSpace(name) == "" { return fmt.Errorf("service name cannot be empty") } return nil }