test: add reproducible Windows VM bundle harness
This commit is contained in:
@@ -4,6 +4,7 @@ COMPOSE := docker compose -f deploy/compose.yaml
|
||||
GO_PACKAGES := ./...
|
||||
|
||||
.PHONY: generate fmt lint test test-race test-coverage test-integration test-e2e test-status build verify \
|
||||
build-windows-test-bundle \
|
||||
_toolchain-generate _toolchain-fmt _toolchain-lint _toolchain-test _toolchain-test-race \
|
||||
_toolchain-test-coverage _toolchain-build _toolchain-verify
|
||||
|
||||
@@ -45,6 +46,11 @@ test-status:
|
||||
@test -n "$(RUN_ID)" || (echo "RUN_ID is required" >&2; exit 2)
|
||||
./scripts/test-env status --run-id "$(RUN_ID)"
|
||||
|
||||
build-windows-test-bundle:
|
||||
@test -n "$(RUN_ID)" || (echo "RUN_ID is required" >&2; exit 2)
|
||||
@test -n "$(WINDOWS_TEST_CONFIG)" || (echo "WINDOWS_TEST_CONFIG is required" >&2; exit 2)
|
||||
./scripts/windows/build-test-bundle --run-id "$(RUN_ID)" --config "$(WINDOWS_TEST_CONFIG)"
|
||||
|
||||
_toolchain-generate:
|
||||
buf generate
|
||||
|
||||
|
||||
@@ -190,7 +190,9 @@ scripts/test-unit [--package PATTERN] [--run REGEXP] [--race]
|
||||
scripts/test-integration [--suite NAME|all] [--run-id ID] [--resume]
|
||||
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/windows/test-host.ps1 Prepare|Status|Run|Collect|Stop|Reset
|
||||
scripts/build build|verify (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 are thin, reviewed orchestration wrappers. `scripts/test-unit` invokes
|
||||
@@ -199,6 +201,13 @@ commands call a shared Go harness under `test/harness` so manifest parsing,
|
||||
timeouts, process control, reporting, and cleanup logic are not reimplemented in
|
||||
shell and PowerShell. The harness itself is built in the toolchain container;
|
||||
no Go/Buf/protoc/SQLite SDK or package manager is installed on the host.
|
||||
`scripts/build` is the corresponding host-safe wrapper around the established
|
||||
containerized `make build`/`make verify` targets. A native run uses
|
||||
`scripts/windows/build-test-bundle` to make one non-secret bundle below its
|
||||
exact `.test-runs/<run-id>` directory, with source-commit and SHA-256 manifest;
|
||||
it refuses replacement rather than overwriting an earlier bundle. This is not a
|
||||
release publisher: signing, version resources, and public checksum publication
|
||||
remain Phase 8 gates.
|
||||
|
||||
The first integration/E2E invocation creates a filesystem-safe random run ID,
|
||||
or validates an explicitly supplied one, and records
|
||||
@@ -257,12 +266,14 @@ copies prior mutable test state.
|
||||
The Linux controller exposes bounded fault controls for frame drop/delay,
|
||||
listener interruption, process termination at named durability checkpoints,
|
||||
filesystem quota/error simulation, and monotonic/wall-clock advancement in the
|
||||
deterministic peer. Native Windows `test-host.ps1` validates administrator state,
|
||||
OS build, UAC/policy fixture, active sessions, service identity, and test-root
|
||||
ownership before running. It uses a test-specific ProgramData root and an
|
||||
exclusive host lease; production-name SCM/Run-key installation tests run only in
|
||||
a resettable VM snapshot. Passwords and VM access credentials come from the CI
|
||||
secret store or interactive prompt, never arguments persisted in the manifest.
|
||||
deterministic peer. Native Windows `test-host` validates administrator state, OS
|
||||
build, UAC/policy fixture, active sessions, service identity, VM/snapshot UUIDs,
|
||||
and test-root ownership before running. It is a POSIX controller which uses SSH
|
||||
to run `VBoxManage` on the Linux fixture host; it uses a test-specific
|
||||
ProgramData root and an exclusive remote host lease. Production-name SCM/Run-key
|
||||
installation tests run only in a resettable VM snapshot. Passwords and VM access
|
||||
credentials come from the CI secret store or interactive prompt, never arguments
|
||||
persisted in the manifest.
|
||||
|
||||
The E2E controller owns the canonical run manifest on Linux and passes the same
|
||||
run ID plus a one-run configuration bundle to the preconfigured Windows runner.
|
||||
@@ -761,7 +772,7 @@ supported Windows 10 or Server 2016 baseline; it need not be the everyday runner
|
||||
A physical Windows machine is optional. It is useful for an additional real
|
||||
display/audio/DDC command smoke, but RVBox only guarantees correct token/session/
|
||||
process execution—not success of arbitrary vendor hardware APIs—so physical
|
||||
hardware is not a release blocker. `test-host.ps1 Prepare` must inventory the
|
||||
hardware is not a release blocker. `test-host prepare` must inventory the
|
||||
host against this checklist and refuse destructive suites unless the machine is
|
||||
explicitly marked disposable/resettable and the clean snapshot identity is
|
||||
recorded.
|
||||
@@ -787,7 +798,7 @@ mirror; update both documents when the fixture is reprovisioned.
|
||||
| VM name / UUID | `rvbox-win10-test` / `6cdc114f-71e5-4167-a394-e922e14e6f5c` |
|
||||
| VM group / config | `/RVBox/Tests`; `/home/cabbage/VirtualBox VMs/RVBox/Tests/rvbox-win10-test/rvbox-win10-test.vbox` |
|
||||
| Guest OS | Windows 10 Pro 22H2, build `19045.2006`, en-US, BIOS boot |
|
||||
| Guest Additions | `7.2.16r174877`; Guest Control readiness requires `GuestAdditionsRunLevel=3` |
|
||||
| Guest Additions | `7.2.16r174877`; readiness requires published Guest Additions version and Windows OS-release properties (this build does not publish a RunLevel property) |
|
||||
| Resources | 2 vCPU, 4096 MiB RAM, 64 MiB VRAM, `VBoxSVGA`, 3D acceleration disabled, 40 GiB dynamically allocated VDI |
|
||||
| Disk / source media | `/home/cabbage/VMs/rvbox-win10-test.vdi`; source ISO `/media/Data2/Downloaded/Win10_22H2_English_x64.iso` (Windows image index 6) |
|
||||
| Devices | Audio (`none`), playback/capture, USB (OHCI/EHCI/xHCI), clipboard/file transfer, drag-and-drop, and shared folders disabled; Intel 82540EM NIC, cable connected |
|
||||
@@ -802,10 +813,23 @@ 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
|
||||
repository; never put them in this plan, a command-line argument, a run
|
||||
manifest, or collected logs. `VBoxManage guestcontrol` supports
|
||||
`--passwordfile`; prefer that option over an inline password. Every operator or
|
||||
agent must set `RVBOX_TEST_GUEST_PASSWORD_FILE` to the absolute path of that
|
||||
host-side file before a native run. The account name and VM metadata above are
|
||||
not credentials.
|
||||
`--passwordfile`; prefer that option over an inline password. The documented
|
||||
fixture's host-local password-file path is a controller default and may be
|
||||
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.
|
||||
|
||||
For this provisioned lane, the approved host-only credential-file location is
|
||||
`/home/cabbage/.local/share/rvbox-secrets/rvbox-win10-test.password`. It must
|
||||
@@ -853,15 +877,18 @@ ssh "$RVBOX_TEST_VBOX_HOST" \
|
||||
ssh "$RVBOX_TEST_VBOX_HOST" \
|
||||
'VBoxManage startvm "rvbox-win10-test" --type headless'
|
||||
|
||||
# Poll these properties before a test; GuestAdditionsRunLevel=3 is required
|
||||
# for Guest Control, and LoggedInUsers/NoLoggedInUsers selects the session case.
|
||||
# Poll these properties before a test. This fixture requires Guest Additions
|
||||
# version plus Windows OS-release properties; LoggedInUsers/NoLoggedInUsers
|
||||
# selects the session case.
|
||||
ssh "$RVBOX_TEST_VBOX_HOST" \
|
||||
'VBoxManage guestproperty enumerate "rvbox-win10-test"'
|
||||
```
|
||||
|
||||
The harness must wait for the VM to report `running`, then poll Guest
|
||||
Properties until Guest Additions is ready and the requested login fixture is
|
||||
observed. NAT address `10.0.2.15` was observed during provisioning but is DHCP
|
||||
Properties until the Guest Additions version and Windows OS-release properties
|
||||
are present and the requested login fixture is observed. Do not require a
|
||||
`GuestAdditionsRunLevel` value: VirtualBox Guest Additions `7.2.16r174877` on
|
||||
this fixture does not publish it. NAT address `10.0.2.15` was observed during provisioning but is DHCP
|
||||
state, not an identity or a stable endpoint; use Guest Control for management
|
||||
and discover any test networking separately. A failed readiness poll is a
|
||||
stopped-resumable run, not permission to start a second VM with the same name.
|
||||
@@ -964,7 +991,7 @@ scripts/
|
||||
test-integration # resumable component-suite entry point
|
||||
test-e2e # resumable production-shaped scenario entry point
|
||||
test-env # doctor/recover/reuse/cleanup by exact run ID
|
||||
windows/test-host.ps1 # native Windows host lifecycle adapter
|
||||
windows/test-host # POSIX controller for the native Windows VM lifecycle
|
||||
test/
|
||||
coverage.toml # requirement-to-case inventory with stable IDs
|
||||
harness/ # shared manifest, journal, orchestration, and reporting
|
||||
@@ -2196,7 +2223,7 @@ The native `windows-supervisor` and `windows-service-tray` integration suites us
|
||||
real Windows subprocesses and APIs for both shells, CWD/environment overlays,
|
||||
concurrent output without newlines, stdin ordering/close, Job tree termination,
|
||||
script materialization/cleanup, service shutdown interruption, and every token/
|
||||
session context. They run through `scripts/windows/test-host.ps1` under the
|
||||
session context. They run through `scripts/windows/test-host` under the
|
||||
exclusive host lease and journal every machine-wide mutation for reset.
|
||||
|
||||
Add a table-driven crash suite for every acceptance, script-upload, launch,
|
||||
|
||||
+55
-19
@@ -24,7 +24,7 @@ observation.
|
||||
| Snapshot folder | `/home/cabbage/VirtualBox VMs/RVBox/Tests/rvbox-win10-test/Snapshots` |
|
||||
| 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`; Guest Control readiness requires `GuestAdditionsRunLevel=3` |
|
||||
| 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` |
|
||||
|
||||
The Guest Control credential is test-only. The account name is safe to record,
|
||||
@@ -108,35 +108,71 @@ the stale unregistered `win10_dev` configuration (its disk is missing).
|
||||
|
||||
## Harness contract
|
||||
|
||||
The checked-in PowerShell adapter is [`scripts/windows/test-host.ps1`](../scripts/windows/test-host.ps1).
|
||||
Its actions are `Prepare`, `Status`, `Run`, `Collect`, `Stop`, and `Reset`.
|
||||
The adapter takes identity and credentials only from the host environment:
|
||||
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`.
|
||||
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.
|
||||
|
||||
```powershell
|
||||
$env:RVBOX_WINDOWS_VM = 'rvbox-win10-test'
|
||||
$env:RVBOX_WINDOWS_BASELINE_SNAPSHOT = 'baseline-disk-first'
|
||||
$env:RVBOX_WINDOWS_GUEST_USER = 'rvboxtest'
|
||||
$env:RVBOX_WINDOWS_GUEST_PASSWORD_FILE = 'C:\secure\rvbox-win10-test.password'
|
||||
```
|
||||
|
||||
The implementation plan also uses the equivalent `RVBOX_TEST_*` names for
|
||||
controllers that run the lifecycle over SSH:
|
||||
The adapter takes identity and credentials only from its environment:
|
||||
|
||||
```sh
|
||||
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_GUEST_USER=rvboxtest
|
||||
export RVBOX_TEST_GUEST_PASSWORD_FILE=/home/cabbage/.local/share/rvbox-secrets/rvbox-win10-test.password
|
||||
```
|
||||
|
||||
Those values are the controller defaults for this one documented fixture, so a
|
||||
normal Helium run does not need to export them. They remain overrideable for a
|
||||
separately recorded fixture. The default contains only the host-local password
|
||||
*file path*, never the password value.
|
||||
|
||||
The provisioned lane's backend is SSH plus `VBoxManage` plus Guest Control; it
|
||||
does not require WinRM, OpenSSH inside Windows, or a stable guest IP. Guest
|
||||
commands must pass explicit paths and argument vectors. With this VirtualBox
|
||||
build, `--wait-stdout` and `--wait-stderr` are supported but `--wait-exit` is
|
||||
not; use `closeprocess` when a process wait cannot complete. For `cmd.exe`
|
||||
payloads, include `--unquoted-args` so Windows backslashes and the single `/c`
|
||||
payload are preserved.
|
||||
does not require WinRM, OpenSSH inside Windows, or a stable guest IP. The
|
||||
adapter first validates the VM and snapshot UUIDs, acquires its exclusive lease
|
||||
on Helium, and writes a non-secret step report to the run directory. Guest
|
||||
commands use exact console-safe executable paths and argument vectors. With
|
||||
this VirtualBox build, `--wait-stdout` and `--wait-stderr` are supported but
|
||||
`--wait-exit` is not; use `closeprocess` when a process wait cannot complete.
|
||||
For `cmd.exe` payloads, include `--unquoted-args` so Windows backslashes and
|
||||
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-id>`. `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.
|
||||
|
||||
### Required one-time service bootstrap
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
## Scope and known limitations
|
||||
|
||||
|
||||
+70
-17
@@ -15,6 +15,30 @@ Run focused unit tests with:
|
||||
scripts/test-unit --package ./internal/domain --run UUIDv7 --race
|
||||
```
|
||||
|
||||
Build all supported binaries without installing `make` or Go on the host:
|
||||
|
||||
```sh
|
||||
scripts/build build
|
||||
```
|
||||
|
||||
For a native Windows run, create the immutable per-run test bundle with the
|
||||
pinned toolchain. Supply a test-specific config whose endpoint and CA path are
|
||||
valid for that run; the command refuses to replace an existing bundle.
|
||||
|
||||
```sh
|
||||
scripts/windows/build-test-bundle \
|
||||
--run-id windows-smoke \
|
||||
--config .test-runs/windows-smoke/client.toml \
|
||||
--ca .test-runs/windows-smoke/ca.pem
|
||||
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
|
||||
```
|
||||
|
||||
The bundle manifest records the source commit and SHA-256 of every bundled
|
||||
file. It is a test artifact, not a signed release package; release signing,
|
||||
version resources, and publication are Phase 8 gates.
|
||||
|
||||
The integration harness provides the Phase 0 `sample` suite, the incremental
|
||||
Phase 2 `store` suite, and the incremental Phase 3 `server-session` suite.
|
||||
The resumable E2E harness adds `smoke`, `script`, `recovery`, and `all`
|
||||
@@ -97,20 +121,49 @@ than treating it as a stable endpoint. VRDE is enabled at
|
||||
`192.168.50.162:3389` for diagnostics, while native Windows RDP is disabled in
|
||||
the baseline.
|
||||
|
||||
The exact headless VirtualBox/Guest Control adapter is
|
||||
`scripts/windows/test-host.ps1`; it takes the VM name, baseline snapshot, and
|
||||
guest identity/password-file only from host environment variables, acquires an
|
||||
exclusive lease, and never writes secrets to the repository. Set
|
||||
`RVBOX_WINDOWS_GUEST_PASSWORD_FILE` to a mode-600 file outside the repository;
|
||||
the adapter passes it with VirtualBox `--passwordfile` and never accepts an
|
||||
inline password. Use `Prepare`, `Run`,
|
||||
`Collect`, `Stop`, and `Reset` in that order for a native run. The VM is the
|
||||
minimum smoke lane, so deferred native multi-session/ambiguous-session, Server
|
||||
Core, and older-build entries remain explicitly blocked until their own
|
||||
fixtures exist. A previous Windows 10 fixture probe found that VirtualBox Guest
|
||||
Control 7.2.16 rejects the GUI-subsystem `rvbox.exe` as a directly runnable
|
||||
guest executable; wrapping it through `cmd.exe` exited the RVBox process but
|
||||
left the Guest Control wrapper waiting. This remains an adapter completion-path
|
||||
limitation: native runtime coverage needs a service-driven or
|
||||
console-compatible guest runner. Wine or a protocol stub is not treated as
|
||||
equivalent coverage.
|
||||
The canonical headless VirtualBox/Guest Control adapter is
|
||||
`scripts/windows/test-host`. It is a POSIX controller script because the
|
||||
fixture's VirtualBox host is Arch Linux and has no PowerShell runtime. The
|
||||
controller connects to Helium over SSH; `VBoxManage` and the host-only password
|
||||
file never need to exist on the Linux development controller. It takes the VM
|
||||
identity, baseline snapshot, guest identity, and password-file only from host
|
||||
environment variables, acquires an exclusive remote lease, and never writes
|
||||
secrets to the repository, run manifest, or command line. Set
|
||||
`RVBOX_TEST_GUEST_PASSWORD_FILE` to the mode-600 host-side file; the adapter
|
||||
passes it only as VirtualBox `--passwordfile`.
|
||||
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.
|
||||
|
||||
This service-driven protocol is required because VirtualBox Guest Control
|
||||
7.2.16 does not reliably complete a direct GUI-subsystem `rvbox.exe` run;
|
||||
wrapping it in `cmd.exe` can leave the Guest Control wrapper waiting. Guest
|
||||
Control is therefore limited to console-safe setup tools (`sc.exe`, `whoami`,
|
||||
`query`, bounded file operations) and artifact collection. A native E2E run
|
||||
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 VM is the minimum smoke lane, so native multi-session/ambiguous-session,
|
||||
Server Core, and older-build entries remain explicitly blocked until their own
|
||||
fixtures exist. Their pure selector tests remain mandatory.
|
||||
|
||||
Executable
+22
@@ -0,0 +1,22 @@
|
||||
#!/bin/sh
|
||||
# Host-safe entry point for the pinned RVBox build toolchain.
|
||||
set -eu
|
||||
|
||||
repo_root=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
||||
target=${1:-build}
|
||||
shift || true
|
||||
|
||||
case $target in
|
||||
build|verify) ;;
|
||||
*)
|
||||
printf '%s\n' 'usage: scripts/build [build|verify]' >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
[ "$#" -eq 0 ] || {
|
||||
printf '%s\n' 'scripts/build does not accept additional arguments' >&2
|
||||
exit 2
|
||||
}
|
||||
|
||||
cd "$repo_root"
|
||||
exec docker compose -f deploy/compose.yaml run --rm toolchain make "$target"
|
||||
Executable
+74
@@ -0,0 +1,74 @@
|
||||
#!/bin/sh
|
||||
# Build a deterministic, non-secret Windows test bundle for one harness run.
|
||||
# It deliberately does not sign or publish artifacts; those are release gates.
|
||||
set -eu
|
||||
|
||||
repo_root=$(CDPATH= cd -- "$(dirname -- "$0")/../.." && pwd)
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
usage: scripts/windows/build-test-bundle --run-id ID --config FILE [--ca FILE] [--no-build]
|
||||
|
||||
Builds bin/rvbox.exe in the pinned Docker toolchain, then creates exactly:
|
||||
.test-runs/ID/windows-bundle/{rvbox.exe,client.toml[,ca.pem],manifest.sha256}
|
||||
|
||||
The bundle is for the resettable native-test fixture only. The config must use
|
||||
the target guest paths and the declared test nginx endpoint. Existing bundles
|
||||
are refused rather than overwritten.
|
||||
EOF
|
||||
}
|
||||
|
||||
fail() { printf '%s\n' "build-test-bundle: $*" >&2; exit 2; }
|
||||
|
||||
run_id=
|
||||
config=
|
||||
ca=
|
||||
build=yes
|
||||
while [ "$#" -gt 0 ]; do
|
||||
case $1 in
|
||||
--run-id) [ "$#" -ge 2 ] || fail "--run-id needs a value"; run_id=$2; shift 2 ;;
|
||||
--config) [ "$#" -ge 2 ] || fail "--config needs a file"; config=$2; shift 2 ;;
|
||||
--ca) [ "$#" -ge 2 ] || fail "--ca needs a PEM file"; ca=$2; shift 2 ;;
|
||||
--no-build) build=no; shift ;;
|
||||
--help|-h) usage; exit 0 ;;
|
||||
*) fail "unknown argument $1" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
case $run_id in
|
||||
[a-z0-9]* ) ;;
|
||||
* ) fail "run ID must start with lowercase alphanumeric" ;;
|
||||
esac
|
||||
case $run_id in
|
||||
''|*[!a-z0-9-]*|????????????????????????????????????????????????????????????????*)
|
||||
fail "run ID must match [a-z0-9][a-z0-9-]{0,63}"
|
||||
;;
|
||||
esac
|
||||
[ -f "$config" ] && [ ! -L "$config" ] || fail "--config must be a regular non-symlink file"
|
||||
if [ -n "$ca" ]; then [ -f "$ca" ] && [ ! -L "$ca" ] || fail "--ca must be a regular non-symlink file"; fi
|
||||
|
||||
if [ "$build" = yes ]; then "$repo_root/scripts/build" build; fi
|
||||
binary=$repo_root/bin/rvbox.exe
|
||||
[ -f "$binary" ] && [ ! -L "$binary" ] || fail "expected regular Windows binary at bin/rvbox.exe"
|
||||
|
||||
bundle=$repo_root/.test-runs/$run_id/windows-bundle
|
||||
mkdir -p "$repo_root/.test-runs/$run_id"
|
||||
if ! mkdir "$bundle"; then
|
||||
fail "refusing to overwrite existing bundle $bundle"
|
||||
fi
|
||||
trap 'rmdir "$bundle" 2>/dev/null || true' INT TERM HUP
|
||||
|
||||
install -m 700 "$binary" "$bundle/rvbox.exe"
|
||||
install -m 600 "$config" "$bundle/client.toml"
|
||||
if [ -n "$ca" ]; then install -m 600 "$ca" "$bundle/ca.pem"; fi
|
||||
|
||||
commit=$(git -C "$repo_root" rev-parse HEAD)
|
||||
{
|
||||
printf 'run_id=%s\n' "$run_id"
|
||||
printf 'git_commit=%s\n' "$commit"
|
||||
(cd "$bundle" && sha256sum rvbox.exe client.toml ${ca:+ca.pem})
|
||||
} >"$bundle/manifest.sha256"
|
||||
chmod 600 "$bundle/manifest.sha256"
|
||||
|
||||
trap - INT TERM HUP
|
||||
printf 'bundle=%s\n' "$bundle"
|
||||
Executable
+412
@@ -0,0 +1,412 @@
|
||||
#!/bin/sh
|
||||
# Native Windows VM controller for the Helium VirtualBox smoke fixture.
|
||||
#
|
||||
# This intentionally runs on the Linux controller. VBoxManage and the
|
||||
# password file stay on the Linux VirtualBox host, reached only over SSH. The
|
||||
# VM's GUI-subsystem rvbox.exe is never started directly by Guest Control:
|
||||
# Guest Control runs console-safe management programs, while SCM runs the real
|
||||
# service process.
|
||||
set -eu
|
||||
|
||||
repo_root=$(CDPATH= cd -- "$(dirname -- "$0")/../.." && pwd)
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
usage: scripts/windows/test-host ACTION [--run-id ID] [--bundle DIRECTORY] [--endpoint HOST:PORT]
|
||||
|
||||
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
|
||||
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
|
||||
recover read-only fixture/run-state check for a stopped-resumable run
|
||||
|
||||
Optional environment:
|
||||
The documented Helium fixture identity and password-file path are defaults.
|
||||
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_HOST_STAGE_ROOT (default /home/cabbage/.local/state/rvbox-test-runs)
|
||||
RVBOX_TEST_RUN_ROOT (default .test-runs/windows-vm)
|
||||
EOF
|
||||
}
|
||||
|
||||
fail() { printf '%s\n' "test-host: $*" >&2; exit 2; }
|
||||
|
||||
require_env() {
|
||||
eval "value=\${$1-}"
|
||||
[ -n "$value" ] || fail "$1 is required"
|
||||
}
|
||||
|
||||
safe_word() {
|
||||
case $2 in
|
||||
''|*[!A-Za-z0-9._:/@+=,-]*) fail "$1 contains unsupported characters" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
safe_id() {
|
||||
case $1 in
|
||||
[a-z0-9]* ) ;;
|
||||
* ) fail "run ID must start with lowercase alphanumeric" ;;
|
||||
esac
|
||||
case $1 in
|
||||
*[!a-z0-9-]*|????????????????????????????????????????????????????????????????*)
|
||||
fail "run ID must match [a-z0-9][a-z0-9-]{0,63}"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
action=${1-}
|
||||
[ -n "$action" ] || { usage >&2; exit 2; }
|
||||
case $action in --help|-h) usage; exit 0 ;; esac
|
||||
shift
|
||||
|
||||
run_id=
|
||||
bundle=
|
||||
endpoint=
|
||||
while [ "$#" -gt 0 ]; do
|
||||
case $1 in
|
||||
--run-id) [ "$#" -ge 2 ] || fail "--run-id needs a value"; run_id=$2; shift 2 ;;
|
||||
--bundle) [ "$#" -ge 2 ] || fail "--bundle needs a value"; bundle=$2; shift 2 ;;
|
||||
--endpoint) [ "$#" -ge 2 ] || fail "--endpoint needs a value"; endpoint=$2; shift 2 ;;
|
||||
--help|-h) usage; exit 0 ;;
|
||||
*) fail "unknown argument $1" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
case $action in status|prepare|stage|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"
|
||||
fi
|
||||
if [ "$action" = stage ]; then
|
||||
[ -d "$bundle" ] || fail "stage requires an existing --bundle directory"
|
||||
[ -f "$bundle/rvbox.exe" ] || fail "bundle must contain rvbox.exe"
|
||||
[ -f "$bundle/client.toml" ] || fail "bundle must contain client.toml"
|
||||
fi
|
||||
if [ -n "$endpoint" ]; then safe_word endpoint "$endpoint"; fi
|
||||
|
||||
# The Helium smoke fixture is the only supported native lane today. Keep its
|
||||
# non-secret identity and host-local password-file *path* here so a developer
|
||||
# can run the controller without retyping fixture metadata. Operators may
|
||||
# override any value for another recorded fixture. The password itself is
|
||||
# never read by this script and is never stored in the repository.
|
||||
: "${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_GUEST_USER:=rvboxtest}"
|
||||
: "${RVBOX_TEST_GUEST_PASSWORD_FILE:=/home/cabbage/.local/share/rvbox-secrets/rvbox-win10-test.password}"
|
||||
|
||||
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 \
|
||||
RVBOX_TEST_GUEST_USER RVBOX_TEST_GUEST_PASSWORD_FILE; do
|
||||
require_env "$name"
|
||||
done
|
||||
|
||||
safe_word RVBOX_TEST_VBOX_HOST "$RVBOX_TEST_VBOX_HOST"
|
||||
safe_word RVBOX_TEST_VBOX_VM "$RVBOX_TEST_VBOX_VM"
|
||||
safe_word RVBOX_TEST_VBOX_VM_UUID "$RVBOX_TEST_VBOX_VM_UUID"
|
||||
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"
|
||||
|
||||
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}
|
||||
safe_word RVBOX_TEST_HOST_STAGE_ROOT "$host_stage_root"
|
||||
remote_run_id=${run_id:-fixture-status}
|
||||
host_stage=$host_stage_root/$remote_run_id
|
||||
guest_root="C:\\ProgramData\\RVBox\\test-runs\\$remote_run_id"
|
||||
|
||||
remote() {
|
||||
# All values below are constrained words before becoming remote shell
|
||||
# arguments. Password contents are never transmitted or printed; only the
|
||||
# approved host-local password-file path is passed to VBoxManage.
|
||||
remote_endpoint=${endpoint:--}
|
||||
remote_script=/home/cabbage/.local/state/rvbox-test-controller/$remote_run_id.sh
|
||||
ssh -o BatchMode=yes "$RVBOX_TEST_VBOX_HOST" \
|
||||
"install -d -m 700 /home/cabbage/.local/state/rvbox-test-controller && cat > '$remote_script' && chmod 700 '$remote_script'" <<'REMOTE'
|
||||
set -eu
|
||||
trap 'rm -f "$0"' EXIT
|
||||
|
||||
action=$1
|
||||
vm=$2
|
||||
expected_vm_uuid=$3
|
||||
snapshot=$4
|
||||
expected_snapshot_uuid=$5
|
||||
guest_user=$6
|
||||
password_file=$7
|
||||
host_stage=$8
|
||||
guest_root=$9
|
||||
shift 9
|
||||
endpoint=$1
|
||||
[ "$endpoint" = - ] && endpoint=
|
||||
|
||||
fail() { printf '%s\n' "remote test-host: $*" >&2; exit 2; }
|
||||
|
||||
lease_root=$(dirname "$host_stage")/.rvbox-windows-vm-lease
|
||||
lease_owner=$lease_root/run-id
|
||||
run_id=$(basename "$host_stage")
|
||||
|
||||
acquire_lease() {
|
||||
install -d -m 700 "$(dirname "$lease_root")"
|
||||
if mkdir "$lease_root" 2>/dev/null; then
|
||||
umask 077
|
||||
printf '%s\n' "$run_id" >"$lease_owner"
|
||||
return 0
|
||||
fi
|
||||
[ -f "$lease_owner" ] || fail "Windows VM lease is malformed: $lease_root"
|
||||
owner=$(cat "$lease_owner")
|
||||
[ "$owner" = "$run_id" ] || fail "Windows VM is leased by run $owner"
|
||||
}
|
||||
|
||||
require_lease() {
|
||||
[ -f "$lease_owner" ] || fail "Windows VM lease is missing"
|
||||
owner=$(cat "$lease_owner")
|
||||
[ "$owner" = "$run_id" ] || fail "Windows VM is leased by run $owner"
|
||||
}
|
||||
|
||||
release_lease() {
|
||||
require_lease
|
||||
rm "$lease_owner"
|
||||
rmdir "$lease_root"
|
||||
}
|
||||
|
||||
step() {
|
||||
install -d -m 700 "$host_stage"
|
||||
printf '%s %s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$*" >>"$host_stage/controller.steps"
|
||||
}
|
||||
|
||||
vm_field() {
|
||||
VBoxManage showvminfo "$vm" --machinereadable | sed -n "s/^$1=\"\([^\"]*\)\"/\1/p" | head -n 1
|
||||
}
|
||||
|
||||
assert_identity() {
|
||||
actual_vm_uuid=$(vm_field UUID)
|
||||
[ "$actual_vm_uuid" = "$expected_vm_uuid" ] || fail "VM UUID mismatch"
|
||||
actual_snapshot_uuid=$(VBoxManage snapshot "$vm" list --machinereadable | sed -n 's/^CurrentSnapshotUUID="\([^"]*\)"/\1/p')
|
||||
[ "$actual_snapshot_uuid" = "$expected_snapshot_uuid" ] || fail "current snapshot UUID mismatch"
|
||||
}
|
||||
|
||||
state() { vm_field VMState; }
|
||||
|
||||
guest_run() {
|
||||
# This VirtualBox build has no --wait-exit. It can return 33 after a
|
||||
# successful guest process, so each caller must emit RVBOX_GUEST_OK only
|
||||
# after its own assertion succeeds. Never treat the VBoxManage exit code
|
||||
# alone as a guest-command result.
|
||||
output=$(VBoxManage guestcontrol "$vm" --username "$guest_user" --passwordfile "$password_file" \
|
||||
run "$@" </dev/null 2>&1) || true
|
||||
printf '%s\n' "$output"
|
||||
printf '%s\n' "$output" | tr -d '\r' | grep -qx 'RVBOX_GUEST_OK'
|
||||
}
|
||||
|
||||
wait_guest_additions() {
|
||||
attempt=0
|
||||
while [ "$attempt" -lt 60 ]; do
|
||||
properties=$(VBoxManage guestproperty enumerate "$vm" 2>/dev/null || true)
|
||||
if printf '%s\n' "$properties" | grep -q '/VirtualBox/GuestAdd/Version' && \
|
||||
printf '%s\n' "$properties" | grep -q '/VirtualBox/GuestInfo/OS/Release'; then
|
||||
return 0
|
||||
fi
|
||||
attempt=$((attempt + 1))
|
||||
sleep 1
|
||||
done
|
||||
fail "Guest Additions did not publish version and Windows OS-release properties"
|
||||
}
|
||||
|
||||
wait_service() {
|
||||
wanted=$1
|
||||
attempt=0
|
||||
while [ "$attempt" -lt 30 ]; do
|
||||
if guest_run --exe 'C:\Windows\System32\cmd.exe' --wait-stdout --wait-stderr --unquoted-args -- \
|
||||
/d /s /c "sc.exe query RVBoxClient | findstr /c:\"$wanted\" >NUL && echo RVBOX_GUEST_OK" >/dev/null 2>&1; then
|
||||
return 0
|
||||
fi
|
||||
attempt=$((attempt + 1))
|
||||
sleep 1
|
||||
done
|
||||
fail "RVBoxClient did not reach $wanted"
|
||||
}
|
||||
|
||||
case "$action" in
|
||||
prepare-stage)
|
||||
assert_identity
|
||||
require_lease
|
||||
[ "$(state)" = running ] || fail "stage requires a running prepared VM"
|
||||
install -d -m 700 "$host_stage"
|
||||
;;
|
||||
status)
|
||||
assert_identity
|
||||
printf 'vm=%s uuid=%s snapshot=%s state=%s\n' "$vm" "$expected_vm_uuid" "$snapshot" "$(state)"
|
||||
;;
|
||||
prepare)
|
||||
assert_identity
|
||||
acquire_lease
|
||||
step prepare-lease-acquired
|
||||
[ "$(state)" = poweroff ] || fail "prepare requires a powered-off VM; use stop or reset first"
|
||||
VBoxManage snapshot "$vm" restore "$snapshot" >/dev/null
|
||||
step prepare-snapshot-restored
|
||||
assert_identity
|
||||
VBoxManage startvm "$vm" --type headless >/dev/null
|
||||
step prepare-vm-started
|
||||
wait_guest_additions
|
||||
step prepare-guest-additions-ready
|
||||
step prepare-bootstrap-complete
|
||||
;;
|
||||
probe-identity)
|
||||
assert_identity
|
||||
require_lease
|
||||
[ "$(state)" = running ] || fail "identity probe requires a running prepared VM"
|
||||
guest_run --exe 'C:\Windows\System32\cmd.exe' --wait-stdout --wait-stderr --unquoted-args -- \
|
||||
/d /s /c 'whoami /groups & query user & echo RVBOX_GUEST_OK' >/dev/null
|
||||
;;
|
||||
stage)
|
||||
assert_identity
|
||||
require_lease
|
||||
[ "$(state)" = running ] || fail "stage requires a running prepared VM"
|
||||
[ -f "$host_stage/rvbox.exe" ] && [ -f "$host_stage/client.toml" ] || fail "host bundle is incomplete"
|
||||
;;
|
||||
stage-create-root)
|
||||
assert_identity
|
||||
require_lease
|
||||
[ "$(state)" = running ] || fail "stage requires a running prepared VM"
|
||||
guest_run --exe 'C:\Windows\System32\cmd.exe' --wait-stdout --wait-stderr --unquoted-args -- \
|
||||
/d /s /c "if not exist \"$guest_root\" mkdir \"$guest_root\" & echo RVBOX_GUEST_OK" >/dev/null
|
||||
;;
|
||||
stage-copy-exe)
|
||||
assert_identity
|
||||
require_lease
|
||||
VBoxManage guestcontrol "$vm" --username "$guest_user" --passwordfile "$password_file" \
|
||||
copyto "$host_stage/rvbox.exe" "$guest_root\\rvbox.exe" </dev/null
|
||||
;;
|
||||
stage-copy-config)
|
||||
assert_identity
|
||||
require_lease
|
||||
VBoxManage guestcontrol "$vm" --username "$guest_user" --passwordfile "$password_file" \
|
||||
copyto "$host_stage/client.toml" "$guest_root\\client.toml" </dev/null
|
||||
;;
|
||||
stage-copy-ca)
|
||||
assert_identity
|
||||
require_lease
|
||||
[ -f "$host_stage/ca.pem" ] || fail "host bundle has no ca.pem"
|
||||
VBoxManage guestcontrol "$vm" --username "$guest_user" --passwordfile "$password_file" \
|
||||
copyto "$host_stage/ca.pem" "$guest_root\\ca.pem" </dev/null
|
||||
;;
|
||||
run)
|
||||
assert_identity
|
||||
require_lease
|
||||
[ "$(state)" = running ] || fail "run requires a running prepared VM"
|
||||
if [ -n "$endpoint" ]; then
|
||||
endpoint_host=${endpoint%:*}
|
||||
endpoint_port=${endpoint##*:}
|
||||
guest_run --exe 'C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe' --wait-stdout --wait-stderr --unquoted-args -- \
|
||||
-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 -- \
|
||||
/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 -- \
|
||||
/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 -- \
|
||||
/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"
|
||||
wait_service RUNNING
|
||||
printf 'service=RVBoxClient state=RUNNING\n'
|
||||
;;
|
||||
collect)
|
||||
assert_identity
|
||||
require_lease
|
||||
install -d -m 700 "$host_stage/artifacts"
|
||||
if [ "$(state)" = running ]; then
|
||||
guest_run --exe 'C:\Windows\System32\cmd.exe' --wait-stdout --wait-stderr --unquoted-args -- \
|
||||
/d /s /c "sc.exe queryex RVBoxClient > \"$guest_root\\service-status.txt\" 2>&1 & echo RVBOX_GUEST_OK" >/dev/null || true
|
||||
VBoxManage guestcontrol "$vm" --username "$guest_user" --passwordfile "$password_file" \
|
||||
copyfrom "$guest_root" "$host_stage/artifacts" --recursive </dev/null >/dev/null 2>&1 || true
|
||||
fi
|
||||
printf 'collected host_stage=%s/artifacts\n' "$host_stage"
|
||||
;;
|
||||
stop)
|
||||
assert_identity
|
||||
require_lease
|
||||
if [ "$(state)" = running ]; then
|
||||
guest_run --exe 'C:\Windows\System32\cmd.exe' --wait-stdout --wait-stderr --unquoted-args -- \
|
||||
/d /s /c 'sc.exe stop RVBoxClient >NUL 2>&1 || exit /b 0 & echo RVBOX_GUEST_OK' >/dev/null || true
|
||||
VBoxManage controlvm "$vm" acpipowerbutton >/dev/null
|
||||
attempt=0
|
||||
while [ "$attempt" -lt 60 ]; do
|
||||
[ "$(state)" = poweroff ] && break
|
||||
attempt=$((attempt + 1))
|
||||
sleep 1
|
||||
done
|
||||
[ "$(state)" = poweroff ] || fail "guest did not power off after ACPI request"
|
||||
fi
|
||||
printf 'stopped vm=%s\n' "$vm"
|
||||
;;
|
||||
reset)
|
||||
assert_identity
|
||||
require_lease
|
||||
if [ "$(state)" = running ]; then
|
||||
VBoxManage controlvm "$vm" acpipowerbutton >/dev/null
|
||||
attempt=0
|
||||
while [ "$attempt" -lt 60 ]; do
|
||||
[ "$(state)" = poweroff ] && break
|
||||
attempt=$((attempt + 1))
|
||||
sleep 1
|
||||
done
|
||||
[ "$(state)" = poweroff ] || fail "guest did not power off before reset"
|
||||
fi
|
||||
VBoxManage snapshot "$vm" restore "$snapshot" >/dev/null
|
||||
assert_identity
|
||||
release_lease
|
||||
printf 'reset vm=%s snapshot=%s\n' "$vm" "$snapshot"
|
||||
;;
|
||||
recover)
|
||||
assert_identity
|
||||
if [ -f "$lease_owner" ]; then
|
||||
printf 'recoverable vm=%s state=%s stage=%s lease_owner=%s\n' "$vm" "$(state)" "$host_stage" "$(cat "$lease_owner")"
|
||||
else
|
||||
printf 'recoverable vm=%s state=%s stage=%s lease_owner=none\n' "$vm" "$(state)" "$host_stage"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
REMOTE
|
||||
ssh -o BatchMode=yes "$RVBOX_TEST_VBOX_HOST" sh "$remote_script" \
|
||||
"$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" </dev/null
|
||||
}
|
||||
|
||||
case $action in
|
||||
prepare)
|
||||
remote prepare
|
||||
printf 'prepared vm=%s stage=%s\n' "$RVBOX_TEST_VBOX_VM" "$host_stage"
|
||||
;;
|
||||
stage)
|
||||
remote prepare-stage
|
||||
scp -q "$bundle/rvbox.exe" "$bundle/client.toml" "$RVBOX_TEST_VBOX_HOST:$host_stage/"
|
||||
if [ -f "$bundle/ca.pem" ]; then scp -q "$bundle/ca.pem" "$RVBOX_TEST_VBOX_HOST:$host_stage/"; fi
|
||||
remote stage
|
||||
remote stage-create-root
|
||||
remote stage-copy-exe
|
||||
remote stage-copy-config
|
||||
if [ -f "$bundle/ca.pem" ]; then remote stage-copy-ca; fi
|
||||
printf 'staged guest_root=%s\n' "$guest_root"
|
||||
;;
|
||||
collect)
|
||||
remote collect
|
||||
local_artifacts=$run_root/$run_id/artifacts
|
||||
mkdir -p "$local_artifacts"
|
||||
scp -q -r "$RVBOX_TEST_VBOX_HOST:$host_stage/artifacts/." "$local_artifacts/" 2>/dev/null || true
|
||||
printf 'artifacts=%s\n' "$local_artifacts"
|
||||
;;
|
||||
*) remote "$action" ;;
|
||||
esac
|
||||
Reference in New Issue
Block a user