import type { PluginManifest } from '@@/plugins/manifest'; /** * Minesweeper X — a 1990s Windows game, running under wine on a virtual display, streamed to the browser. * * ── Why this is worth having at all ── * * It is a game, and it is also the smallest honest proof that Officer can put an arbitrary X11 program in a * panel. Everything the Desktop plugin wants to do is here at one-tenth the size: a headless framebuffer, a * process on it, a VNC server reading it, and a client in the browser. If this works, "run a Linux app in * Officer" is a solved shape rather than a plan. * * ── It was in the platform until this extraction ── * * `/api/minesweeper`, `Screens/Dashboard/Minesweeper`, an `officerdev` panel, a permission-registry entry * and a hand-written websocket provider in `server.tsx`. The socket is why it had to live there: Bun's route * table is fixed at `serve()`, so nothing installed later could add one. That ceiling is gone — the platform * now serves one wildcard socket door for every plugin — and this plugin is the first thing through it. * * ── The session is the sidecar's, not the platform's ── * * `officer-minesweeper` owns the whole lifecycle: it starts Xvfb, launches the game under wine, runs x11vnc * against that display, and tears all three down. The platform's router forwards three routes to it and * knows nothing else. * * That is not tidiness. The platform used to spawn `bin/session.sh` detached, which meant the game outlived * every restart, nothing supervised it, and a crashed run left wine processes holding the prefix so the next * start hung rather than failed. PM2 supervises a sidecar; a sidecar can clean up after itself on SIGTERM. * * ── x11vnc listens on a unix socket, and has no password ── * * `-nopw` is correct here and the reasoning is not "it is only a game". x11vnc binds a socket inside the * sidecar's private run directory, mode 700, and NOTHING else can reach it — not another local account, not * a member with a Linux user, not a process that guessed a port. There is no network listener to * authenticate against, so a password would protect nothing and would be one more secret to store. * * The version this replaced listened on `127.0.0.1:5907`, which is a different claim: every local account * could open it and take the display. That was survivable on a single-user machine and stopped being so the * moment members got Linux accounts. A fixed port is also squattable — anything can bind 5907 first and be * relayed to by the platform. * * ── It is grantable, and that took a fix rather than a decision ── * * In the platform this was `kind: 'execution'` — never grantable to anyone, at any level — because the wine * process runs as the server owner and Minesweeper X has Open and Save dialogs for its replays. A fresh wine * prefix maps the whole filesystem to `Z:` and symlinks the profile into the owner's real home, so "granted * Minesweeper" quietly meant "granted a file picker as the owner". * * A plugin permission has no `kind` and is always grantable, so extraction alone would have turned a * structurally sealed capability into a hole. `sidecar/prefix.ts` closes it instead: the prefix is confined * to `C:` on every start, and the game can see nothing outside it. That is what makes the entry below * honest rather than optimistic. * * One thing that is NOT solved: there is one display and one game, so two people granted this share a board * and fight over the mouse. Per-viewer sessions would be a display and a prefix each — cheap, but a design * decision rather than an implementation detail. * * ── `osDependencies` ── * * Six binaries, all `owner` scope: the session runs as the server owner, on the owner's display, in the * owner's wine prefix. `wine` is the awkward one — the game is a 32-bit VB6 executable, so the host needs * i386 multiarch and a `win32` prefix, and neither is something a package name can express. The sidecar * builds the prefix on first start and says what it needs if it cannot; see `sidecar/prefix.ts`. */ export const manifest: PluginManifest = { publisher: 'officerdev', version: '1.0.0', platform: '>=1.0.0', label: 'Minesweeper', summary: 'Minesweeper X under wine, on a headless display, streamed to the browser', icon: 'Bomb', color: '#64748b', // The relay target. Not a port: a sidecar name, so the port is one the platform watched arrive at // registration rather than one this file asserted. See `servers/api/plugin-ws/target.ts`. websocket: { sidecar: 'minesweeper' }, permissions: [ { key: 'minesweeper', label: 'Minesweeper', description: 'Play Minesweeper on the server display. The game is confined to its own wine prefix and can reach nothing else on the machine.', }, ], osDependencies: [ { binary: 'wine', reason: 'Runs the game. Minesweeper X is a 32-bit Windows executable, so this needs the 32-bit wine package and the i386 architecture enabled on the host — a 64-bit-only wine cannot load it.', // `multiarch: 'i386'` is what makes the apt entry below installable at all. Minesweeper X is an // i386 PE, so it needs `wine32:i386` — and that package does not exist on a 64-bit host until dpkg // has been told the architecture does. The platform runs `dpkg --add-architecture i386` and // `apt-get update` first; it is system-wide and permanent, and the owner sees it before consenting. // // Without it `wine` alone lands 64-bit-only, the install reports success, and the game dies with // `Bad EXE format` several steps away from anything that mentions architectures. multiarch: 'i386', packages: { apt: ['wine', 'wine32:i386'], 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.', // 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', }, { binary: 'x11vnc', reason: 'Reads that display and serves it, so the browser has something to draw. Bound to a private unix socket, never a port.', packages: { apt: 'x11vnc', dnf: 'x11vnc', pacman: 'x11vnc' }, scope: 'owner', }, { binary: 'xdotool', reason: 'Finds the game window and reads its size, so the panel can fit its canvas to the board instead of the whole framebuffer.', packages: { apt: 'xdotool', dnf: 'xdotool', pacman: 'xdotool' }, scope: 'owner', }, { binary: 'xdpyinfo', reason: 'Waits for the display to be usable. A live Xvfb process is not the same as a display that answers, and starting the game too early fails silently.', packages: { apt: 'x11-utils', dnf: 'xorg-x11-utils', pacman: 'xorg-xdpyinfo' }, scope: 'owner', }, { binary: 'winetricks', reason: 'Installs the Visual Basic 6 runtime into the wine prefix on first start. Minesweeper X needs MSVBVM60.DLL, which wine does not ship; without it the process exits immediately with nothing but an unresolved-import line.', packages: { apt: 'winetricks', dnf: 'winetricks', pacman: 'winetricks' }, scope: 'owner', }, ], };