127 lines
3.8 KiB
Go
127 lines
3.8 KiB
Go
// 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 Configure(startup StartupMode) error { return configureNative(startup) }
|
|
func Start() error { return startNative() }
|
|
func Stop(timeoutSeconds uint32) error { return stopNative(timeoutSeconds) }
|
|
func Restart(timeoutSeconds uint32) error { return restartNative(timeoutSeconds) }
|
|
func Run(run func(context.Context) error) error { return runNative(run) }
|