Video and audio no longer share one iframe-based /screen/<name> page. Firefox refuses to load iframe content signed by a certificate whose warning hasn't been accepted at the top level, with no way to click through inside the iframe (Chrome is more lenient, which is why this briefly looked fine there). "Open Screen" is now a plain top-level link straight to noVNC's own URL (new tab, normal Accept-the-Risk applies), and the "Enable Sound" toggle moved to the main setup page - meant to be left open in its own tab while playing. Because that page now hosts a persistent AudioContext/WebSocket, its auto-refresh no longer fires just because a game is running (only during active installs), since a full-page refresh would have killed that connection every 3s. Two real certificate bugs found and fixed along the way: - CN=NY (a meaningless placeholder) -> CN=localhost + SAN (DNS:localhost, IP:127.0.0.1). Firefox validates the WebSocket-Secure connection's cert against the hostname independently of the page load and rejected the CN mismatch there even after the page-level warning was accepted. - Worse: adding -addext without also pinning basicConstraints=CA:FALSE left the cert defaulting to CA:TRUE - i.e. flagged as a Certificate Authority, not a server cert. Firefox hard-refuses that with no override option at all (not a normal clickable warning), which is why "the warning appears but won't let me proceed" even after the CN fix. Fixed by explicitly setting basicConstraints=critical,CA:FALSE plus keyUsage/extendedKeyUsage=serverAuth. Confirmed fixed: Firefox now shows the same clickable self-signed warning Chrome always did, and the noVNC connection completes successfully after accepting it. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NiNnj78HGx1KWyCCo39HSz
127 lines
9.2 KiB
Markdown
127 lines
9.2 KiB
Markdown
# TODO
|
|
|
|
- ~~Reduce image size of `jayfield/dosbox-novnc` (currently ~2.3GB).~~ Done in two steps:
|
|
1. Merged the game download/extract steps and the unzip/smbclient install into a single
|
|
layer (so the downloaded zips and the packages only needed to extract them no longer
|
|
persist in the final image). 2.31GB -> 1.6GB.
|
|
2. Stopped baking the games into the image at all (927MB of that was just t7g's two CD
|
|
ISOs). `run.sh` mounts a named volume (`dosbox-games`) so installed games persist
|
|
across `--rm` restarts. 1.6GB -> ~690MB. Container now needs network access to
|
|
`vlda-01` at runtime (not just build time) to install games.
|
|
3. Dropped the auto-download at container startup entirely. `scripts/setup_server.py` is
|
|
a small stdlib-only HTTP server that serves an HTML page on `${SETUP_PORT}`
|
|
(`70${DISPLAY_NUM}`, published as `7099` by `run.sh`) listing every `*.zip` on the SMB
|
|
share with an Installed/Install status per game; the user clicks "Install" to
|
|
fetch+extract a specific game into `${GAMES_HOME}` on demand.
|
|
|
|
- **Manifest-driven games + Start/Stop/Uninstall**: games are no longer discovered from raw
|
|
`*.zip` files on the share. `setup_server.py` lists `*.json` manifests instead (one per
|
|
game, in the `Games\dosbox\` catalog directory), each declaring
|
|
`name`/`title`/`zip_path`/`start_cmd`. The web page grew Start/Stop/Uninstall buttons
|
|
alongside Install, driven entirely by that manifest — nothing about a game's identity or
|
|
how to run it is hardcoded in the image anymore. Started as a `stuntcar`-only pilot, now
|
|
extended to 6 games (see the SCUMM item below). `t7g` still has no manifest and won't show
|
|
up in the setup page until one is added (`t7g.json` with a `start_cmd` for its
|
|
`dosbox-0.74-3.conf`). `game.sh`/`scripts/start_game.sh` (the old `docker exec`-based
|
|
launch path, with its hardcoded `DOSBOX_VERSION`/`run.bat` convention) were intentionally
|
|
left untouched during the pilot and still overlap with the new Start button — worth
|
|
reconciling (or removing) once every remaining game has a manifest.
|
|
|
|
- ~~Create manifests for SCUMM engine games~~ Done: `scummvm` added to the Dockerfile
|
|
(`/usr/games/scummvm`, not on `$PATH` by default under a non-login shell — manifests use
|
|
the absolute path). Manifests live in the usual `Games\dosbox\*.json` catalog directory on
|
|
the share, but their `zip_path` now points at wherever the game's zip actually already
|
|
lived (`Games\Monkey Island\...`, `Games\Indiana Jones\...`, etc.) — this required
|
|
generalizing the manifest schema from a bare `zip` filename (implicitly under
|
|
`Games\dosbox\`) to a full `zip_path` relative to the share root, since these games weren't
|
|
colocated with dosbox's. Added and verified end-to-end (install/start/stop/uninstall) via
|
|
`monkey2`:
|
|
- `monkey` — The Secret of Monkey Island
|
|
- `monkey2` — Monkey Island 2: LeChuck's Revenge
|
|
- `atlantis` — Indiana Jones and the Fate of Atlantis
|
|
- `indy3` — Indiana Jones and the Last Crusade
|
|
- `tentacle` — Day of the Tentacle
|
|
|
|
Skipped the German CD release of Day of the Tentacle on the share (`Day Of The Tentacle
|
|
(CD DOS, German).zip`) — unlike every other zip here, it doesn't wrap its contents in a
|
|
single top-level folder (loose files at the zip root plus a stray `MANIAC/` dir for the
|
|
embedded Maniac Mansion easter egg), so it doesn't fit the current "zip's top-level folder
|
|
== install name" extraction convention. Also skipped Curse of Monkey Island (much larger,
|
|
untested) — could be added the same way if wanted.
|
|
|
|
ScummVM's own game-target IDs (`monkey`, `monkey2`, `atlantis`, `indy3`, `tentacle`)
|
|
happened to exactly match each zip's existing top-level folder name, so no change was
|
|
needed to the install/extraction logic itself, only to where zips are looked up from.
|
|
|
|
- ~~Handle multiple games running at once~~ Done: each running game now gets its own
|
|
ephemeral X session (`Xvnc`+`fluxbox`+`websockify`, its own display `:90`-`:99` and noVNC
|
|
port `8090`-`8099`) spun up by `start_game()` and torn down by `stop_game()` (or lazily on
|
|
the next page load, if the game exited/crashed on its own) — instead of every game sharing
|
|
one screen and fighting over focus/audio. Capped at `MAX_CONCURRENT_GAMES = 10`; starting
|
|
an 11th game while all 10 slots are in use is refused with an error shown on its row rather
|
|
than silently doing nothing. There is no more a single shared "default" desktop or global
|
|
noVNC link — `server.sh` no longer starts Xvnc/fluxbox/websockify at container startup at
|
|
all, only `setup_server.py` itself; each game's own "Open Screen" link appears on its row
|
|
only while it's running, built with a little client-side JS (the noVNC port differs from
|
|
the setup port and can't be known server-side without knowing which hostname the browser
|
|
used to reach the container). `run.sh` publishes the whole `8090-8099` range up front,
|
|
since Docker can't add port mappings to an already-running container. Verified end-to-end
|
|
with two different games running concurrently (separate processes, separate displays,
|
|
separate noVNC ports, independent stop/teardown) and the 10-slot cap logic.
|
|
|
|
- ~~Get game sound actually audible during play~~ Done: PulseAudio + a hand-rolled stdlib
|
|
WebSocket bridge (`pcm_ws_bridge.py`, in `docker-common` — reusable, not dosbox-specific,
|
|
per the earlier plan for this item) carries raw PCM to the browser, played back via a
|
|
Web Audio `AudioWorkletNode` (`pcm-worklet.js`, also in `docker-common`). Verified real
|
|
end-to-end audio: DOSBox → ALSA (default device, routed through Pulse via
|
|
`/etc/asound.conf`) → PulseAudio's one default sink → `parec` → the bridge → a raw
|
|
WebSocket client, measured RMS ≈9292 while `stuntcar` was actually playing (vs. silence
|
|
before advancing past its title screen) — not just a plumbing check, actual game audio.
|
|
|
|
**Audio is a single shared mix, not per-game** — deliberately simplified from an
|
|
earlier per-slot-isolated design (considered and dropped as unnecessary complexity):
|
|
every running game's audio mixes into Pulse's one default sink, and every `/screen/<name>`
|
|
page connects to the same single `AUDIO_PORT` (`71${DISPLAY_NUM}`, `7199` by default).
|
|
Trade-off: with more than one game running, you can't tell their audio apart.
|
|
|
|
**Real bug found and fixed along the way**: `parec --device=@DEFAULT_SINK@.monitor`
|
|
(the seemingly-obvious macro syntax) fails with "Stream error: Invalid argument" —
|
|
PulseAudio has a *separate* single-token macro, `@DEFAULT_MONITOR@`, for exactly this;
|
|
concatenating `@DEFAULT_SINK@` with a literal `.monitor` suffix isn't valid.
|
|
|
|
`server.sh` now starts `pulseaudio --start --exit-idle-time=-1` and the bridge
|
|
(`pcm_ws_bridge.py 7199 parec --device=@DEFAULT_MONITOR@ ...`) once, before `exec`-ing
|
|
`setup_server.py` — both live for the container's whole lifetime, independent of any
|
|
game's start/stop.
|
|
|
|
**No iframe, no per-game screen page** — tried an `<iframe>`-based `/screen/<name>` wrapper
|
|
first (embedding noVNC + an Enable Sound button on one page), but Firefox refuses to load
|
|
iframe content signed by a certificate whose warning hasn't been accepted at the top level,
|
|
with no way to click through *inside* the iframe (Chrome is more lenient, which is why it
|
|
briefly looked like it worked). Settled on: "Open Screen" is a plain top-level link straight
|
|
to noVNC's own URL (opens in a new tab — normal "Accept the Risk and Continue" applies
|
|
there), and the one "Enable Sound" toggle lives on the main setup page instead, meant to be
|
|
left open in its own tab for as long as you're playing. Also regenerated the self-signed
|
|
cert with `CN=localhost` + SAN (`DNS:localhost`, `IP:127.0.0.1`) instead of the old
|
|
meaningless `CN=NY` — Firefox validates the WebSocket-Secure connection's cert against the
|
|
hostname independently of the page load and was rejecting the CN mismatch there even after
|
|
the page-level warning was accepted. Only fixes access via `localhost`/`127.0.0.1`; a LAN IP
|
|
or other hostname would hit the same mismatch again, since a static cert can't cover every
|
|
possible address in advance.
|
|
|
|
**A second, worse cert bug turned up right after**: adding `-addext` without also pinning
|
|
`basicConstraints=CA:FALSE` left `openssl req -x509` defaulting to `CA:TRUE` — i.e. the
|
|
generated cert claimed to be a Certificate Authority, not a normal server certificate.
|
|
Firefox hard-refuses a CA-flagged cert used as a TLS server cert (not an overridable
|
|
"Advanced -> Accept the Risk" warning, just a dead end), which is exactly why "the warning
|
|
appears but won't let me proceed" even after the CN fix above. Chrome is lenient about this
|
|
too, which is why it kept looking fine there. Fixed by explicitly setting
|
|
`basicConstraints=critical,CA:FALSE`, `keyUsage=critical,digitalSignature,keyEncipherment`,
|
|
and `extendedKeyUsage=serverAuth` in the same `openssl req -x509 -addext ...` call.
|
|
|
|
Because the setup page now hosts a persistent `AudioContext`/`WebSocket`, its
|
|
`<meta http-equiv="refresh">` auto-refresh was narrowed to only fire while an install is
|
|
actively in progress — it used to also refresh continuously whenever any game was running
|
|
(to catch a crashed game's slot being freed), which would have torn down the live audio
|
|
connection every 3 seconds.
|