Experimental Linux host testing
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.