// Controlled, observable proof that the PACKAGED Linux desktop bundle attaches // to an OS-supervised daemon — a manual harness (run by hand, not in the vitest // matrix). Linux + Xvfb avoids the macOS Aqua-session limitation: the packaged // Electron app runs headless under a virtual X server in a tart guest, so the // attach path can be proven without a logged-in GUI session (which the // desktop-packaged vitest project needs and skips without). Mirrors that test's // intent, with a finally that always discards the VM. // // bun e2e/scripts/verify-linux-desktop-attach.ts /tmp/linux-desktop-bundle.tgz import { execFile } from "node:child_process"; import { promisify } from "node:util"; import { tartVm } from "../src/vm/tart"; const execFileP = promisify(execFile); const t0 = Date.now(); const log = (m: string) => console.log(`[+${((Date.now() - t0) / 1000).toFixed(0)}s] ${m}`); const bundleTgz = process.argv[2] ?? "/tmp/linux-desktop-bundle.tgz"; // Ubuntu 24.04 (noble) renamed several libs to `…t64`; apt fails the whole // transaction if ANY name is missing, so install each separately and list both // the pre-noble and t64 names — only the available one installs. const DEPS = [ "xvfb", "libnss3", "libnspr4", "libatk1.0-0", "libatk1.0-0t64", "libatk-bridge2.0-0", "libatk-bridge2.0-0t64", "libcups2", "libcups2t64", "libdrm2", "libxkbcommon0", "libxcomposite1", "libxdamage1", "libxfixes3", "libxrandr2", "libgbm1", "libpango-1.0-0", "libcairo2", "libasound2", "libasound2t64", "libatspi2.0-0", "libatspi2.0-0t64", "libgtk-3-0", "libgtk-3-0t64", ]; const main = async () => { log("provisioning tart linux guest..."); const vm = await tartVm("linux", "arm64").provision(); log(`provisioned host=${vm.host}`); try { log("pushing packaged bundle tarball..."); await execFileP("sshpass", [ "-p", "admin", "scp", "-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null", bundleTgz, `admin@${vm.host}:/tmp/app.tgz`, ]); log("extracting + installing Electron runtime deps (apt)..."); await vm.ssh("mkdir -p /tmp/app && tar -xzf /tmp/app.tgz -C /tmp/app"); await vm.ssh("sudo DEBIAN_FRONTEND=noninteractive apt-get update -qq"); // Install each package independently so one missing name (noble t64 rename) // doesn't fail the whole transaction. const apt = await vm.ssh( `for p in ${DEPS.join(" ")}; do sudo DEBIAN_FRONTEND=noninteractive apt-get install -y -qq $p >/dev/null 2>&1 && echo "ok $p" || echo "skip $p"; done`, ); log(`apt:\n${apt.stdout.trim()}`); const nss = await vm.ssh("ldconfig -p | grep -c libnss3 || true"); log(`libnss3 present: ${nss.stdout.trim()}`); const home = "/tmp/eh"; const exe = "/tmp/app/executor-desktop"; const sidecar = "/tmp/app/resources/sidecar/executor-sidecar"; const webui = "/tmp/app/resources/web-ui"; log("starting bundled sidecar as supervised daemon..."); await vm.ssh( `rm -rf ${home}; mkdir -p ${home}; nohup env HOME=${home} EXECUTOR_SUPERVISED=1 EXECUTOR_DATA_DIR=${home}/.executor EXECUTOR_PORT=4789 EXECUTOR_HOST=127.0.0.1 EXECUTOR_AUTH_TOKEN=linux-attach EXECUTOR_CLIENT_DIR=${webui} ${sidecar} > /tmp/daemon.log 2>&1 &`, ); // Wait for the daemon to publish its manifest + serve. for (let i = 0; i < 30; i++) { const r = await vm.ssh(`cat ${home}/.executor/server-control/server.json 2>/dev/null`); if (r.stdout.includes("cli-daemon")) break; await new Promise((res) => setTimeout(res, 1000)); } const before = (await vm.ssh(`cat ${home}/.executor/server-control/server.json`)).stdout.trim(); const beforeManifest = JSON.parse(before) as { kind: string; pid: number }; log(`daemon manifest: kind=${beforeManifest.kind} pid=${beforeManifest.pid}`); if (beforeManifest.kind !== "cli-daemon") throw new Error("daemon did not publish cli-daemon"); const health = await vm.ssh( `curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:4789/api/health`, ); log(`/api/health = ${health.stdout.trim()}`); log("launching PACKAGED app under Xvfb..."); await vm.ssh( `nohup xvfb-run -a env HOME=${home} ELECTRON_ENABLE_LOGGING=1 ${exe} --no-sandbox > /tmp/app.log 2>&1 &`, ); // Positive attach signal: the main process logs "attaching to supervised // daemon" (sidecar.ts) — proves the attach CODE ran and succeeded, not just // "the manifest is unchanged" (which a crash would also leave). let attached = false; for (let i = 0; i < 40; i++) { const logs = await vm.ssh( `cat /tmp/app.log ${home}/.config/Executor/logs/main.log 2>/dev/null`, ); if (/attaching to supervised daemon/i.test(logs.stdout)) { attached = true; break; } await new Promise((res) => setTimeout(res, 2000)); } const appLog = (await vm.ssh(`tail -40 /tmp/app.log 2>/dev/null`)).stdout.trim(); log(`app.log tail:\n${appLog}`); const after = (await vm.ssh(`cat ${home}/.executor/server-control/server.json`)).stdout.trim(); const afterManifest = JSON.parse(after) as { kind: string; pid: number }; log(`manifest after app launch: kind=${afterManifest.kind} pid=${afterManifest.pid}`); if (!attached) { throw new Error("no 'attaching to supervised daemon' log — app did not attach (see app.log)"); } if (afterManifest.kind !== "cli-daemon" || afterManifest.pid !== beforeManifest.pid) { throw new Error( `manifest changed (kind=${afterManifest.kind} pid=${afterManifest.pid}) — app spawned its own sidecar instead of attaching`, ); } log("RESULT=PASS — packaged Linux app attached to the supervised daemon (Xvfb)"); } finally { log("discarding guest VM"); await vm.discard(); log("discarded"); } }; main() .then(() => process.exit(0)) .catch((e) => { console.error("RESULT=FAIL", e); process.exit(1); });