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