feat: complete Windows client control and recovery paths
This commit is contained in:
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user