Replace Linux workspace noVNC with Xpra HTML5 - #1141
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7dc3e35cb0
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
|
I don't understand why we need CTFd changes. This should all live entirely in the workspace. It'll be great, this PR can remove all the special case stuff that does currently exist for desktop (which is much less than this PR is attempting to do). Also desktop.nix should not need to be so crazy. All of this |
|
Also what are the primary non-wall-of-LLM text reasons we wanted to switch to Xpra again? |
|
Addressed on current head:
Why Xpra, briefly:
Final evidence is on product commit Across the 10 matched workload intervals that fully drained, Xpra recorded 16.53% fewer combined bytes, though the result varied by profile and the poor estimate has n=1. Two poor intervals are retained as censored lower bounds. The PR body has p95/max latency, per-profile traffic, methods, limitations, and matched screenshots. It also discloses a later longer-ceiling run that was excluded wholesale after system-wide stalls and a VM crash, without dropping its adverse samples selectively. |
Why Xpra
desktop-windowsis unchanged.Implementation and security
Xpra 6.4.4 and its HTML5 v20 client run on port 6080 inside the learner workspace. The listener uses
auth=noneand is not published as a public port. CTFd gives the authenticated requester an HMAC-signed bearer URL covering the workspace target and port; Nginx validates it before proxying. The existing network policy blocks direct peer-workspace connections. As with other workspace URLs, anyone who receives the signed URL can use it while its target exists. Requests using the API's top-leveluser=parameter remain admin-only.The only CTFd change is 2 additions and 33 deletions: it removes the old Linux desktop password/view-only special case and sends
service=desktopthrough the existing generic workspace route. There are no Xpra-specific CTFd dependencies, credentials, certificates, or Nginx routes.Xpra does not inherit
DOJO_AUTH_TOKEN. Command, shell, control, file-transfer, printing, open-file/open-URL, audio, and webcam features are disabled. The workspace service also serializes startup, checks readiness, removes legacy noVNC state, and recovers a recorded orphaned Xorg after an abrupt exit.Normal HTML client settings use
default-settings.txt; there is nosubstituteInPlace. Two small tracked patches cover failures reproduced during degraded-link testing:keymap.sync=falseleaves repeat handling with the browser. A focused poor-link probe completed exact held-key and Shift-modified sequences with no trailing keys.PING_GRACEsetting. Successfully handled inbound packets affect only the soft warning; hard reconnect logic still depends only on ping echoes. This prevents active display traffic on a saturated 1 Mbit/s, 1% loss link from being mistaken for a dead server. After at least one successful ping, a completely silent connection is marked unhealthy after roughly 30–35 seconds and triggers reconnect after roughly 60–65 seconds; the new attempt begins about one second later. An explicit WebSocket close enters the reconnect path without waiting for those timers.Verification
28539e9f9a9ca1975ddf4aedf6fcb30730559ca0f507883428bf9f69c7d39f7de38afa7d8f8e2ff4(the additional commit only hardens benchmark evidence preservation, validation, and aggregation)nix build --no-link --print-out-paths ./workspace#core: passed; the closure contains Xpra and no noVNC/TigerVNCEvery trial checked connection, a visible frameless desktop, resize/restore, trusted remote input, exact terminal input, a 30-second/300-update workload, sampled browser delivery, recovery after a two-second client-interface interruption, and pre/post-workload quiescence. Xpra trials also checked server-confirmed resize and both Unicode clipboard directions; the noVNC resize check covered the changed browser surface rather than remote X/RandR state. The clipboard probe used X11
xclip, Xpra's internal buffer, and a mocked browser Clipboard API, so it covers the Unicode protocol/X11 round trip rather than browser permission UI or the host OS clipboard. Clipboard was not supported by the noVNC harness and was not counted among its common checks.Latency
Latency is the conservative browser-observed upper bound from a trusted Firefox keydown to the first decoded canvas observation of a calibrated terminal marker, confirmed by the next frame. Each entry pools 60 low-traffic observations across three trials; these are not physical input-to-photon measurements or network RTT.
Xpra's p50 and p95 were higher in every profile, so this is a measurable responsiveness regression even though the page, desktop workflow, and feature checks remain intact. The canonical Xpra maximum was 1,104 ms. noVNC also produced retained long-tail samples of 2,237 ms on mobile and 1,267 ms on poor. No canonical-matrix sample was discarded, and every one passed the trusted/exact-input checks.
Workload traffic
Traffic is the gross client-
eth0counter delta from workload input through the first two-second quiet interval with empty local shaping queues. It includes protocol overhead, retransmission, background traffic, and drain; interval rates are averages, not codec payload rates or link capacity. Byte totals are median [min-max], and rates are medians within the available complete trials.The canonical run reached that endpoint in 10/12 intervals. Poor repetitions 2 and 3 were right-censored after drain durations of 31.218 and 31.579 seconds, with 165,554 bytes/67 packets and 14,160 bytes/14 packets still queued. Their captured values—at least 6,597,096 RX/149,804 TX bytes and 6,993,244 RX/181,776 TX bytes—are lower bounds and are excluded from the table and aggregate. Complete paired sample sizes are n=3 for clean, WAN, and mobile, and n=1 for poor.
Longer drain-limit run and VM failure
A later exploratory run raised only the drain ceiling to 45 seconds. It is excluded wholesale from the primary aggregates because cross-profile system stalls and a subsequent guest crash made attribution indeterminate. Its first eight trials passed. In repetition 3, mobile contained an 11,524 ms trusted/exact latency sample alongside a 5,382.72 ms rAF stall. Poor had a 0.713-second WebSocket reconnect, two canvas-read gaps, and missed its drain ceiling by 0.093 seconds. Clean accepted the exact key remotely but did not decode its marker within 15 seconds while the probe was blocked in ext4 writeback. WAN later passed with a 3,012 ms maximum and 1,399.28 ms rAF gap. The console then showed that Xorg, Firefox, systemd-journald, and kernel-worker tasks were blocked for more than 122 seconds before the VM powered off. These outcomes are retained as unresolved failure evidence; none were selectively removed as outliers.
Across the 10 matched, fully drained intervals, Xpra recorded 483,738,161 RX and 5,674,495 TX bytes (489,412,656 combined), versus noVNC's 579,402,679 RX and 6,922,980 TX bytes (586,325,659 combined): 16.51% less RX, 18.03% less TX, and 16.53% less combined traffic. Eligibility is based on Xpra reaching the drain gate; the corresponding noVNC poor repetitions 2 and 3 are therefore excluded as well. Combined per-profile changes were -25.75% clean, +3.41% WAN, -31.00% mobile, and +84.74% poor. This raw sum is dominated by the higher-volume clean trials. With a 3/3/3/1 profile mix and one poor-link pair, it is not an equal-profile average, link-capacity estimate, or general poor-link claim.
Measurement method and limitations
Both variants used the same libvirt VM, benchmark setup, disposable browser image, 1440x900 viewport, Firefox 140.15 ESR, geckodriver 0.36, shaping profiles, 20 latency transitions per trial, two warmups, and cyclic profile order. Each variant reused one warm workspace/display session. The variants ran serially rather than interleaved, so time and cache state are possible confounders. Shaping was symmetric client egress plus IFB ingress. The configured delay is not an independently measured internet RTT, and loss is applied independently in each direction.
Latency ran after connect, resize, clipboard, a three-second settle, and a traffic quiet gate, but before the deliberate outage and workload. It therefore describes low-traffic interaction, not loaded or recovery latency. The marker is issued by one Bash
printf, which does not imply one PTY, transport, codec, or display frame. The final three-second exact-input guard does not rule out arbitrarily late duplication.The results are descriptive: 60 observations per profile and client are clustered within three serial trials in cyclic profile order, without confidence intervals or significance claims. The two-second interface interruption probes later transport, input, and canvas delivery; it does not force the WebSocket to close or reconnect. Browser delivery means changing sampled canvas states, not visual fidelity or continuous frame accounting. Workload bytes are interface counters rather than codec payload measurements, so this is controlled comparative evidence, not a production bandwidth forecast. Both sessions used
workspace_home=falsebecause of a benchmark-guest homefs issue; the desktop protocol path was unchanged.Measurement harness as run SHA-256:
12607296860b38cd1020db68421894cd11865042f9daa17023902d685abb818fCensor-aware analyzer SHA-256:
d89f839606394be8d07e12d60d3c714d16c2707a7fd82e746bee3fa740baaf9f; output:a1cce599f64d07c0aa64af9d6fcb6a7c40e71be69fb6da4d5a7476a8d0ddfb1cXpra results/summary/manifest SHA-256:
1bd09c37439de4ef7d800919e24c887686eb0d46383304b80cf756b59cbfee56/6a1ace47c05ba963b6cd771b2f174f2b3b58c4142eb0ed7095756957acc60baf/475f2236901f692fcfdb946c3d5d64be7a38717c6d65b566f0320ea2a6b1ec8anoVNC results/summary/manifest SHA-256:
fc13929c626d0d3143a00b237b0ff1a4baf7b59c0d7b41c90b084d8b60eec61f/47792860c41ec6456fed36391ca06e3b2f26a8569f2c4d045dab31b7305387aa/c47309ead2d199973cd4d59c801e28d43a6ce4f7b0df15734988a5b9b464765aExploratory-run manifest/recovered-trials/diagnosis SHA-256:
5c041eeada40c4bac80057b8c5e8ae32c8eb2f140eb88ddc60877f355597b64c/a705ad4e8d2b83b16fe4c7eb332139044e995423abf0d4413f8254aa642f35f4/f8024ee7d4da1381ad522312c35a23db41c8bc29142dc4be9017a9a355716d98Visual parity
These clean-link 1440x766 captures use the same 1440x900 browser-window setting. They were taken at visual implementation commit
2606d053; later commits change keyboard, liveness, hardening, and tests rather than the layout. They demonstrate the same workspace page and XFCE desktop chrome; clock text, generated workload content, pointer position, and focus are live state.