minesweeper: extracted from the platform
Minesweeper X under wine on a headless display, as a plugin. It was /api/minesweeper, a Dashboard screen, an officerdev panel, a permission registry entry and a hand-written websocket provider in server.tsx. The socket is why it had to live in the platform: Bun's route table is fixed at serve(), so nothing installed later could add one. That ceiling is gone, and this is the first plugin through the per-plugin relay. Three things changed rather than moved: The sidecar owns the session. The platform used to spawn bin/session.sh detached, so the game outlived every restart, nothing supervised it, and a crashed run left wine processes holding the prefix — the next start hung rather than failed. PM2 supervises a sidecar, and stop() uses 'wineserver -k' scoped by WINEPREFIX instead of 'pkill -f wineserver', which would have killed every other wine prefix on the machine. x11vnc moved from 127.0.0.1:5907 to a unix socket in a mode-0700 directory. -nopw was only ever safe because of the machine, not the platform: any local account could open that port and take the display. A fixed port is squattable too. There is now no listener to authenticate against. The wine prefix is confined to C:. This is the one that mattered. The platform classified Minesweeper as kind:'execution' — never grantable — because a fresh prefix maps the whole filesystem to Z: and symlinks the profile into the owner's real home, and the game has Open and Save dialogs. A plugin permission has no kind and is always grantable, so extracting it without this would have converted a sealed capability into a file picker running as the owner. Re-asserted on every start, because wine restores z: whenever it updates a prefix. Not solved: one display and one game, so two grantees share a board. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Officer-Session: 656f5c85-e49a-4f1f-b9f3-042a04b4d043
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
import { createSidecarProxy } from '@@/sidecar/create-proxy';
|
||||
import { mountPrefix } from '@@/plugins/manifest';
|
||||
import { manifest } from '../manifest';
|
||||
|
||||
// /api/minesweeper/* — authenticate, then forward to officer-minesweeper. Three routes, no logic:
|
||||
//
|
||||
// GET /status { installed, running, window }
|
||||
// POST /start brings the session up (idempotent)
|
||||
// POST /stop takes it down
|
||||
//
|
||||
// The pixels do not come through here. They come through `/api/plugin-ws/minesweeper`, which the platform
|
||||
// relays onto the same sidecar's `/ws` — the socket and these routes share one listener and one announced
|
||||
// port, which is the whole reason a plugin can have both.
|
||||
//
|
||||
// The prefix is DERIVED rather than written. `mountPrefix` returns `/minesweeper` for a first-party
|
||||
// publisher, but published by anyone else this mounts at `/api/p/<publisher>/minesweeper`, and the proxy
|
||||
// strips exactly `prefix.length` characters to build the sidecar's path. A literal would forward
|
||||
// `/alice/minesweeper/status` to a sidecar expecting `/status`.
|
||||
const proxy = createSidecarProxy({
|
||||
name: 'minesweeper',
|
||||
prefix: `/api${mountPrefix({ appName: 'minesweeper', manifest })}`,
|
||||
// Building the wine prefix on a cold install fetches and unpacks the VB6 runtime. It happens once, inside
|
||||
// POST /start, and it is minutes rather than seconds.
|
||||
timeoutSeconds: 600,
|
||||
});
|
||||
|
||||
export const router = proxy.router;
|
||||
Reference in New Issue
Block a user