Drops the /flash and /html5 sections (old URLs redirect to /browse),
the GitHub star count endpoint, the unused HOME_RECENT_HOURS setting,
upstream's design notes and code review docs, and the issue template's
link to upstream discussions.
A Gitea workflow builds the app, session manager and streamer images
and pushes them to the instance's registry on pushes to main and on
version tags, reading the registry host at run time. Compose can pull
them with IMAGE_PREFIX. The update checker reads APP_UPDATE_URL instead
of upstream's GitHub releases and is off without it. Upstream's Docker
Hub workflow is removed.
The app forwards /stream/ to the session manager, WebSocket upgrades
and the save-sync event stream included, ahead of helmet and
compression. A reverse proxy in front now needs no route of its own
for streams, and the session manager publishes no ports.
The app, session manager and streamer are built from source by a single
docker-compose.yml. Streaming is the opt-in "streaming" profile, enabled
by setting STREAMING_API_TOKEN. Replaces the separate production, dev and
streaming compose files.
On a network share the database copy and cache pre-warm run for minutes,
and the listener only opened after all of it, so anyone who loaded the
page during that window got a connection error.
Open the port immediately after registering the static frontend, and
hold every route that needs the database behind a gate that answers 503
with a machine-readable marker and the current startup phase. The SPA
shell needs nothing the gate blocks, so the frontend renders a "server
is starting" screen on the login page's own background, polls /health,
and reloads once the server reports ready.
Move the database copy out of the entrypoint: it does the same freshness
check the application does, and doing it before node starts kept the port
closed for the longest part of startup. Distinguish a dead server from a
slow one by requiring the backend's own marker, since a proxy in front of
a stopped backend still answers.
The shipped compose mounted the Flashpoint directory read-only, so every write
the app makes to it failed: recording downloaded state in flashpoint.sqlite,
saving downloaded game data under Data/Games, and metadata sync. The Downloaded
page marked nothing and the reconciler warned on every boot.
Drop :ro from both compose files and the README's snippet, and correct the docs
that described the mount as read-only.
better-sqlite3 is synchronous, so a slow read froze the entire server — a
filter-options request over a slow mount was measured blocking it for 167
seconds, during which even /health timed out.
Add DbQueryPool: read-only SQLite connections in worker threads, with
DatabaseService.allAsync routing the full-table scans through them and falling
back in process when workers are unavailable. Filter-options queries now run
concurrently rather than serially. Workers reopen on database reload and stop
on shutdown.
Also set UV_THREADPOOL_SIZE=16: libuv's default of 4 throttled every image and
ZIP read in the process, which dominates on a network mount.
Collapse the separate backend (Node) and frontend (nginx) images into
one image where the Express backend serves the built React app in
addition to the API and game content, so deployment is a single
container and one origin.
- Add a root Dockerfile with frontend-builder + backend-builder stages
producing one runtime image; remove backend/ and frontend/ Dockerfiles,
the nginx config, and the frontend entrypoint
- Serve the frontend dist and SPA fallback from Express (config.serveFrontend
/ FRONTEND_DIST_PATH), before auth/maintenance so the shell always loads and
before the API 404 so client routes resolve to index.html
- Port the SPA-oriented CSP from nginx into Helmet, gated on serveFrontend;
keep the strict API-only policy otherwise
- Reduce both compose files to a single flashpoint-web service on WEB_PORT
-> 3100; drop API_PORT, BACKEND_HOST, BACKEND_PORT
- Build one image in the release workflow instead of a two-service matrix
- Update .env.example, README, workflow README, and env var docs
Also add .gitattributes (*.sh eol=lf) and normalize the entrypoint line
endings — without it, Windows checkouts bake CRLF into the image and the
shebang fails with "no such file or directory".
- Read game paths and NODE_ENV through the config module in
gamezipserver instead of process.env directly
- Harden SQLite cache/mmap parsing with isNaN checks so invalid
values fall back to defaults instead of NaN
- Pass auth, rate-limit, application, and OpenTelemetry variables
through both docker-compose files with documented defaults
- Expand env var documentation and .env examples to match
feat: add shared download registry, type safety, and test infra
Add DownloadRegistry to prevent duplicate downloads between
DownloadManager and GameZipServer. Consolidate PreferencesService
(delete game/services copy, add compatibility aliases). Replace
`any` defaults with `unknown` in DatabaseService/UserDatabaseService
and add explicit type parameters to ~40 query call sites. Add
vitest config, test setup, test helpers, and unit tests for auth
middleware, RBAC, pagination, and time range utils. Enable
strictPropertyInitialization in backend tsconfig. Add SHA-256
checksum verification to RuffleService updates. Harden Docker
Compose with log rotation, resource limits, and bridge network.
Fix NODE_ENV default in dev compose, exclude guest userId from
activity logs, add activity logging to password change, guard
nullable permission descriptions, and align frontend Game/Permission
types with actual API shape.
- Pin DNS resolution to prevent SSRF rebinding between
check and connect (GameDataDownloader)
- Fail-closed on realpath failure in GameZipServer
- Zip Slip validation before extraction (RuffleService)
- Escape LIKE wildcards in ActivityService queries
- Sanitize sort order to prevent SQL injection
- Whitelist sort order instead of passing raw user input
- Add headersSent guard to global error handler
- Remove cookie from CGI allowed headers, sanitize
content-type against null/CR/LF injection
- Revoke refresh tokens on password change and user
deactivation
- Wrap role/user uniqueness checks in transactions
(TOCTOU fix)
- Fix concurrent ZIP mount race with deferred promise
and double-close prevention via _manuallyClosed flag
- Handle client disconnect in collectRequestBody
- Tighten nginx CSP, repeat headers in location blocks,
hide server version, pin nginx:1.27-alpine
- Fix health check endpoint path in backend Dockerfile
- Use wget instead of curl for frontend health checks
- Replace any types with proper types across backend
and frontend
- Replace || with ?? for config/form defaults
- Replace hardcoded Tailwind colors with theme tokens
- Replace console.log/error with logger in ErrorReporter
and ChartErrorBoundary
- Conditionally render AddToPlaylistModal to avoid
hidden instances
- Add NaN guards for parseInt in GameBrowseLayout
- Standardize query key for publicSettings
- Simplify README to Docker-first quickstart with wiki
link
- Fix license to GPL-3.0 in backend package.json
- Slow down particle animation speed
- Add Zod max-length limits on playlist/error schemas
- Ensure positive limit in parsePaginationQuery
- Validate settings category param against allowlist
- Fix static/parameterized route ordering in favorites
- Add asyncHandler to game-zip wildcard route
- Close readline/stream on error in getRecentErrors
- Add .catch() on fire-and-forget syncAndReload
- Fix LazyBackgroundImage cleanup on unmount
- Store NetworkStatusIndicator timeout in ref
- Add Flash/SWF condition to random game query
- Add edition config to backend, .env.example, and Docker
- Gate metadata sync and update checks on edition
- Conditionally select logoPath/screenshotPath in queries
- Add edition selector to General Settings (admin only)
- Hide metadata sync UI for Ultimate in Update Settings
- Add `metadata.flashpoint_edition` system setting with
migration seed data
Routes game content through nginx instead of requiring direct
localhost:22500 access from browser. This enables proper Docker
networking where containers communicate internally.
Changes:
- Add /game-proxy and /gamezip-proxy locations to nginx config
- Add GAME_SERVICE_EXTERNAL_URL config for browser-facing URLs
- Update games route to use external URL config
- Add GAME_SERVICE_HOST env var to frontend container
- Add game-service dependency to frontend in docker-compose