tags are how the marketplace is browsed rather than scrolled: a list of them on the left, the plugins that carry one on the right. a closed vocabulary (PLUGIN_TAGS in the platform) because free text becomes homelab, home-lab, Home Lab and selfhosted inside a month, and a filter list of synonyms is worse than no filter at all. declared here rather than curated in the catalogue, so a plugin can say what it is and nobody edits a table when one ships. the marketplace caches it on sync, exactly as it already does for label and summary.
140 lines
7.6 KiB
TypeScript
140 lines
7.6 KiB
TypeScript
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',
|
|
/** the only one that needs no other justification. */
|
|
tags: ['Fun'],
|
|
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',
|
|
},
|
|
],
|
|
};
|