15 KiB
Interactive Windows VM access
This directory is an explicitly temporary, manual-recovery path to the Helium
Windows fixture desktop. It is for the small class of actions that require an
interactive UAC consent dialog. Normal setup, testing, collection, and reset
remain scripts/windows/test-host plus VirtualBox Guest Control.
The helper builds this private path:
browser -- HTTPS/self-signed --> nginx + Guacamole containers
(IPv4 and, in public mode, a tracked IPv6 forward)
|
private Docker gateway
|
controller SSH watchdog/tunnel --> Helium 127.0.0.1:3389 --> VirtualBox VRDE --> VM console
Only the HTTPS listener can be made public, and that requires an explicit
--bind 0.0.0.0. The VirtualBox VRDE endpoint stays on Helium loopback and the
SSH tunnel binds only to the Docker network gateway; neither is publicly
exposed. Guacamole requires the fixture login before it forwards the entered
password to the VM. Clipboard, drives, printing, audio, microphone input, GFX,
and display-resize extensions are disabled because the fixture's VirtualBox
RDP4 server does not handle them reliably.
Current fixture quick start
The following is the copy/paste path for the documented rvbox-win10-test
fixture. Run it from the repository root. The first command acquires the
exclusive VM lease, restores the clean administrator-enabled snapshot, starts
the VM, and applies the VRDE display keepalive. The second command starts the
Docker-only gateway at the currently published endpoint:
scripts/windows/test-host prepare --run-id interactive-rdp
test/rdp-access/rdp-access up \
--bind 0.0.0.0 \
--public-host x1.xcel.me
up prompts for the fixture password. If the password is not already in the
operator's secure notes, the test-only value is available only in the Helium
host's mode-600 file documented in docs/testing-vm.md.
For an unattended first start, pass that file through the existing SSH alias
without putting the value in an argument, environment variable, or log:
ssh helium-remote 'cat /home/cabbage/.local/share/rvbox-secrets/rvbox-win10-test.password' |
test/rdp-access/rdp-access up \
--bind 0.0.0.0 \
--public-host x1.xcel.me \
--web-password-stdin
The browser URL is https://x1.xcel.me:5002/guacamole/; accept the temporary
self-signed certificate warning and sign in as rvboxtest. The same command
is safe to rerun after a tunnel interruption. If the stack is already running,
up reuses it and repairs only missing forwarding; it does not consume stdin.
Use --reset-auth only after down when intentionally replacing the saved
verifier, for example:
test/rdp-access/rdp-access down
ssh helium-remote 'cat /home/cabbage/.local/share/rvbox-secrets/rvbox-win10-test.password' |
test/rdp-access/rdp-access up \
--bind 0.0.0.0 \
--public-host x1.xcel.me \
--reset-auth \
--web-password-stdin
Do not run clean or test-host reset while another agent owns the fixture
lease or has a live browser session. The generated verifier, certificate,
tunnel state, and logs are disposable and live only under the ignored
test/rdp-access/.runtime/ directory.
Before a first prepare, run scripts/windows/test-host status. If it reports
state=running, another run may already own the fixture lease; do not restore
or reset it. Reuse that run's access path or coordinate with its owner. If it
reports state=poweroff, the quick-start sequence above is safe. The helper
does not guess at ownership or silently take over a running VM.
Lifecycle
Run commands from the repository root. First prepare the disposable VM using a dedicated run ID. This restores the snapshot, starts the VM headlessly, and holds the exclusive fixture lease while the manual action is in progress:
scripts/windows/test-host prepare --run-id interactive-rdp
For browser access from another machine, deliberately expose the temporary HTTPS listener and name the host or IP users will enter in the browser:
test/rdp-access/rdp-access up \
--bind 0.0.0.0 \
--public-host x1.example.net
The command prompts without echo for the fixture password. It stores only its
MD5 verifier in test/rdp-access/.runtime/config/user-mapping.xml, mode 600;
the plaintext password is not placed in a command line, environment variable,
log, or repository file. Browse to the printed https://.../guacamole/ URL,
accept the short-lived self-signed certificate warning, and sign in as
rvboxtest with the fixture password.
For localhost-only use, omit --bind and --public-host. The default listener
is 127.0.0.1:5002; use a local SSH forward or a browser on the controller.
Choose alternate ports with --http-port and --tunnel-port if either is in
use. The VM must already be running. up checks the documented VM/snapshot
identity but intentionally does not restore, start, stop, or reset the VM. It
starts a small host-side watchdog for the SSH master. Before printing the URL,
up waits until the HTTPS gateway can serve /guacamole/; this prevents a
browser login from racing Tomcat's Guacamole WAR deployment. The watchdog
reconnects after a transient Helium/SSH failure while preserving the same
Docker-gateway listener, so an already-open Guacamole session can recover
without restarting the containers. Its PID, stop marker, and diagnostic log
are kept under the ignored .runtime/ directory.
All fixture-specific values have embedded, working defaults: the
helium-remote SSH alias, Helium's loopback VRDE endpoint (127.0.0.1:3389),
rvboxtest, the browser listener (127.0.0.1:5002), and the private tunnel
port (54001). They can be overridden without editing tracked files through
RVBOX_TEST_VBOX_HOST, RDP_ACCESS_VRDE_HOST, RDP_ACCESS_VRDE_PORT,
RDP_ACCESS_PROFILE,
RDP_ACCESS_WEB_USER, RDP_ACCESS_RDP_USER, RDP_ACCESS_BIND,
RDP_ACCESS_HTTP_PORT, RDP_ACCESS_TUNNEL_PORT, and
RDP_ACCESS_PUBLIC_HOST, RDP_ACCESS_IPV6_BIND, and
RDP_ACCESS_IPV6_FORWARD. The helper deliberately limits the VRDE host to
Helium loopback (127.0.0.1 or localhost) so an override cannot accidentally
turn the diagnostic server into a remote target.
Set RDP_ACCESS_GUACD_LOG_LEVEL=debug temporarily when collecting detailed
guacd/RDP negotiation diagnostics; the default is info.
The VM selector and the gateway profile are separate. Set the matching
RVBOX_TEST_VBOX_* identity variables (and RDP_ACCESS_VRDE_PORT) for the
VM you want; the complete identity set is listed in
docs/testing-vm.md. For two already-running VMs,
give the second gateway a distinct profile and listener ports, for example:
RDP_ACCESS_PROFILE=vm-b \
RVBOX_TEST_VBOX_HOST=helium-remote-b \
RDP_ACCESS_VRDE_PORT=3391 \
RDP_ACCESS_HTTP_PORT=5003 \
RDP_ACCESS_TUNNEL_PORT=54002 \
test/rdp-access/rdp-access up --bind 0.0.0.0 --public-host vm-b.example.net
Replace helium-remote-b, 3391, and the public hostname with the second
fixture's recorded values, and export its matching VM/snapshot UUID variables
before running up or status. A non-default profile stores its verifier,
certificate, tunnel state, and logs under .runtime/<profile>/ and uses the
Compose project rvbox-rdp-access-<profile>. Keep the HTTP port, public
hostname, and profile unique so IPv4/IPv6 listeners and browser sessions do
not collide. Inspect, repair, stop, or clean that instance by repeating the
same RDP_ACCESS_PROFILE and endpoint variables on status, repair,
down, or clean.
Profile isolation does not bypass the native controller's exclusive lease:
test-host prepare/reset must still be coordinated when VMs share one
fixture host and staging root. For VMs that are already running, the profile
and matching identity/VRDE variables are sufficient to select the endpoint.
When --bind 0.0.0.0 is used, up also starts a tracked socat listener on
[::]:$RDP_ACCESS_HTTP_PORT and forwards it to the IPv4 gateway listener. This
matters when the public hostname has an AAAA record: some browsers choose IPv6
for the WebSocket even if the initial page used IPv4. The listener is recorded
under .runtime/ipv6-forward.pid and is removed by down and clean. Its
default bind is :: (RDP_ACCESS_IPV6_BIND); set
RDP_ACCESS_IPV6_FORWARD=never to deliberately use IPv4 only, always to make
IPv6 startup a hard requirement, or leave the default auto for a warning and
an IPv4-only fallback when socat is unavailable. status reports
ipv6_forward=active, active_external, inactive, or disabled.
If up is run again while the Compose services are still running, it is
idempotent: an existing healthy tunnel is reused, and a missing tunnel is
recreated in place. status reports a helper-owned tunnel as
private_tunnel=active and a listener supplied by an external/interactive
SSH supervisor as private_tunnel=active_external. A missing listener is
reported as private_tunnel=inactive; inspect .runtime/tunnel.log and run
up again to trigger a bounded reconnect attempt.
If up reports that an existing gateway has a different configuration, or a
previous start left only part of the Compose stack, run down once and then
repeat up. Changing the bind address, browser host, or either port likewise
requires down first; refusing to mutate a live stack prevents an old browser
session from silently reaching a different endpoint.
The XML mapping intentionally uses Guacamole's ${GUAC_PASSWORD} connection
parameter token. Guacamole resolves this to the password entered at web login;
it is not a host environment variable and must remain in the template.
After the interactive action, close the browser connection and remove the temporary access path before releasing the fixture lease:
test/rdp-access/rdp-access down
scripts/windows/test-host reset --run-id interactive-rdp
down stops containers and the SSH watchdog/master/tunnel but retains the
one-day certificate and password verifier for a quick restart. To remove all
generated state, including the certificate, verifier, watchdog PID, and tunnel
log:
test/rdp-access/rdp-access clean
To also reclaim the exact Guacamole and nginx images when they have no container dependency, use:
test/rdp-access/rdp-access clean --images
clean --images deliberately leaves alpine:3.20 alone because it may be
shared by unrelated containers. Docker will refuse removal if any other
container still depends on an image.
Operational checks and recovery
test/rdp-access/rdp-access status
test/rdp-access/rdp-access logs --tail=100
test/rdp-access/rdp-access url
If the browser reaches Guacamole but stays on “Waiting for response”, run
status first. Confirm private_tunnel=active (or
active_external) and, for a public dual-stack hostname,
ipv6_forward=active (or a known-good external forward). Then check the VM
state and the last lines of
.runtime/tunnel.log. A tunnel can be recreated without losing the Compose
stack by running up again. This helper already uses security=rdp and
disables Guacamole's GFX extension, which are required by the fixture's legacy
VRDE server. Do not switch the helper to native Windows RDP:
TermService is intentionally disabled in the baseline. If VRDE remains
unusable, stop this helper and use Guest Control for the deterministic portion
of the work; record the blocked interactive step in the native test report.
If guacd has a stale worker and the VM reports that multiple connections are disabled, run:
test/rdp-access/rdp-access repair
repair restarts only guacd, retains the VM, SSH tunnel, gateway, and IPv6
forward, and does not alter the Windows guest. Close old browser tabs and sign
in again after it completes.
For quick recovery, use this decision table:
| Need | Command | Effect |
|---|---|---|
| Inspect everything | test/rdp-access/rdp-access status |
Read-only VM, Compose, tunnel, and IPv6-forward state |
| Start or reconnect access | test/rdp-access/rdp-access up --bind 0.0.0.0 --public-host x1.xcel.me |
Reuses healthy services; recreates only missing tunnel/forward |
| Browser says “Waiting for response” | test/rdp-access/rdp-access status; test/rdp-access/rdp-access logs --tail=100; if guacd is stale, test/rdp-access/rdp-access repair |
Diagnoses tunnel/dual-stack issues; restarts only guacd |
| Partial stack or changed endpoint | test/rdp-access/rdp-access down, then test/rdp-access/rdp-access up --bind 0.0.0.0 --public-host x1.xcel.me |
Recreates the exact project with the new settings |
| Print the current URL | test/rdp-access/rdp-access url |
Read-only URL from the saved session |
| Stop temporary access | test/rdp-access/rdp-access down |
Stops gateway, IPv6 forward, SSH watchdog/tunnel; retains verifier/cert |
| Reclaim generated state | test/rdp-access/rdp-access clean |
Stops access and removes only .runtime/ |
| Reclaim exact unused images too | test/rdp-access/rdp-access clean --images |
Also removes owned Guacamole/nginx images; leaves shared Alpine intact |
| Return VM to clean baseline | scripts/windows/test-host reset --run-id interactive-rdp |
Stops/ restores the exact leased VM snapshot and leaves it powered off |
The two scripts have a deliberate ownership boundary. rdp-access manages only
the temporary browser gateway, Guacamole/guacd containers, IPv6 forward, and
Helium SSH tunnel. scripts/windows/test-host manages the leased VM and its
service lifecycle. For a prepared run, the remaining VM-side management actions
are:
scripts/windows/test-host inspect --run-id interactive-rdp # SCM/read-only client state
scripts/windows/test-host logs --run-id interactive-rdp # bounded startup/client logs
scripts/windows/test-host collect --run-id interactive-rdp # bounded artifacts
scripts/windows/test-host stop --run-id interactive-rdp # stop service and request guest shutdown
scripts/windows/test-host recover --run-id interactive-rdp # inspect an interrupted stopped run
stage, install, run, and logoff are also available in the
test-host --help command reference for a complete native-service run; they
are not required merely to use the RDP gateway. native-test recover and
native-test clean are the corresponding whole-lane recovery/cleanup actions
when the Linux server stack is part of the run.
The helper requires Docker/Docker Compose with Docker socket access for the
invoking account (docker version must succeed), socat for the optional
public dual-stack forward, SSH access through the existing helium-remote
alias, and the fixture password file documented in
docs/testing-vm.md. It does not install host
packages, write credentials into Git, or alter VM identity/settings. The
authoritative test-host prepare step applies only the disposable
display/sleep keepalive needed by VirtualBox VRDE; all generated content is
ignored beneath .runtime/.