Provisioning and saving a Linux Test Machine
For stored-password setup commands on macOS and Linux, follow administrator commands. Follow setup responsibilities and the project-owned manual preparation inventory in the provisioning index. Codex on Linux covers scoped posture and helper recovery.
Follow this ordered procedure as a dedicated non-root test user, from a bare Ubuntu 24.04 host through a verified saved state. Infrastructure, account, endpoint, project slug, passwords and baseline paths remain project-owned.
1. Host, tools and account
Declare cloud resources through project IaC, or record an explicit operator exception before provisioning. Use Ubuntu 24.04, a root volume of at least 40 GiB (disk sizing), a 4 GiB instance (8 GiB recommended), at least 2 GiB swap, and one dedicated non-root user. Kernel reservations reduce nominal RAM; verify requires 3.5 GiB usable RAM (3758096384 bytes). It refuses test_machine_memory_below_minimum with observed and required bytes before reset. A 1 GiB host cannot leave the native launch minimum of 1 GiB available. Resize first; swap does not replace RAM. The administrator installs base tools and creates that account:
sudo apt-get update
sudo apt-get install -y ca-certificates curl git tmux python3 python3-venv nodejs npm
sudo adduser --disabled-password --gecos '' TESTUSER
sudo install -d -m 0700 -o TESTUSER -g TESTUSER /var/lib/yoke-golden/TESTUSER
Complete the project's manual runtime preparation, including service/group readiness and real containers/Compose v2 when Docker is declared. This rig needs passwordless sudo for Chromium library setup, independently of runtime access. As the administrator, prepare a drop-in substituting the actual test account:
printf '%s\n' 'TESTUSER ALL=(ALL) NOPASSWD: ALL' > /tmp/yoke-test-user.sudoers
sudo visudo -c -f /tmp/yoke-test-user.sudoers
sudo install -o root -g root -m 0440 /tmp/yoke-test-user.sudoers /etc/sudoers.d/yoke-test-user
sudo visudo -c
sudo -u TESTUSER sudo -n true
Require a nonzero id -u, Ubuntu24.04 in /etc/os-release, and working
git --version, tmux -V, python3 --version, sudo -n true and the
project-declared runtime workload checks
as the user. System packages, services and sudo policy are outside the home
golden. Do not record QA package changes as the permanent machine baseline.
Codex sandbox namespace access
Ubuntu24.04 restricts unprivileged user namespaces by default. Keep
kernel.apparmor_restrict_unprivileged_userns=1. If a harmless command in the
Codex session opened through the desktop fails before execution with
bwrap: loopback: Failed RTM_NEWADDR: Operation not permitted, inspect fresh
kernel audit events as administrator:
sudo journalctl -k --since '10 minutes ago' --no-pager -g 'apparmor.*(bwrap|userns|codex)'
Read the bwrap executable path from the denied userns/capability event. Do
not infer it from PATH or the npm launcher: Codex may use its app-server
daemon. Install bubblewrap only if the actual required helper is missing.
Preserve existing profiles; grant userns only to the exact audited executable.
For an app-server helper, create /etc/apparmor.d/codex-bwrap with:
abi <abi/4.0>,
include <tunables/global>
profile codex_bwrap "/home/user/.codex/packages/app-server-daemon/releases/INSTALLED_RELEASE/codex-resources/bwrap" flags=(unconfined) {
userns,
include if exists <local/codex-bwrap>
}
Replace the entire quoted path with the actual audited absolute executable;
TESTUSER and INSTALLED_RELEASE are placeholders, never a copied version.
Load with sudo apparmor_parser -r /etc/apparmor.d/codex-bwrap, then check:
sysctl kernel.apparmor_restrict_unprivileged_userns
sudo aa-status
Require the global sysctl still1 and codex_bwrap loaded. In the SAME desktop-opened Codex
session, request a harmless normal sandboxed pwd/printf command. Require
actual exit code 0 and output, then inspect fresh audit events for renewed
namespace/capability denial. Do not escalate the probe, change Codex
permissions, disable the global sysctl, reset the host, or accept model text
as execution proof. Re-audit the helper path after Codex updates. A new
denial or unavailable helper is a provisioning blocker; stop and report it.
2. SSH, capability and desktop access
Authorize the project's shared public SSH key in the test account's
~/.ssh/authorized_keys (directory0700, file0600, both test-user-owned).
Disable password/keyboard-interactive SSH; retain public-key access. Restrict
network ingress to approved execution machines; audit the effective SSH and
firewall policy, not just successful login.
Register test-machine:<name> through yoke test-machine settings-replace --project P --test-machine NAME --settings-file FILE --new. Read --help; updates
use the as-read settings token. Settings include resource_name, os=linux,
host, user and the absolute home golden path outside the home. A cloud
host's cloud_instance_id and operating notes belong to this record, not Yoke
source; with the id, a lease starts the host and its last release stops it
when an Elastic IP keeps host stable.
A capacity machine is independent; ordinary agent work must use another home.
On each QA workstation, store shared SSH and per-host desktop secrets:
yoke projects capability secret set --project P --cap-type test-machine --key ssh_private_key --value-file KEY_FILE
yoke projects capability secret set --project P --cap-type test-machine:NAME --key desktop_password --value-file PASSWORD_FILE
yoke test-machine exec --project P --test-machine NAME -- /usr/bin/true
Read each command's --help. Private bytes never enter settings, argv, logs,
artifacts or source. exec uses the workstation's SSH agent; host operations
resolve capability secrets. A serial host lease held by another session
requires waiting before preparation/access.
After step4, declare desktop_protocol=rdp, desktop_route=ssh-forward,
desktop_port=3389, desktop_user=TESTUSER; the default forwarded desktop host
is remote loopback. Operator access uses yoke test-machine desktop-access --project P --test-machine NAME; read --help. Open its returned loopback address
in an RDP client, no gateway, Xorg session, with the registered test user and
password from the private password_file. Close its tunnel after use with
ssh -F /dev/null -S PASSWORD_FILE.ssh -O exit SSH_USER@SSH_HOST; remove only
its temporary password copy. No public RDP ingress is needed. For an announced
personal sign-in handoff with a visible FreeRDP SDL viewer, add --view; the
product supplies the password on stdin and closes its forward with the viewer.
Read --help for the required client. Agent captures prove test rendering.
3. Harness installation and sign-in
Use each vendor's supported installer as the test user, with .local/bin on
login and SSH PATH. These commands are provisioning actions, not audit probes:
curl -fsSL https://claude.ai/install.sh -o /tmp/claude-install
bash /tmp/claude-install
curl -fsSL https://chatgpt.com/codex/install.sh -o /tmp/codex-install
sh /tmp/codex-install
curl -fsSL https://cursor.com/install -o /tmp/cursor-install
bash /tmp/cursor-install
Check current Claude requirements,
Codex installation and
Cursor installation before use;
a distribution's old Node package is not proof of supported vendor runtime.
Record actual executable paths in the fixture. Golden capture records each CLI's version beside the golden in
GOLDEN.harness-clis. Before a QA plan's first reset on this host, Yoke
compares those versions with each vendor's latest release; when one is newer
it resets, installs the newer CLIs with each vendor's installer as the test user and
captures a new golden under the plan's own host lease. Do not hand-update
CLIs for QA; a failed refresh stops the plan with the step to repair.
Use the test user's terminal/tmux session and the browser identity procedure:
Claude's documented login flow (claude, /login); Codex
codex login --device-auth when the account/workspace permits it;
Cursor NO_OPEN_BROWSER=1 agent login and its printed URL
(authentication).
Installed Cursor may also expose cursor-agent. Apply that same authorization
procedure to the native login URL; keep credentials on the host.
Require a real request from each harness, not status text or a credential-file
count. Once per machine, accept Claude's dangerous-mode prompt
in the dedicated test context; require skipDangerousModePermissionPrompt=true
before saving. It is signed-in home state restored from the golden, not a
permission to bypass unrelated product safeguards.
Keep every harness installation and sign-in live outside the golden; follow
Linux live-state preservation and reset recovery.
Require all three real requests before reset. Chromium quits to flush its
state; preserve its existing ~/snap/chromium/common/chromium store only when
present, never an older golden copy. Require separate reset and browser
sign-in proof in the baseline procedure.
4. Durable desktop and unattended sessions
For desktop coverage, the executing product operation runs this stdlib-only
core helper as the dedicated test user, with private capability input. Copy
yoke_harness/host_provisioning/provision_linux_desktop.py from the controller's
Yoke package into the target working directory before running:
python3 provision_linux_desktop.py --desktop-password-stdin
python3 provision_linux_desktop.py --verify
It requires Ubuntu24.04 and passwordless sudo, installs XFCE, xfce4-terminal,
xorgxrdp/xrdp, dbus-x11, scrot, xdotool and venv support, refuses another
.xsession choice, and proves active services and loopback-only RDP. It sets
TerminalEmulator=xfce4-terminal while preserving other helpers.rc choices.
It installs the Chromium snap, selects chromium_chromium.desktop for web
links in ~/.config/mimeapps.list (keeping the user's other default
applications) and makes Chromium the system default browser:
sudo apt-get install -y chromium-browser
sudo update-alternatives --set x-www-browser /usr/bin/chromium-browser
On the XFCE desktop, xdg-open hands links to exo-open, which falls back to
debian-sensible-browser and runs x-www-browser; mimeapps.list alone
leaves links failing with "Failed to execute default Web Browser". Ubuntu's
chromium-browser package wraps the snap in /usr/bin/chromium-browser and
registers it as x-www-browser. Never point x-www-browser at
/snap/bin/chromium: snap picks the app from the name it is called by and
fails with unknown command.
--verify resolves /usr/bin/x-www-browser to the program it runs, the same
check the approval gate makes, so an SSH session sees what the desktop opens.
It refuses linux_desktop_chromium_not_proved or
linux_desktop_default_browser_not_selected until that program is Chromium.
To confirm on the desktop, run xdg-open https://upyoke.com in its terminal;
Chromium opens the page.
An existing customization requires an operator decision before provisioning.
Tools/services are system baseline; session selection is captured home state.
Do not install scrot/xdotool as a mission-journaled package delta: another reset
can reverse that journal. Current terminal selection is retained by reset.
Provisioning requires the operation to stream the capability-owned
desktop_password on private stdin. It sets the dedicated fixture user's
local password with sudo -n chpasswd. The next registered GUI operation reuses
the product's existing uploaded desktop helper and owned-session startup; the
host needs no harness-package import. The operator never types sudo, sets a machine
password or logs into the desktop. No secret enters a file, argv or output;
SSH stays key-only and PAM stays enabled. --verify checks readiness only and
refuses the password flag. Provisioning itself reports desktop_session=not_started.
The serving build's registered Linux screenshot, GUI-session execution and
desktop-access operations start or reuse the dedicated XFCE session. Their
receipt states desktop_session=started|reused. Exactly one existing human
XFCE desktop may be reused; multiple desktops refuse as ambiguous. The first
capture waits for a valid nonblank frame while retaining started evidence;
an already running desktop with a blank capture still refuses.
The unattended route uses xrdp-sesrun -s ::1 -t Xorg -F 0 TESTUSER, feeding
the registered password on stdin. On a host whose sesman listens on
[::1]:3350, omitting -s ::1 can report a connect error. Do not run a
password-bearing workaround. If the operation refuses sesrun/startup, repair
its named prerequisite and rerun. Human RDP login is used only by cases that
explicitly test human login.
For XTEST input on the actual leased display, use xdotool without --window;
delivery through the focused window needs several seconds before being judged
undelivered. The registered GUI-session route selects DISPLAY, XAUTHORITY and
D-Bus from the test user's actual XFCE session; a dummy display is not proof.
Prove the visible result and screenshot with yoke test-machine screenshot --project P --test-machine NAME --json; read --help. Transcripts alone do not
prove the desktop. Reset ends the registered fixture user's desktop, including a session reused
from a human login. It verifies process identity before termination and proves
that the desktop ended; other users' sessions are excluded.
Prove unattended start after a fresh-host reset, then prove reuse with a second
capture of the same display. Reset performs fixture desktop logout itself.
5. Optional live browser identities
Follow Linux browser identities for sign-in and approval gates.
6. Capture, reset and recovery
Continue with Linux baseline capture and recovery. Complete its standard probes, save/reset roundtrip and final clean signed-in baseline before reporting the host ready. Its recovery table preserves the named refusal and sealed-state repair for a failed operation.