Files
rvbox/test/rdp-access

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.

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_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.

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.

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.

The helper requires Docker/Docker Compose, 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/.