confine the prefix again after wine boots, and fix two manifest package names
Two things the live install found. The manifest was refused at discovery. `apt: 'wine wine32:i386'` is two packages and an architecture suffix, and a package name may be neither — nor should it be, since `dpkg --add-architecture i386` has to happen first and no package name can express that. And Fedora's Xvfb package is `xorg-x11-server-Xvfb`, with a capital X, which a manifest package name may not have; the dnf entry is dropped rather than guessed, which turns into an instruction to the owner instead of a wrong install. More importantly: confining the prefix before wine starts is a confinement wine can undo. Observed directly — after a run, every com* link had come back with a fresh timestamp, because wine rebuilds dosdevices when it boots a prefix it thinks needs updating, and a wine package upgrade is enough to make it think so. Z: did not come back that time, but "the dangerous one happened not to regenerate" is not a property to rely on. confineAndVerify now runs again once the game is up and says so if Z: returned. Verified on the live server: Z: and the three home symlinks are gone from a prefix that had them, run/ is mode 700, nothing listens on 5907, and the RFB handshake arrives through the platform relay with a valid owner token and through nothing else. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Officer-Session: 656f5c85-e49a-4f1f-b9f3-042a04b4d043
This commit is contained in:
+9
-2
@@ -88,13 +88,20 @@ export const manifest: PluginManifest = {
|
||||
{
|
||||
binary: 'wine',
|
||||
reason: 'Runs the game. Minesweeper X is a 32-bit Windows executable, so this host also needs i386 multiarch (`dpkg --add-architecture i386`) and the 32-bit wine package — a 64-bit-only wine cannot load it.',
|
||||
packages: { apt: 'wine wine32:i386', dnf: 'wine', pacman: 'wine', brew: 'wine-stable' },
|
||||
// `apt: 'wine'` alone, deliberately. The 32-bit half is `wine32:i386`, and a package name may not
|
||||
// carry an architecture suffix or a second package — nor should it, since `dpkg --add-architecture
|
||||
// i386` has to happen first and no package name can express that. The `reason` above carries the
|
||||
// whole command, which is what the owner is shown when this is missing.
|
||||
packages: { apt: 'wine', dnf: 'wine', pacman: 'wine', brew: 'wine-stable' },
|
||||
scope: 'owner',
|
||||
},
|
||||
{
|
||||
binary: 'Xvfb',
|
||||
reason: 'The virtual display the game draws on. A framebuffer with no hardware behind it, so nothing appears on any real monitor.',
|
||||
packages: { apt: 'xvfb', dnf: 'xorg-x11-server-Xvfb', pacman: 'xorg-server-xvfb' },
|
||||
// No `dnf` entry, and not by omission. Fedora's package is `xorg-x11-server-Xvfb` — capital X —
|
||||
// and a manifest package name must be lowercase. Left out means "cannot be installed automatically
|
||||
// here", which turns into an instruction to the owner rather than a wrong guess.
|
||||
packages: { apt: 'xvfb', pacman: 'xorg-server-xvfb' },
|
||||
scope: 'owner',
|
||||
},
|
||||
{
|
||||
|
||||
+27
-4
@@ -115,12 +115,35 @@ export async function ensurePrefix(log: (line: string) => void): Promise<PrefixR
|
||||
* ── What is left ──
|
||||
*
|
||||
* `C:` alone, pointing at the prefix. The game keeps its own directory, its settings and its replays; it can
|
||||
* see nothing else. Serial and parallel device nodes go too — a 1997 puzzle game has no business holding an
|
||||
* open descriptor on `/dev/ttyS0`.
|
||||
* see nothing else.
|
||||
*
|
||||
* Re-asserted on every start rather than only at creation, because wine recreates `z:` whenever it decides
|
||||
* the prefix needs updating — a wine package upgrade is enough, and it would restore the hole silently.
|
||||
* ── It runs TWICE per start, and that is not belt-and-braces ──
|
||||
*
|
||||
* Wine rebuilds `dosdevices` when it boots a prefix it thinks needs updating, and a wine package upgrade is
|
||||
* enough to make it think so. Observed directly: after a run, every `com*` link had come back with a fresh
|
||||
* timestamp — so a confinement that happens only before wine starts is a confinement wine can undo.
|
||||
*
|
||||
* `z:` did not come back in that observation, and the device nodes are harmless. But "the dangerous one
|
||||
* happened not to regenerate this time" is not a property to rely on, so `confineAndVerify` runs again once
|
||||
* the game is up and says so loudly if `z:` returned. The second pass is a handful of `unlink` calls.
|
||||
*/
|
||||
/**
|
||||
* Confine, then check it held. Returns whether `z:` had to be removed a second time — which means wine
|
||||
* regenerated it and the first pass was not enough.
|
||||
*/
|
||||
export function confineAndVerify(log: (line: string) => void): boolean {
|
||||
const z = join(WINEPREFIX, 'dosdevices', 'z:');
|
||||
const regenerated = existsSync(z);
|
||||
confinePrefix();
|
||||
if (regenerated) {
|
||||
// Not fatal — it has just been removed again, before anything could look at the display. Worth saying
|
||||
// out loud because it means a wine upgrade landed, and the NEXT thing to check is whether the prefix
|
||||
// still runs the game at all.
|
||||
log('wine regenerated the Z: drive mapping during startup; removed it again');
|
||||
}
|
||||
return regenerated;
|
||||
}
|
||||
|
||||
export function confinePrefix(): void {
|
||||
const dosdevices = join(WINEPREFIX, 'dosdevices');
|
||||
if (existsSync(dosdevices)) {
|
||||
|
||||
+6
-1
@@ -1,7 +1,7 @@
|
||||
import type { Subprocess } from 'bun';
|
||||
import { existsSync, rmSync } from 'node:fs';
|
||||
import { GAME_EXE, RUN_DIR, VNC_SOCKET, ensureDirs } from './paths';
|
||||
import { WINE_ENV, ensurePrefix, prefixReady } from './prefix';
|
||||
import { WINE_ENV, confineAndVerify, ensurePrefix, prefixReady } from './prefix';
|
||||
import { run, waitFor } from './run';
|
||||
|
||||
/**
|
||||
@@ -179,6 +179,11 @@ async function startInner(log: (line: string) => void): Promise<SessionStatus> {
|
||||
}
|
||||
}
|
||||
|
||||
// Wine has now booted the prefix, and booting it is what can restore `Z:`. The confinement in
|
||||
// `ensurePrefix` happens before that, so on its own it would be undone by exactly the event it exists to
|
||||
// survive. See `confineAndVerify`.
|
||||
confineAndVerify(log);
|
||||
|
||||
// ── the display server ──
|
||||
if (!alive(vnc)) {
|
||||
// A socket left by a crashed run refuses to bind. Removing it is safe: nothing else may write here.
|
||||
|
||||
Reference in New Issue
Block a user