feat: execute durable client commands through supervisor
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
// Package windowsservice owns the machine-wide Windows service contract. The
|
||||
// policy and transition model are platform-neutral so they can be exercised
|
||||
// on Linux; service manager calls live in build-tagged adapters.
|
||||
package windowsservice
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
winlaunch "github.com/rvbox/rvbox/internal/client/supervisor/windows"
|
||||
)
|
||||
|
||||
const (
|
||||
Name = "RVBoxClient"
|
||||
DisplayName = "RVBox Client"
|
||||
Description = "RVBox Windows client daemon and command supervisor"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInvalidInstallSpec = errors.New("invalid Windows service install specification")
|
||||
ErrUnsupported = errors.New("Windows service management is unavailable on this platform")
|
||||
ErrInvalidTransition = errors.New("invalid Windows service lifecycle transition")
|
||||
)
|
||||
|
||||
type StartupMode string
|
||||
|
||||
const (
|
||||
StartupAutomatic StartupMode = "automatic"
|
||||
StartupManual StartupMode = "manual"
|
||||
)
|
||||
|
||||
// InstallSpec is the complete immutable service image contract. The service
|
||||
// is always one machine-wide LocalSystem process; config selection is explicit
|
||||
// and never delegated to PATH, the current directory, or Task Scheduler.
|
||||
type InstallSpec struct {
|
||||
ExecutablePath string
|
||||
ConfigPath string
|
||||
Startup StartupMode
|
||||
}
|
||||
|
||||
func (spec InstallSpec) Validate() error {
|
||||
if !winlaunch.ValidAbsoluteWindowsPath(spec.ExecutablePath) || !winlaunch.ValidAbsoluteWindowsPath(spec.ConfigPath) {
|
||||
return ErrInvalidInstallSpec
|
||||
}
|
||||
if strings.ContainsRune(spec.ExecutablePath, 0) || strings.ContainsRune(spec.ConfigPath, 0) {
|
||||
return ErrInvalidInstallSpec
|
||||
}
|
||||
if spec.Startup != StartupAutomatic && spec.Startup != StartupManual {
|
||||
return ErrInvalidInstallSpec
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (spec InstallSpec) String() string {
|
||||
return fmt.Sprintf("%s config=%s startup=%s", spec.ExecutablePath, spec.ConfigPath, spec.Startup)
|
||||
}
|
||||
|
||||
type State uint8
|
||||
|
||||
const (
|
||||
StateUnknown State = iota
|
||||
StateStopped
|
||||
StateStartPending
|
||||
StateRunning
|
||||
StateStopPending
|
||||
StatePaused
|
||||
)
|
||||
|
||||
type Command uint8
|
||||
|
||||
const (
|
||||
CommandStart Command = iota + 1
|
||||
CommandStop
|
||||
CommandRestart
|
||||
)
|
||||
|
||||
// Transition validates only state changes that the service control adapter is
|
||||
// allowed to request. Pending states are intentionally not collapsed into a
|
||||
// successful result: callers must poll SCM and observe the final state.
|
||||
func Transition(state State, command Command) (State, error) {
|
||||
switch command {
|
||||
case CommandStart:
|
||||
switch state {
|
||||
case StateStopped:
|
||||
return StateStartPending, nil
|
||||
case StateRunning, StateStartPending:
|
||||
return state, nil
|
||||
default:
|
||||
return StateUnknown, ErrInvalidTransition
|
||||
}
|
||||
case CommandStop:
|
||||
switch state {
|
||||
case StateRunning, StatePaused:
|
||||
return StateStopPending, nil
|
||||
case StateStopped, StateStopPending:
|
||||
return state, nil
|
||||
default:
|
||||
return StateUnknown, ErrInvalidTransition
|
||||
}
|
||||
case CommandRestart:
|
||||
switch state {
|
||||
case StateStopped:
|
||||
return StateStartPending, nil
|
||||
case StateRunning, StatePaused:
|
||||
return StateStopPending, nil
|
||||
case StateStartPending, StateStopPending:
|
||||
return state, nil
|
||||
default:
|
||||
return StateUnknown, ErrInvalidTransition
|
||||
}
|
||||
default:
|
||||
return StateUnknown, ErrInvalidTransition
|
||||
}
|
||||
}
|
||||
|
||||
// Install, Uninstall, Start, and Stop are intentionally narrow. Their
|
||||
// platform implementations return ErrUnsupported on non-Windows builds.
|
||||
func Install(spec InstallSpec) error { return installNative(spec) }
|
||||
func Uninstall() error { return uninstallNative() }
|
||||
func Start() error { return startNative() }
|
||||
func Stop(timeoutSeconds uint32) error { return stopNative(timeoutSeconds) }
|
||||
func Run(run func(context.Context) error) error { return runNative(run) }
|
||||
Reference in New Issue
Block a user