From 985a2c2422feb80fd4fc925fdd6173322b6bbf7e Mon Sep 17 00:00:00 2001 From: cabbage Date: Wed, 9 Sep 2026 07:13:14 +0000 Subject: [PATCH] test: reset Windows native runs from clean baseline --- docs/implementation-plan.v1.md | 36 +++++++------- docs/testing-vm.md | 83 ++++++++++++++++++------------- docs/testing.md | 41 +++++++++------- scripts/windows/test-host | 89 +++++++++++++++++++++++++++++----- scripts/windows/test-host.ps1 | 2 +- 5 files changed, 170 insertions(+), 81 deletions(-) diff --git a/docs/implementation-plan.v1.md b/docs/implementation-plan.v1.md index e5cc1c6..70a4e19 100644 --- a/docs/implementation-plan.v1.md +++ b/docs/implementation-plan.v1.md @@ -192,7 +192,7 @@ scripts/test-e2e [--scenario NAME|all] [--run-id ID] [--resume] scripts/test-env doctor|coverage|status|logs|collect|recover|reuse|stop|reset|purge|gc ... scripts/build build|verify|doctor|recover|clean (pinned toolchain; host-safe) scripts/windows/build-test-bundle --run-id ID --config FILE [--ca FILE] -scripts/windows/test-host status|prepare|stage|run|collect|stop|reset|recover +scripts/windows/test-host status|prepare|stage|install|run|collect|stop|reset|recover ``` Scripts are thin, reviewed orchestration wrappers. `scripts/test-unit` invokes @@ -813,8 +813,8 @@ mirror; update both documents when the fixture is reprovisioned. | Diagnostic VRDE | Enabled at `192.168.50.162:3389`, external/`VBoxAuthSimple` authentication, input/display enabled, audio/USB/clipboard/RDPDR disabled; diagnostic-only because client compatibility is unreliable | | Native Windows RDP | Disabled in baseline (`TermService` stopped, `fDenyTSConnections=1`); port 3390 must not be treated as a usable control endpoint | | Test account | Local `rvboxtest`; split-token local administrator; console session 1 observed; Guest Control verified with `whoami`, `whoami /groups`, and `query user` | -| Baseline | `baseline-clean` (UUID `5e79176a-3e56-4c5d-bb61-a405a6dcdd59`) and child `baseline-disk-first` (UUID `9430a9a4-754a-4b22-beaa-8dfd90043f5b`); both are clean and must be retained | -| Last checked state | `poweroff`, current snapshot `baseline-disk-first`; the harness must re-check state and leave the VM powered off after cleanup | +| Baseline | Reset target `baseline-clean` (UUID `5e79176a-3e56-4c5d-bb61-a405a6dcdd59`): no RVBox service, tray registration, state, logs, or staged binary. Retain child `baseline-disk-first` (UUID `9430a9a4-754a-4b22-beaa-8dfd90043f5b`) for diagnostics only. | +| Last checked state | `poweroff`, current snapshot `baseline-disk-first`; restore `baseline-clean` before native runs, and leave that reset target selected after cleanup | The guest password, SSH key, and any host account secret are test secrets. Keep them in the operator/CI secret store or a mode-600 password file outside the @@ -826,17 +826,19 @@ overridden with `RVBOX_TEST_GUEST_PASSWORD_FILE`; the password value is never a default or repository value. The account name and VM metadata above are not credentials. -Guest Control uses this split-token administrator's medium-integrity token; its -Administrators SID is deny-only. Do not bypass UAC to create the service during -automation. A one-time interactive elevated fixture bootstrap must install the -test `RVBoxClient` service in the reset baseline and grant `rvboxtest` only -query/change-config/start/stop service access plus write access to its dedicated -test subtree. Each native run then changes that bootstrapped service's explicit -image/configuration and starts it through SCM. This is fixture administration, -not a second product worker/elevation broker or a Task Scheduler dependency. -Keep the exact service SDDL and test-subtree ACL with the fixture record before -taking its replacement baseline snapshot. The production installer/UAC flow is -tested separately through an interactive elevated lane. +Guest Control uses `rvboxtest`'s split-token, medium-integrity identity; its +Administrators SID is deny-only. The reset snapshot contains no RVBox +installation and the harness proves that `RVBoxClient` is absent immediately +after every `prepare`. Do not bypass UAC or turn this active-session test user +into an always-elevated account. Instead, provision a separate fixture-only +full-token administrator, with its username and a mode-0600 host-side password +file held outside the repository. `test-host install` uses that identity only +to execute the staged real `rvbox.exe --install-service` path and proves it by +polling SCM. It is not an RVBox product process, a service/broker, or a Task +Scheduler dependency, and it never enters the daemon's command-context choice. +The normal active `rvboxtest` session remains the target for execution-role +tests. The consent-prompt branch itself remains an interactive UAC test; an +invisible Guest Control session must never answer it. For this provisioned lane, the approved host-only credential-file location is `/home/cabbage/.local/share/rvbox-secrets/rvbox-win10-test.password`. It must @@ -861,7 +863,7 @@ identity in the run manifest: ```sh export RVBOX_TEST_VBOX_HOST=helium-remote export RVBOX_TEST_VBOX_VM=rvbox-win10-test -export RVBOX_TEST_VBOX_SNAPSHOT=baseline-disk-first +export RVBOX_TEST_VBOX_SNAPSHOT=baseline-clean export RVBOX_TEST_GUEST_USER=rvboxtest export RVBOX_TEST_GUEST_PASSWORD_FILE=/secure/outside-repo/rvbox-win10-test.password ``` @@ -880,7 +882,7 @@ ssh "$RVBOX_TEST_VBOX_HOST" \ # Restore only while powered off, then boot without a GUI. ssh "$RVBOX_TEST_VBOX_HOST" \ - 'VBoxManage snapshot "rvbox-win10-test" restore "baseline-disk-first"' + 'VBoxManage snapshot "rvbox-win10-test" restore "baseline-clean"' ssh "$RVBOX_TEST_VBOX_HOST" \ 'VBoxManage startvm "rvbox-win10-test" --type headless' @@ -928,7 +930,7 @@ Use this shutdown/reset sequence for every native run: acpipowerbutton` and poll. Use `controlvm ... poweroff` only for a hung, disposable test; it intentionally loses guest state. 3. Collect diagnostics while the VM is still available, then restore - `baseline-disk-first` and verify the snapshot UUID/current marker. + `baseline-clean` and verify the snapshot UUID/current marker. 4. Leave the VM powered off after cleanup. Never delete either baseline snapshot, unregister the VM, or modify `win10_dev` (that name refers to a stale unregistered configuration with a missing disk on this host). diff --git a/docs/testing-vm.md b/docs/testing-vm.md index 0af594e..e4ad17e 100644 --- a/docs/testing-vm.md +++ b/docs/testing-vm.md @@ -6,7 +6,9 @@ Windows release matrix. Keep the values here in sync with the VM before adding or changing native test automation. Last configuration check: 2026-09-09 UTC. The VM was observed powered off with -`baseline-disk-first` selected. A test run must still perform its own identity, +`baseline-disk-first` selected. The native harness now targets `baseline-clean`; +the fixture must be rechecked and its current snapshot returned to that clean +baseline before native runs resume. A test run must still perform its own identity, snapshot, readiness, and exclusive-lease checks rather than relying on that observation. @@ -25,7 +27,7 @@ observation. | Guest OS | Windows 10 Pro 22H2, build `19045.2006`, en-US, BIOS boot | | Guest account | Local `rvboxtest`; split-token local administrator; console session 1 was observed during provisioning | | Guest Additions | `7.2.16r174877`; readiness requires published Guest Additions version and Windows OS-release properties (this build does not publish a RunLevel property) | -| Last observed state | `poweroff`; current snapshot `baseline-disk-first` | +| Last observed state | `poweroff`; current snapshot `baseline-disk-first` (must be restored to `baseline-clean` before native runs) | The Guest Control credential is test-only. The account name is safe to record, but the password value is intentionally not committed to this repository. On @@ -84,7 +86,9 @@ and collection. Do not expose the VM's RDP endpoints beyond the test LAN. ## Snapshots and reset contract -Two clean snapshots exist and must be retained: +Two clean snapshots exist and must be retained. `baseline-clean` is the only +reset target: it contains no `RVBoxClient` SCM service, RVBox tray Run-key +registration, RVBox state, logs, or staged binaries. | Snapshot | UUID | Description | | --- | --- | --- | @@ -95,12 +99,12 @@ Restore only while the VM is powered off. Every destructive or potentially stateful run must: 1. Acquire the run lease and verify the VM name, UUID, and snapshot UUID. -2. Restore `baseline-disk-first` if the current state is not the baseline. +2. Restore `baseline-clean` if the current state is not the baseline. 3. Start headless and wait for `VMState=running` plus Guest Additions readiness. 4. Run the bounded test, collect redacted artifacts, and close every Guest Control process that was opened by the run. 5. Request a graceful guest shutdown and wait for `VMState=poweroff`. -6. Restore `baseline-disk-first` again and leave the VM powered off. +6. Restore `baseline-clean` again and leave the VM powered off. Use `controlvm ... poweroff` only for a hung, disposable test; it can lose guest state. Never delete either clean snapshot, unregister the VM, or alter @@ -112,7 +116,7 @@ The canonical adapter is the POSIX controller script [`scripts/windows/test-host`](../scripts/windows/test-host). It runs from the Linux controller and invokes `VBoxManage` only through SSH on Helium; the fixture host is Arch Linux and does not provide PowerShell. Its actions are -`status`, `prepare`, `stage`, `run`, `collect`, `stop`, `reset`, and `recover`. +`status`, `prepare`, `stage`, `install`, `run`, `collect`, `stop`, `reset`, and `recover`. The legacy [`test-host.ps1`](../scripts/windows/test-host.ps1) is retained only as a reference for a future Windows-hosted fixture and is not the Helium lane. @@ -122,8 +126,8 @@ The adapter takes identity and credentials only from its environment: export RVBOX_TEST_VBOX_HOST=helium-remote export RVBOX_TEST_VBOX_VM=rvbox-win10-test export RVBOX_TEST_VBOX_VM_UUID=6cdc114f-71e5-4167-a394-e922e14e6f5c -export RVBOX_TEST_VBOX_SNAPSHOT=baseline-disk-first -export RVBOX_TEST_VBOX_SNAPSHOT_UUID=9430a9a4-754a-4b22-beaa-8dfd90043f5b +export RVBOX_TEST_VBOX_SNAPSHOT=baseline-clean +export RVBOX_TEST_VBOX_SNAPSHOT_UUID=5e79176a-3e56-4c5d-bb61-a405a6dcdd59 export RVBOX_TEST_GUEST_USER=rvboxtest export RVBOX_TEST_GUEST_PASSWORD_FILE=/home/cabbage/.local/share/rvbox-secrets/rvbox-win10-test.password ``` @@ -145,34 +149,47 @@ the single `/c` payload are preserved. `stage` accepts one versioned non-secret test bundle and copies it first to an exact host staging directory, then to -`C:\\ProgramData\\RVBox\\test-runs\\`. `run` never directly executes the -GUI-subsystem `rvbox.exe` through Guest Control. It uses `sc.exe` and other -console-safe management tools to start/query/stop the installed RVBox service, -then checks the service's real health endpoint, named-pipe response, durable -state, and agent-server results. Before a WSS scenario it performs a bounded -guest-to-nginx connectivity and CA-trust probe. The resulting service and -artifact paths are recorded in the run report and reclaimed by the snapshot -reset rather than broad guest deletion. +`C:\\ProgramData\\RVBox\\test-runs\\`. `install` performs the one +purposeful direct Guest Control launch of the staged GUI-subsystem executable, +using only the fixture provisioner's high token; because this VirtualBox build +cannot reliably report that process's exit, SCM `RUNNING` is the completion +proof. After installation, `run` uses `sc.exe` and other console-safe management +tools to reconfigure/start/query/stop the installed RVBox service, then checks +the service's real health endpoint, named-pipe response, durable state, and +agent-server results. Before a WSS scenario it performs a bounded guest-to-nginx +connectivity and CA-trust probe. The resulting service and artifact paths are +recorded in the run report and reclaimed by the snapshot reset rather than broad +guest deletion. -### Required one-time service bootstrap +### Clean baseline and non-interactive installation -Guest Control launches `rvboxtest` with its filtered, medium-integrity UAC -token: the Administrators SID is deny-only. The harness must not bypass UAC to -create services. Before native service tests can run, an operator must use a -trusted interactive elevated session to install one test-only `RVBoxClient` -service in the baseline and grant only `rvboxtest` the service rights required -by the harness: query status/configuration, change its image/configuration, -start, and stop. The test account also needs write access only to the dedicated -`C:\\ProgramData\\RVBox\\test-runs` subtree; SYSTEM retains ownership of normal -RVBox state and logs. Record the resulting service SDDL and subtree ACL in this -document before taking a new reset snapshot. +`rvboxtest` deliberately remains a split-token administrator. Guest Control +therefore launches it at medium integrity and it must never be used to create +or modify machine-wide SCM state. The reset snapshot has no RVBox installation. -Each run then stages an exact bundle, changes the bootstrapped service image to -that run's explicit `--service --config` command line, and starts it through -SCM. The one-time bootstrap is fixture administration, not a second RVBox -process, runtime elevation broker, or Task Scheduler mechanism. Native tests -for the production installer/UAC flow remain a separately interactive test; -they cannot be automated through this filtered Guest Control token. +To automate the real install path, provision one separate **fixture-only** +full-token local administrator and retain its username/password solely in the +Helium secret store. It must be a genuinely high-integrity Guest Control token; +do not globally disable UAC or change `rvboxtest` into an always-elevated user. +The normal harness receives it only through these environment variables: + +```sh +export RVBOX_TEST_PROVISIONER_USER=FIXTURE_ONLY_FULL_ADMIN +export RVBOX_TEST_PROVISIONER_PASSWORD_FILE=/home/cabbage/.local/share/rvbox-secrets/rvbox-win10-test-provisioner.password +``` + +Both files remain mode `0600` on Helium and neither value is recorded in run +reports or artifacts. `test-host install` first verifies that the reset guest +has no `RVBoxClient`, checks the provisioner's High Mandatory Level, invokes +the actual staged `rvbox.exe --install-service --config ...`, and polls SCM for +`RUNNING`. `run` may then exercise reconfigure/start/restart paths. Snapshot +reset removes the installed service and all RVBox data again. + +The provisioner is fixture administration only: it is not shipped with RVBox, +not a product service/broker, not a Task Scheduler dependency, and never +participates in command-context selection. The separately interactive UAC +prompt route remains a small manual test because an invisible Guest Control +session cannot safely approve a consent prompt. ## Scope and known limitations diff --git a/docs/testing.md b/docs/testing.md index 2d31744..6fe9c81 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -49,6 +49,7 @@ scripts/windows/build-test-bundle \ scripts/windows/test-host prepare --run-id windows-smoke scripts/windows/test-host stage --run-id windows-smoke \ --bundle .test-runs/windows-smoke/windows-bundle +scripts/windows/test-host install --run-id windows-smoke ``` The bundle manifest records the source commit and SHA-256 of every bundled @@ -151,16 +152,17 @@ The provisioned fixture's non-secret identity values, including the host-local password-file path, are safe defaults in that script and may be overridden for another documented fixture; the password itself is never embedded. -The native lifecycle is `status`, `prepare`, `stage`, `run`, `collect`, -`stop`, and `reset`. `prepare` verifies the VM and snapshot UUIDs, restores the -baseline, starts headless, waits for Guest Additions, and records the selected -login fixture. `stage` copies a versioned non-secret test bundle through a -run-specific host directory to a run-specific guest directory. `run` starts -the real RVBox SCM service and observes it through SCM, the local health/tray -pipe, durable artifacts, and the test agent endpoint; it must not invoke the -GUI-subsystem `rvbox.exe` directly through Guest Control. `collect` obtains -only bounded/redacted artifacts, and `reset` restores the exact baseline and -leaves the VM powered off. +The native lifecycle is `status`, `prepare`, `stage`, `install`, `run`, +`collect`, `stop`, and `reset`. `prepare` verifies the VM and snapshot UUIDs, +restores the clean baseline, starts headless, waits for Guest Additions, and +proves that `RVBoxClient` is absent. `stage` copies a versioned non-secret test +bundle through a run-specific host directory to a run-specific guest directory. +`install` uses the fixture-only high-integrity automation principal to invoke +the real `rvbox.exe --install-service` path and proves completion through SCM. +`run` is for reconfiguration/restart scenarios after that first installation. +Neither action invokes the GUI-subsystem executable directly with the normal +Guest Control account. `collect` obtains only bounded/redacted artifacts, and +`reset` restores the exact clean baseline and leaves the VM powered off. This service-driven protocol is required because VirtualBox Guest Control 7.2.16 does not reliably complete a direct GUI-subsystem `rvbox.exe` run; @@ -171,14 +173,17 @@ also performs a bounded guest-to-nginx HTTPS/TCP readiness probe before it starts the service. Wine and protocol stubs are not equivalent Windows coverage. -The fixture needs a one-time operator-approved service bootstrap before the -SCM smoke lane can run: Guest Control supplies the split-token administrator's -medium UAC token, so it cannot safely install services. The bootstrap installs -the test service in the reset baseline and grants the test account only the SCM -query/change-config/start/stop rights plus access to its dedicated test subtree. -Each run then reconfigures and starts that one service. This does not add a -product broker or use Task Scheduler; it is fixture setup. See -[testing-vm.md](testing-vm.md) for the exact boundary. +The clean baseline intentionally contains no RVBox service, tray registration, +or RVBox state. Guest Control supplies `rvboxtest` with a filtered medium UAC +token, so it cannot safely perform the first machine-wide install. The fixture +therefore has a separate test-only full-token automation principal, whose +username and mode-600 host-side password-file are provided only as +`RVBOX_TEST_PROVISIONER_USER` and `RVBOX_TEST_PROVISIONER_PASSWORD_FILE` for +the `install`/machine-mutation actions. It is not an RVBox process, service, +broker, or Task Scheduler dependency, and it is never used to choose a command +execution context. The normal `rvboxtest` console session remains the subject +of active-user and elevation tests. See [testing-vm.md](testing-vm.md) for the +exact fixture contract. The VM is the minimum smoke lane, so native multi-session/ambiguous-session, Server Core, and older-build entries remain explicitly blocked until their own diff --git a/scripts/windows/test-host b/scripts/windows/test-host index f068c37..f2eb42f 100755 --- a/scripts/windows/test-host +++ b/scripts/windows/test-host @@ -18,7 +18,8 @@ Actions: status read-only VM/snapshot identity and state check prepare restore the declared baseline, boot headless, and verify Guest Additions stage copy a bundle containing rvbox.exe and client.toml into the guest test root - run create/update and start the RVBox SCM service from the staged bundle + install install and start RVBox from the staged bundle through a fixture-only full-admin principal + run start the already-installed RVBox SCM service from the staged bundle collect copy bounded guest artifacts to the local test-run directory stop stop RVBox through SCM and request a graceful guest shutdown reset stop the guest if necessary, restore the declared baseline, and leave it off @@ -29,6 +30,8 @@ Optional environment: RVBOX_TEST_VBOX_HOST, RVBOX_TEST_VBOX_VM, RVBOX_TEST_VBOX_VM_UUID, RVBOX_TEST_VBOX_SNAPSHOT, RVBOX_TEST_VBOX_SNAPSHOT_UUID, RVBOX_TEST_GUEST_USER, RVBOX_TEST_GUEST_PASSWORD_FILE (overrides) + RVBOX_TEST_PROVISIONER_USER, RVBOX_TEST_PROVISIONER_PASSWORD_FILE + (required by install; a fixture-only full-token administrator) RVBOX_TEST_HOST_STAGE_ROOT (default /home/cabbage/.local/state/rvbox-test-runs) RVBOX_TEST_RUN_ROOT (default .test-runs/windows-vm) EOF @@ -77,7 +80,7 @@ while [ "$#" -gt 0 ]; do esac done -case $action in status|prepare|stage|run|collect|stop|reset|recover) ;; *) usage >&2; fail "unknown action $action" ;; esac +case $action in status|prepare|stage|install|run|collect|stop|reset|recover) ;; *) usage >&2; fail "unknown action $action" ;; esac if [ "$action" != status ]; then [ -n "$run_id" ] || fail "$action requires --run-id" safe_id "$run_id" @@ -97,10 +100,12 @@ if [ -n "$endpoint" ]; then safe_word endpoint "$endpoint"; fi : "${RVBOX_TEST_VBOX_HOST:=helium-remote}" : "${RVBOX_TEST_VBOX_VM:=rvbox-win10-test}" : "${RVBOX_TEST_VBOX_VM_UUID:=6cdc114f-71e5-4167-a394-e922e14e6f5c}" -: "${RVBOX_TEST_VBOX_SNAPSHOT:=baseline-disk-first}" -: "${RVBOX_TEST_VBOX_SNAPSHOT_UUID:=9430a9a4-754a-4b22-beaa-8dfd90043f5b}" +: "${RVBOX_TEST_VBOX_SNAPSHOT:=baseline-clean}" +: "${RVBOX_TEST_VBOX_SNAPSHOT_UUID:=5e79176a-3e56-4c5d-bb61-a405a6dcdd59}" : "${RVBOX_TEST_GUEST_USER:=rvboxtest}" : "${RVBOX_TEST_GUEST_PASSWORD_FILE:=/home/cabbage/.local/share/rvbox-secrets/rvbox-win10-test.password}" +provisioner_user=${RVBOX_TEST_PROVISIONER_USER:-} +provisioner_password_file=${RVBOX_TEST_PROVISIONER_PASSWORD_FILE:-} for name in RVBOX_TEST_VBOX_HOST RVBOX_TEST_VBOX_VM RVBOX_TEST_VBOX_VM_UUID \ RVBOX_TEST_VBOX_SNAPSHOT RVBOX_TEST_VBOX_SNAPSHOT_UUID \ @@ -115,6 +120,8 @@ safe_word RVBOX_TEST_VBOX_SNAPSHOT "$RVBOX_TEST_VBOX_SNAPSHOT" safe_word RVBOX_TEST_VBOX_SNAPSHOT_UUID "$RVBOX_TEST_VBOX_SNAPSHOT_UUID" safe_word RVBOX_TEST_GUEST_USER "$RVBOX_TEST_GUEST_USER" safe_word RVBOX_TEST_GUEST_PASSWORD_FILE "$RVBOX_TEST_GUEST_PASSWORD_FILE" +if [ -n "$provisioner_user" ]; then safe_word RVBOX_TEST_PROVISIONER_USER "$provisioner_user"; fi +if [ -n "$provisioner_password_file" ]; then safe_word RVBOX_TEST_PROVISIONER_PASSWORD_FILE "$provisioner_password_file"; fi host_stage_root=${RVBOX_TEST_HOST_STAGE_ROOT:-/home/cabbage/.local/state/rvbox-test-runs} run_root=${RVBOX_TEST_RUN_ROOT:-$repo_root/.test-runs/windows-vm} @@ -145,6 +152,8 @@ host_stage=$8 guest_root=$9 shift 9 endpoint=$1 +provisioner_user=$2 +provisioner_password_file=$3 [ "$endpoint" = - ] && endpoint= fail() { printf '%s\n' "remote test-host: $*" >&2; exit 2; } @@ -206,6 +215,41 @@ guest_run() { printf '%s\n' "$output" | tr -d '\r' | grep -qx 'RVBOX_GUEST_OK' } +provisioner_run() { + # The clean baseline deliberately has no RVBox service. Guest Control's + # normal test account has a filtered UAC token, so only the fixture-only + # full-token administrator may perform the first machine-wide install. + [ -n "$provisioner_user" ] || fail "install requires RVBOX_TEST_PROVISIONER_USER" + [ -n "$provisioner_password_file" ] || fail "install requires RVBOX_TEST_PROVISIONER_PASSWORD_FILE" + output=$(VBoxManage guestcontrol "$vm" --username "$provisioner_user" --passwordfile "$provisioner_password_file" \ + run "$@" &1) || true + printf '%s\n' "$output" + printf '%s\n' "$output" | tr -d '\r' | grep -qx 'RVBOX_GUEST_OK' +} + +assert_provisioner_elevated() { + # This fixture is en-US. Check the mandatory label before allowing any + # machine-wide mutation, so an accidentally filtered automation account + # fails closed instead of silently weakening the test contract. + provisioner_run --exe 'C:\\Windows\\System32\\cmd.exe' --wait-stdout --wait-stderr --unquoted-args -- \ + /d /s /c 'whoami /groups | findstr /c:"High Mandatory Level" >NUL && echo RVBOX_GUEST_OK' >/dev/null || \ + fail "fixture provisioner is not a full high-integrity administrator" +} + +assert_clean_guest() { + # A missing service is the authoritative clean-baseline condition. The + # test service name is unique, so do not delete or alter any other service. + guest_run --exe 'C:\Windows\System32\cmd.exe' --wait-stdout --wait-stderr --unquoted-args -- \ + /d /s /c 'sc.exe query RVBoxClient >NUL 2>&1 & if errorlevel 1060 (echo RVBOX_GUEST_OK) else exit /b 1' >/dev/null || \ + fail "reset baseline is not clean: RVBoxClient is already installed" +} + +assert_staged_guest() { + guest_run --exe 'C:\Windows\System32\cmd.exe' --wait-stdout --wait-stderr --unquoted-args -- \ + /d /s /c "if exist \"$guest_root\\rvbox.exe\" if exist \"$guest_root\\client.toml\" echo RVBOX_GUEST_OK" >/dev/null || \ + fail "staged guest bundle is missing rvbox.exe or client.toml" +} + wait_guest_additions() { attempt=0 while [ "$attempt" -lt 60 ]; do @@ -257,7 +301,8 @@ case "$action" in step prepare-vm-started wait_guest_additions step prepare-guest-additions-ready - step prepare-bootstrap-complete + assert_clean_guest + step prepare-clean-baseline-verified ;; probe-identity) assert_identity @@ -298,10 +343,29 @@ case "$action" in VBoxManage guestcontrol "$vm" --username "$guest_user" --passwordfile "$password_file" \ copyto "$host_stage/ca.pem" "$guest_root\\ca.pem" /dev/null 2>&1 || true + wait_service RUNNING + step install-scm-service-running + printf 'service=RVBoxClient state=RUNNING install=clean-baseline\n' + ;; run) assert_identity require_lease [ "$(state)" = running ] || fail "run requires a running prepared VM" + assert_staged_guest + assert_provisioner_elevated if [ -n "$endpoint" ]; then endpoint_host=${endpoint%:*} endpoint_port=${endpoint##*:} @@ -309,15 +373,15 @@ case "$action" in -NoProfile -NonInteractive -Command "if (-not (Test-NetConnection -ComputerName '$endpoint_host' -Port $endpoint_port -InformationLevel Quiet)) { exit 1 }; Write-Output RVBOX_GUEST_OK" >/dev/null fi image="\\\"$guest_root\\rvbox.exe\\\" --service --config \\\"$guest_root\\client.toml\\\"" - guest_run --exe 'C:\Windows\System32\cmd.exe' --wait-stdout --wait-stderr --unquoted-args -- \ + provisioner_run --exe 'C:\Windows\System32\cmd.exe' --wait-stdout --wait-stderr --unquoted-args -- \ /d /s /c "sc.exe query RVBoxClient >NUL 2>&1 && echo RVBOX_GUEST_OK" >/dev/null || \ - fail "RVBoxClient is not fixture-bootstrapped or the test token lacks SCM query access" - guest_run --exe 'C:\Windows\System32\cmd.exe' --wait-stdout --wait-stderr --unquoted-args -- \ + fail "RVBoxClient is not installed; run install from the clean baseline first" + provisioner_run --exe 'C:\Windows\System32\cmd.exe' --wait-stdout --wait-stderr --unquoted-args -- \ /d /s /c "sc.exe config RVBoxClient binPath= \"$image\" start= demand >NUL 2>&1 && echo RVBOX_GUEST_OK" >/dev/null || \ - fail "fixture service does not grant the test token SERVICE_CHANGE_CONFIG" - guest_run --exe 'C:\Windows\System32\cmd.exe' --wait-stdout --wait-stderr --unquoted-args -- \ + fail "fixture provisioner could not change RVBoxClient configuration" + provisioner_run --exe 'C:\Windows\System32\cmd.exe' --wait-stdout --wait-stderr --unquoted-args -- \ /d /s /c '(sc.exe start RVBoxClient >NUL 2>&1 || sc.exe query RVBoxClient | findstr /c:"RUNNING" >NUL) && echo RVBOX_GUEST_OK' >/dev/null || \ - fail "fixture service did not accept SCM start" + fail "fixture provisioner could not start RVBoxClient" wait_service RUNNING printf 'service=RVBoxClient state=RUNNING\n' ;; @@ -382,7 +446,8 @@ REMOTE "$1" "$RVBOX_TEST_VBOX_VM" "$RVBOX_TEST_VBOX_VM_UUID" \ "$RVBOX_TEST_VBOX_SNAPSHOT" "$RVBOX_TEST_VBOX_SNAPSHOT_UUID" \ "$RVBOX_TEST_GUEST_USER" "$RVBOX_TEST_GUEST_PASSWORD_FILE" \ - "$host_stage" "$guest_root" "$remote_endpoint"