123 lines
3.1 KiB
Go
123 lines
3.1 KiB
Go
// 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
|
|
}
|