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:
2026-08-23 17:52:02 +00:00
co-authored by Claude Opus 5
parent f3cc1242c4
commit b88cacecac
3 changed files with 42 additions and 7 deletions
+27 -4
View File
@@ -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)) {