//go:build windows package service import ( "fmt" "os" "os/exec" "strings" "golang.org/x/sys/windows" ) func newPlatformManager(configDir string) (platformManager, error) { return &windowsServiceManager{configDir: configDir}, nil } // windowsServiceManager manages services via sc.exe, with the service // binPath pointing back at this same ss executable in // "service run-windows" mode (see wrapper_windows.go) instead of a separate // pywin32-based wrapper process. Direct port of WindowsServiceManager in // service_manager.py, minus the pywin32/JSON-config-file indirection. type windowsServiceManager struct { configDir string } func isAdmin() bool { token := windows.GetCurrentProcessToken() return token.IsElevated() } // formatErrorMessage mirrors _format_error_message in service_manager.py. func formatErrorMessage(operation, serviceName, errText string) string { lower := strings.ToLower(errText) switch { case strings.Contains(lower, "access is denied") || strings.Contains(errText, "5"): return fmt.Sprintf( "Failed to %s service '%s': Access denied.\n\n"+ "Administrator privileges are required to %s Windows services.\n\n"+ "Solutions:\n"+ "• Run this program as administrator (right-click → 'Run as administrator')\n"+ "• Open an elevated Command Prompt and run the command manually\n"+ "• Ensure User Account Control (UAC) is enabled and accept the prompt", operation, serviceName, operation) case strings.Contains(errText, "1060"): return fmt.Sprintf( "Failed to %s service '%s': Service not found.\n\n"+ "The specified service does not exist. Check the service name and try again.", operation, serviceName) case strings.Contains(errText, "1062") && (operation == "stop" || operation == "restart"): return fmt.Sprintf( "Service '%s' is not currently running.\n\n"+ "This is not an error - the service was already stopped.", serviceName) default: return fmt.Sprintf("Failed to %s service '%s': %s", operation, serviceName, errText) } } // runAsAdmin runs cmd directly if already elevated, otherwise returns an // error with manual-elevation instructions. Direct port of // WindowsServiceManager._run_as_admin (service_manager.py:116-134); the // Python version doesn't self-elevate via UAC either, it only checks and // instructs. func runAsAdmin(cmdArgs []string, description string) error { if !isAdmin() { commandStr := strings.Join(cmdArgs, " ") return fmt.Errorf( "administrator privileges required to %s.\n\n"+ "Command: %s\n\n"+ "Please do one of the following:\n"+ "1. Run this program as administrator (right-click → 'Run as administrator')\n"+ "2. Open an elevated Command Prompt and run: %s\n"+ "3. Accept the UAC prompt when it appears", strings.ToLower(description), commandStr, commandStr) } out, err := exec.Command(cmdArgs[0], cmdArgs[1:]...).CombinedOutput() if err != nil { return fmt.Errorf("failed to %s: %s", strings.ToLower(description), string(out)) } return nil } func (w *windowsServiceManager) Install(cfg Config) error { selfPath, err := os.Executable() if err != nil { return fmt.Errorf("failed to determine ss executable path: %w", err) } serviceCmd := fmt.Sprintf(`"%s" service run-windows --name "%s" --bin "%s" --args "%s"`, selfPath, cfg.Name, cfg.ExecutablePath, cfg.Args) cmdArgs := []string{ "sc", "create", cfg.Name, "binPath=", serviceCmd, "start=", "auto", } if cfg.Description != "" { cmdArgs = append(cmdArgs, "DisplayName=", cfg.Description) } if err := runAsAdmin(cmdArgs, fmt.Sprintf("install service '%s'", cfg.Name)); err != nil { return fmt.Errorf("%s", formatErrorMessage("install", cfg.Name, err.Error())) } return nil } func (w *windowsServiceManager) Uninstall(name string) error { runAsAdmin([]string{"sc", "stop", name}, fmt.Sprintf("stop service '%s'", name)) if err := runAsAdmin([]string{"sc", "delete", name}, fmt.Sprintf("uninstall service '%s'", name)); err != nil { return fmt.Errorf("%s", formatErrorMessage("uninstall", name, err.Error())) } return nil } func (w *windowsServiceManager) Start(name string) error { if err := runAsAdmin([]string{"sc", "start", name}, fmt.Sprintf("start service '%s'", name)); err != nil { return fmt.Errorf("%s", formatErrorMessage("start", name, err.Error())) } return nil } func (w *windowsServiceManager) Stop(name string) error { if err := runAsAdmin([]string{"sc", "stop", name}, fmt.Sprintf("stop service '%s'", name)); err != nil { return fmt.Errorf("%s", formatErrorMessage("stop", name, err.Error())) } return nil } func (w *windowsServiceManager) Restart(name string) error { if err := runAsAdmin([]string{"sc", "stop", name}, fmt.Sprintf("stop service '%s'", name)); err != nil { if !strings.Contains(strings.ToLower(err.Error()), "1062") { return fmt.Errorf("%s", formatErrorMessage("restart", name, err.Error())) } } if err := runAsAdmin([]string{"sc", "start", name}, fmt.Sprintf("start service '%s'", name)); err != nil { return fmt.Errorf("%s", formatErrorMessage("restart", name, err.Error())) } return nil }