feat: migrate to golang
This commit is contained in:
122
internal/service/service.go
Normal file
122
internal/service/service.go
Normal file
@ -0,0 +1,122 @@
|
||||
// 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
|
||||
}
|
||||
Reference in New Issue
Block a user