Experimental Ubuntu/Debian host testing

This is an experimental x86_64 EXE host path alongside macOS. It does not install Ubuntu/Debian into the target image or certify SteamOS/NVIDIA hardware. Ownership remains defined by BOUNDARIES.md. Core supplies compatibility policy and authenticated contracts; EXE owns host adapters and managed disposable appliances.

Ubuntu 24.04.4 is the host version used for local graphical testing. Debian 12.15 amd64 is covered by a headless CI build in the exact official debian:12.15-slim image pinned by platform-manifest digest. That Debian check runs the real /etc/os-release host inventory with explicit TCG, builds the debug test package against Debian’s glibc 2.36 and OpenSSL 3 baseline, and inspects it, then installs and purges it inside that disposable Debian container while verifying package state, installed binary bytes, desktop-entry identity, and residue-free cleanup. CI also launches the installed debug application in an isolated Xvfb and D-Bus session beneath a child-process subreaper, exercises the bounded AT-SPI unavailable-host, chooser, Settings, compatibility-dialog, focus, process-group, and QEMU-orphan checks, then purges the package even when the graphical check fails. A physical Debian graphical desktop, managed-appliance boot, KVM, and hardware remain unvalidated. A development binary and the extracted debug-package binary have launched and closed in an Ubuntu 24.04.4 Wayland session, with no remaining launcher or EXE processes. The package was not installed. Tauri capabilities are scoped per window: the main window owns only its event, window inventory/focus/drag, dialog-open, and URL-open calls; maintainer owns event, hide/drag, and dialog-open calls; build progress owns event and hide/drag calls. No window receives a broad core, dialog, or opener default. The packaged Ubuntu smoke validates the main window after this split. Its CSP loads only bundled resources and Tauri IPC, retains inline styles for dynamic progress/log presentation, and denies objects, forms, frames, base changes, and all other sources. Shared controls also honor reduced motion and forced colors without hiding focus, status, checkbox, or disabled state. GNOME denied noninteractive screenshot access, so pixel rendering, delivered key-event traversal, companion windows, desktop integration, and interactive close remain unvalidated. WebKit exposes no AT-SPI EditableText interface for the resolver text field, so automated pasted-input entry remains unvalidated. The former HTML file control did not open its chooser through AT-SPI; the inspector now uses Tauri’s native chooser, and the packaged smoke validates that chooser, its JSON-only filter, cancellation, and restored opener focus. AT-SPI has validated the main native frame, WebKit Settings/compatibility controls, exact Settings and compatibility-dialog focus order, debug generation row names and values, scoped dialog close, and main-document survival on Ubuntu Wayland. A focused renderer test requires the accessible status label to follow loading, result, error, and clear without retaining stale text. A separate no-screenshot Google Chrome 152.0.7977.82 check loads the actual application CSS from the main, build-progress, maintainer, shared-control, and compatibility-inspector surfaces. It first proves regular mode does not report forced colors, then starts a fresh isolated headless profile with forced high contrast and network resolution disabled:

"$OPEMOS_HEAVY" env PATH="/path/to/node/bin:$PATH"   node scripts/check_forced_colors_browser.mjs

Computed styles must prove forced-colors: active, remove decorative body layers and backdrop filters, retain one-pixel control/status borders without shadows, preserve a two-pixel focus outline and full disabled opacity, and use one CanvasText dialog-border color. The disposable document and profiles are removed on every success or failure path; no screenshot, desktop capture, application launch, or network input is used. The parser/state regressions run in the frontend suite, while the live browser invocation remains local because the mandated host heavy.sh wrapper is unavailable on GitHub runners.

These host checks do not establish a successful appliance boot, complete image build, installed package, or physical-hardware result. Consult TODO for the exact validation evidence and remaining gates.

Preview the installation-media welcome flow

Run ./test_welcome_linux.sh from an existing X11 or Wayland session. The launcher selects the first installed browser in the fixed Google Chrome/Chromium order, gives it a disposable profile and XDG state, and serves the real welcome frontend from the loopback-only mock controller. Closing the preview or sending a termination signal stops both processes and removes that state. Set OPEMOS_GRAPHICAL_TEST_PRINT_ONLY=1 to print the mock-controller contract without requiring Linux, a display, or a browser. The preview uses synthetic disks and never requests privileges, inspects storage, starts QEMU, or invokes an installer.

Install development prerequisites

Use Rust with Cargo and Node.js 22, including npm. For Ubuntu 24.04 or a Debian environment providing WebKitGTK 4.1, the package prerequisites are:

OPEMOS_HEAVY="/home/connor/Documents/ChatGPT/Handoff troubleshooting/opemos-scheduler/heavy.sh"
"$OPEMOS_HEAVY" sudo -n apt-get update
"$OPEMOS_HEAVY" sudo -n apt-get install --yes --no-install-recommends \
  build-essential pkg-config curl wget file libssl-dev liblzma-dev \
  libwebkit2gtk-4.1-dev libxdo-dev librsvg2-dev \
  libayatana-appindicator3-dev patchelf xdg-utils \
  qemu-system-x86 qemu-utils ovmf genisoimage openssh-client python3

These are explicit host installation commands; the doctor below never installs packages, changes permissions, joins groups, or changes network configuration. Run them only when host package installation is authorized. A missing package on another distribution version is a setup blocker, not evidence of support. Keep builds as your ordinary user.

The runtime needs a matched OVMF pair under /usr/share/OVMF: either OVMF_CODE_4M.fd plus OVMF_VARS_4M.fd, or the legacy OVMF_CODE.fd plus OVMF_VARS.fd. Do not mix pairs or substitute secure-boot variants. The writable variable store belongs to the disposable appliance session; never modify the installed template.

Inspect and launch

From the repository root, select one explicit acceleration mode:

export OPEMOS_EXPERIMENTAL_LINUX=1
export OPEMOS_LINUX_ACCEL=kvm
bash scripts/check_linux_host.sh

KVM needs read/write access to /dev/kvm. Access alone does not prove KVM usability: the application must pass its runtime ioctl probe. The doctor only inventories prerequisites and returns a nonzero status for missing entries. It cannot authenticate appliances, authorize Core actions, or establish output trust. There is no automatic software fallback after a failed KVM launch.

For explicitly slower software-only testing:

export OPEMOS_EXPERIMENTAL_LINUX=1
export OPEMOS_LINUX_ACCEL=tcg
bash scripts/check_linux_host.sh

The OPEMOS_DOCTOR_* environment overrides are solely for isolated script tests; they neither configure nor authorize the application’s runtime backend.

On this coordinated development host, all compilation, large tests, packaging, compression, and QEMU work must use the shared scheduler wrapper:

OPEMOS_HEAVY="/home/connor/Documents/ChatGPT/Handoff troubleshooting/opemos-scheduler/heavy.sh"
"$OPEMOS_HEAVY" npm ci
"$OPEMOS_HEAVY" npm run dev:linux-test

Launch from a graphical desktop session. Exit 75 means the shared resource slot is occupied: wait for scheduler coordination or do light work. Do not retry-loop, bypass the wrapper, increase its limits, or run builds as root. Appliance operations require an already provisioned, appropriately authenticated Fedora appliance; installing QEMU does not provision or authenticate it. Missing appliance state must remain unavailable rather than trigger an unreviewed image download.

The Linux entry point requires both explicit environment choices above, an x86_64 Linux host, and a graphical session for development launch. The experimental launcher and packaged GUI smoke force WEBKIT_DISABLE_DMABUF_RENDERER=1: on the validated GNOME Wayland/NVIDIA host, WebKitGTK exposed a complete AT-SPI tree but presented a blank captured surface without this renderer guard. Runtime Ubuntu/Debian discovery and all appliance/Core checks still apply. Unsupported extra CLI arguments are rejected. The separate test configuration uses an opaque main window and its own application identifier; macOS defaults remain unchanged. The launcher snapshots the exact pre-launch bytes and modes of Cargo.toml and the Linux capability schema path. After normal exit, signal handling, or child failure, it waits for the isolated process group to disappear, restores preexisting files, and removes only a schema proven absent before launch. A launcher SIGKILL can still bypass this in-process restoration.

Create local Ubuntu-built debug Debian and AppImage artifacts without installing either artifact:

"$OPEMOS_HEAVY" npm run build:linux-test
"$OPEMOS_HEAVY" npm run test:package-linux
"$OPEMOS_HEAVY" python3 scripts/check_linux_appimage.py

The AppImage check requires exactly one regular executable x86_64 AppImage, uses extract-and-run mode so FUSE is not required, and invokes only the packaged resolve-core-driver command. It hash-binds the closed local Core fixture and verifies the returned artifact and exact target. It also proves that a bad candidate digest, an exact-kernel mismatch, and two different compatible decisions all fail closed inside the packaged executable. It does not open a window, access the network, download a driver, or activate anything. The AppImage remains an experimental unsigned debug artifact and is not published.

CI separately builds the same debug-only application in the pinned Debian 12.15 amd64 container with:

export OPEMOS_EXPERIMENTAL_LINUX=1
export OPEMOS_LINUX_ACCEL=tcg
npm run build:debian12-test
python3 scripts/check_linux_packaging.py --expected-libc 2.36 --expected-openssl libssl3
python3 scripts/check_linux_appimage.py

The Debian-specific configuration changes only the declared glibc/OpenSSL baseline and is guarded against window, identity, target, and description drift from the regular Linux test configuration. The image digest and both dependency expectations are asserted by documentation tests. This path makes network requests only while CI installs Debian build dependencies; the built archive is never installed, launched, signed, or published.

The package check extracts only this locally generated archive into a temporary directory. It checks metadata, amd64 ELF identity, the exact Tauri bundle-marker patch, shared-library resolution, archive permissions, the desktop entry, and absence of maintainer scripts. It does not install the package or launch its GUI.

From a graphical desktop, extract the package to a disposable directory and run the bounded accessibility smoke against its exact binary path:

dpkg-deb -x 'src-tauri/target/debug/bundle/deb/OPEMOS EXE Linux Test_0.1.0_amd64.deb' /tmp/opemos-exe-package
"$OPEMOS_HEAVY" env OPEMOS_EXPERIMENTAL_LINUX=1 \
  npm run test:package-linux-gui -- \
  --expect-host-unavailable \
  --executable /tmp/opemos-exe-package/usr/bin/steamos-nvidia-image-builder

The smoke inherits the graphical session environment, including its AT-SPI bus and accessibility bridge setting. The shared scheduler supplies the current one-CPU/6 GiB test budget, while this package smoke runs without an approved KVM path. --expect-host-unavailable requires the experimental window, readiness section, and unavailable heading and the exact ordered explanation that KVM is unavailable, TCG requires explicit selection, and automatic fallback is disabled. The unavailable surface must expose exactly Settings, image selection, and Valve’s download page as buttons; build and USB-writing actions must remain absent. It rejects any Linux-ready or normal-ready heading, altered fallback policy, extra button, or unexpected initial button focus. The smoke also opens the native recovery-image chooser, requires its SteamOS recovery-image filter without an all-files option, requires Open to remain disabled before selection and Cancel to remain enabled, cancels without selecting a file, requires both chooser accessibility nodes to disappear, and requires focus to return to Choose Image. Omit the unavailable assertion only when running outside this scheduler with a deliberately different resource budget. It accepts only the accessibility application whose process ID matches the process it launched, then opens Settings and the read-only Core compatibility inspector. In the tested unauthenticated package session, the Settings landmark must expose exactly Close, the two enabled update preferences, the local language selector, Connect GitHub, and the compatibility inspector in that focus order. Opening the selector must expose exactly System default, English (United States), Deutsch (Deutschland), 日本語(日本), and العربية as plain-text accessibility options before the selector closes again. Opening Settings must focus its Close control, and closing Settings must restore focus to its opener. CUDA omission, maintainer workspace access, and automated release must each remain present for explanation but disabled and unfocusable in the unauthenticated package session. The compatibility dialog must expose exact warnings that its structural preview is unauthenticated and non-authorizing, fixtures are debug-only and non-production, and local inputs are cleared without credentials, downloads, cache changes, or guest operations. Dynamic status text is also mirrored into its accessibility label: a fixture result must expose Development fixture — non-production as a status bar and its result container as the Unverified Core result landmark. Clearing or closing must replace the status label with exactly one No result loaded. node and remove the prior fixture-origin label. Before parsing fixtures, the smoke opens the native local resolver chooser, requires the Core resolver JSON filter without an all-files option, keeps Open disabled until selection, keeps Cancel enabled, cancels without reading a file, proves the chooser closes, and requires focus to return to its opener. The smoke also verifies the ordered compatibility controls, initial Close focus, and initial empty status. Inspecting an empty pasted document must expose the bounded Choose or paste error as exactly one status bar without a result landmark; Clear must restore the exact empty status before fixture use. Every compatible, no-artifact, and compatible-after-clear fixture result must then expose exactly the non-production status and unverified-result landmark with no stale empty/error status. The smoke verifies all four development-fixture generation rows and focus restoration to the Settings opener after closing the dialog. It exercises both fixture branches. The compatible branch must expose Core’s exact publication, artifact, pending-verification, and target fields. The no-artifact branch must expose Core’s exact status, reason, message, bounded exact-target build action, architecture, and kernel policy in order. Clear must remove the result heading, Core fields, next action, and all generation rows from the accessibility tree while retaining focus on Clear. Reloading Compatible after Clear must reproduce only its exact rows and retain focus on the fixture control. Closing and reopening the populated dialog must expose no prior result fields and must restore the native Close-first focus order. These remain non-production fixture data and grant no authorization. It then stops the isolated application process group. It has a 20-second default deadline (configurable from 1 through 60 seconds), reports early application exit immediately with its status, refuses symlink or non-executable inputs, pins the accepted regular executable to a no-follow file descriptor before launch, and sends SIGKILL after a bounded SIGTERM grace period. It takes bounded before/after /proc snapshots keyed by PID, kernel start time, and process name, so PID reuse cannot hide a new qemu-system-* process. It does not install the archive, use production compatibility inputs, or start QEMU.

The package is written under src-tauri/target/debug/bundle/deb/. This command needs no graphical session. It deliberately uses a debug build and the deb bundle target, with no signing, publication, or system installation. This test package requires glibc 2.39 or newer, matching the Ubuntu 24.04 build baseline; the Ubuntu-built binary is not a Debian 12 package. OpenSSL 3 and liblzma runtime dependencies are declared alongside Tauri’s GTK/WebKit dependencies. The pinned Debian 12.15 CI job supplies that separate headless build and archive validation. The test application identifier does not provide isolation for user-selected images or shared host tools: use disposable inputs. To test the compiled application from a graphical desktop, preserving the same explicit environment and resource cap:

"$OPEMOS_HEAVY" src-tauri/target/debug/steamos-nvidia-image-builder

The existing build:app and default release bundle targets remain macOS paths. A package build alone does not validate graphical launch, installed-package integration, Debian compatibility, managed guest boot, or hardware.

Validation and limits

Managed-appliance planning uses the smaller of physical RAM and all inherited cgroup-v2 memory.max limits. Its existing minimum is 6 GiB; the user-authorized shared 6 GiB scheduler budget now permits bounded managed-appliance validation while retaining one-CPU serialization and no swap. The disposable tool smoke below uses only a 64 MiB paused QEMU machine, with no host disks or networking, and does not establish Fedora boot or image-build readiness.

Resolve and prepare the pinned Fedora 44 development appliance with mandatory signed-checksum verification:

./builder/appliance/build_linux.sh --resolve-only
"$OPEMOS_HEAVY" ./builder/appliance/build_linux.sh

After installing prerequisites, explicitly exercise seed-ISO creation, qcow2 backing-file preservation, and TCG startup/cleanup:

"$OPEMOS_HEAVY" env OPEMOS_EXPERIMENTAL_LINUX=1 OPEMOS_LINUX_ACCEL=tcg \
  cargo test --manifest-path src-tauri/Cargo.toml live_linux_disposable_host_tools -- --ignored --nocapture

The focused doctor tests use disposable directories and fake executable paths:

"$OPEMOS_HEAVY" node --test tests/linux-host-doctor.test.mjs

For the applicable repository gates:

"$OPEMOS_HEAVY" cargo fmt --manifest-path src-tauri/Cargo.toml -- --check
"$OPEMOS_HEAVY" cargo clippy --manifest-path src-tauri/Cargo.toml --all-targets --all-features -- -D warnings
"$OPEMOS_HEAVY" cargo test --manifest-path src-tauri/Cargo.toml
"$OPEMOS_HEAVY" npm run test:frontend

Use only disposable image files and managed appliances for initial Linux integration work. Preserve source images, retain cancellation/process cleanup, and independently verify exported images. Storage admission checks available bytes and finite inode capacity; passing admission is not a reservation against other host writers or proof that later writes cannot fail.

Physical USB writing is unsupported on Linux. Do not expose macOS diskutil assumptions or bypass that refusal. Real removable-device support needs its own verified Linux implementation and safety tests.

Launcher cancellation

The experimental launcher forwards SIGINT and SIGTERM to its isolated Tauri process group. It allows five seconds for shutdown, then sends SIGKILL if the leader remains alive. When the leader exits, it stops any remaining group members and preserves the command’s exit status or reports termination. Disposable subprocess tests cover graceful and stubborn children and leftover children after leader exit. They do not establish native GUI launch/close or managed-appliance lifecycle validation. SIGKILL of the launcher itself and children that deliberately leave the group require separate supervision.

Inspect Core compatibility without activation

Open Settings → Inspect Core compatibility… in either the macOS application or the experimental Linux application. Select a local Core resolver schema-2 JSON file, or paste its contents and choose Inspect pasted result. Local files must contain UTF-8 text and be nonempty and no larger than 1 MiB. The EXE opens only an absolute, regular, non-symlink file, performs a bounded read, and rejects a file whose descriptor length changes during that read. The existing Rust Core consumer checks the same document structure and 1 MiB byte limit used by its resolver adapter. Structural validity does not authenticate a supplied result: the dialog always identifies it as Unverified document. Filenames and file extensions do not establish trust; only the selected contents are passed to the parser.

Debug builds also offer Compatible fixture and No-artifact fixture from the existing repository conformance fixtures. Their results are always labeled Development fixture — non-production; release builds reject fixture requests. A reported exact-target action is shown as text and cannot be executed here. The inspector offers no build, download, trust, or generation-activation action. It needs no credentials, network requests, image files, guest, or cache changes.

The packaged executable also exposes a closed host-orchestration entrypoint for already-authenticated resolver documents:

steamos-nvidia-image-builder resolve-core-driver \
  --steamos 3.8.14 \
  --kernel 6.16.12-valve24.4-1-neptune-616-gfe145653a794 \
  --architecture x86_64 \
  --candidate-sha256 LOWERCASE_SHA256 /absolute/path/resolver.json

Repeat --candidate-sha256 HASH PATH for each independently authenticated Core candidate. The executable verifies every supplied byte hash, consumes resolver schema 2 and its capability metadata, requires the exact kernel ABI and target, rejects conflicting or multiple different compatible decisions, and emits the one unique Core document as compact JSON. Candidate order does not affect the result. Supplying a hash is only a byte binding; the caller must obtain that expected hash through an authenticated Core channel. This entrypoint performs no network access, download, build, install, release publication, or activation.

The dialog displays Core’s status, target, reason, publication, artifact trust, and next action without inventing another decision. Editing, clearing, or closing invalidates pending preview responses; closing also clears pasted text. Cancelling the file picker preserves the current preview. Selecting the same file again performs a fresh read. Long fields are explicitly truncated for display. Keyboard focus stays within the native dialog, and main-window file drops are ignored while it is open. Frontend behavior and Rust adapter tests are automated. On a real graphical Linux desktop with an active input route, add --expect-keyboard-traversal to the bounded scripts/linux_gui_smoke.py --executable /absolute/path/to/app invocation. The option synthesizes exactly seven Tab events, requires every inspector control in order, and requires focus to wrap to Close. Do not enable it under bare Xvfb: duplicate disposable Debian runs showed that AT-SPI accepts both symbolic and hardware Tab synthesis there without delivering either event to the webview. Visual rendering and real monitor scaling remain separate graphical desktop validations.

Page zoom

The main, build-progress, and maintainer windows provide bounded page zoom even when WebKitGTK does not implement browser zoom shortcuts itself. Use Ctrl++ or Command++ to increase zoom, Ctrl+- or Command+- to decrease it, and Ctrl+0 or Command+0 to reset it. Zoom steps range from 80% through 200%, remain local to the current window, and are announced through an accessibility status region. The shortcuts also work while an editable field is focused; unrelated, repeated, composing, Alt-modified, or ambiguous Ctrl+Command input is ignored.

This changes presentation only. It does not change compatibility results, build inputs, trust, authorization, or activation. Real monitor scaling and native OS forced-colors behavior require separate validation.

Interface language

Settings offers a local interface-language choice. System default follows the first supported system locale, with en-US as the source and fallback. The initial delivered catalogs are de-DE, ja-JP, and ar; Arabic applies RTL page direction while resolver JSON remains left-to-right and displayed Core values select their own bidi direction. The explicit choice is stored locally and shared with already-open application windows. No translation service, network request, Core-field translation, trust decision, or activation follows from this preference. Missing or unsupported locales and keys fail closed to the English catalog.

This host opt-in does not install production keys, select publication policy, authorize source fallback, or activate a generation. Existing production trust and activation gates remain intact. macOS regression validation, Debian validation, managed-appliance smoke tests, final-image equivalence, and real SteamOS/NVIDIA certification require their own recorded evidence.

The managed Fedora appliance boot does not currently complete under the scheduler’s one-vCPU TCG path. Fedora reaches the real root, but guest device deadlines expire before slow udev coldplug recreates the UUID links, and the guest enters emergency mode. Use of KVM remains a separate hardware validation gate.