feat: complete Windows client control and recovery paths

This commit is contained in:
2026-09-06 13:37:32 +00:00
parent 56b15c7f4f
commit 486894557d
38 changed files with 2188 additions and 106 deletions
@@ -10,6 +10,7 @@ import (
"time"
"golang.org/x/sys/windows"
"golang.org/x/sys/windows/registry"
"golang.org/x/sys/windows/svc"
"golang.org/x/sys/windows/svc/mgr"
)
@@ -67,6 +68,9 @@ func installNative(spec InstallSpec) error {
if err := service.Start(); err != nil && !errors.Is(err, windows.ERROR_SERVICE_ALREADY_RUNNING) {
return fmt.Errorf("start %s service: %w", Name, err)
}
if err := registerTray(spec); err != nil {
return fmt.Errorf("register per-user tray: %w", err)
}
return nil
}
@@ -75,6 +79,7 @@ func serviceImage(spec InstallSpec) string {
}
func uninstallNative() error {
var serviceErr error
manager, err := connect()
if err != nil {
return fmt.Errorf("connect to service control manager: %w", err)
@@ -82,17 +87,79 @@ func uninstallNative() error {
defer manager.Disconnect()
service, err := manager.OpenService(Name)
if errors.Is(err, windows.ERROR_SERVICE_DOES_NOT_EXIST) {
return nil
serviceErr = nil
} else if err != nil {
return fmt.Errorf("open %s service: %w", Name, err)
} else {
defer service.Close()
if err := stopService(service, 30*time.Second); err != nil {
return err
}
if err := service.Delete(); err != nil && !errors.Is(err, windows.ERROR_SERVICE_MARKED_FOR_DELETE) {
return fmt.Errorf("delete %s service: %w", Name, err)
}
}
if err := removeTrayRegistration(); err != nil {
return fmt.Errorf("remove per-user tray: %w", err)
}
return serviceErr
}
func configureNative(startup StartupMode) error {
if startup != StartupAutomatic && startup != StartupManual {
return ErrInvalidInstallSpec
}
manager, err := connect()
if err != nil {
return fmt.Errorf("connect to service control manager: %w", err)
}
defer manager.Disconnect()
service, err := manager.OpenService(Name)
if errors.Is(err, windows.ERROR_SERVICE_DOES_NOT_EXIST) {
return fmt.Errorf("%s service is not installed", Name)
}
if err != nil {
return fmt.Errorf("open %s service: %w", Name, err)
}
defer service.Close()
if err := stopService(service, 30*time.Second); err != nil {
configuration, err := service.Config()
if err != nil {
return fmt.Errorf("query %s service configuration: %w", Name, err)
}
if startup == StartupAutomatic {
configuration.StartType = mgr.StartAutomatic
} else {
configuration.StartType = mgr.StartManual
}
if err := service.UpdateConfig(configuration); err != nil {
return fmt.Errorf("update %s startup type: %w", Name, err)
}
return nil
}
const trayRunValue = "RVBoxTray"
func registerTray(spec InstallSpec) error {
key, _, err := registry.CreateKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows\CurrentVersion\Run`, registry.SET_VALUE)
if err != nil {
return err
}
if err := service.Delete(); err != nil && !errors.Is(err, windows.ERROR_SERVICE_MARKED_FOR_DELETE) {
return fmt.Errorf("delete %s service: %w", Name, err)
defer key.Close()
image := syscall.EscapeArg(spec.ExecutablePath) + " --tray --config " + syscall.EscapeArg(spec.ConfigPath)
return key.SetStringValue(trayRunValue, image)
}
func removeTrayRegistration() error {
key, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows\CurrentVersion\Run`, registry.SET_VALUE)
if errors.Is(err, registry.ErrNotExist) {
return nil
}
if err != nil {
return err
}
defer key.Close()
if err := key.DeleteValue(trayRunValue); err != nil && !errors.Is(err, registry.ErrNotExist) {
return err
}
return nil
}
@@ -138,6 +205,13 @@ func stopNative(timeoutSeconds uint32) error {
return stopService(service, timeout)
}
func restartNative(timeoutSeconds uint32) error {
if err := stopNative(timeoutSeconds); err != nil {
return err
}
return startNative()
}
func stopService(service *mgr.Service, timeout time.Duration) error {
status, err := service.Query()
if err != nil {