compose.yaml / compose.override.yaml:
- container_name is now ${STACK_NAME:-escapepage}-* (STACK_NAME is a plain var,
not COMPOSE_PROJECT_NAME, so prod keeps its escapepage-* names with no config change)
- every published host port is ${*_PORT:-<current default>}, so prod is unchanged
and a second stack can bind its own (localhost-only) ports
- nginx joins the external nginx_default network so Nginx Proxy Manager can
forward to <stack>-nginx by name
restart.sh:
- scoped to STACK_NAME / COMPOSE_PROJECT_NAME read from docker/.env, so running it
from the test checkout can't touch the prod stack
- host-wide `docker system prune` / `docker builder prune` moved behind --prune-all
Adds docker/.env.test.example and doc/test-environment.md (separate checkout,
env layers, NPM proxy host + Access List IP allowlist, Mercure on test).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
checkAllPlayersReady() set the session to PLAYING and cleared/updated its
timer in memory but never flushed, relying on callers to do so. The
toggleReady() caller flushed right after, but GameController::index()'s
lazy catch-up call did not - so if the "everyone ready" transition was
only detected on a page load (e.g. players didn't click ready within the
same 60s window), the terminal would render for that one request from the
in-memory state, letting the game be played entirely through the
unguarded message API, while the database silently kept the session on
'ready' with timer 0 forever. This made sessions invisible to the mainframe
hint cron and the admin "running sessions" count. Moved the flush inside
checkAllPlayersReady() itself so both callers persist reliably.
Also chmod the logrotate configs after COPY in the Dockerfile, since
their on-disk mode ended up group-writable (0664) depending on the
build host's umask, which made logrotate refuse to use them.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Lost accidentally in the previous commit; both scripts are invoked
directly (./docker/setup.sh) rather than via bash, so they need +x.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Game1 terminal: flesh out the virtual filesystem with a realistic
spread of Linux directories/files (~70 dirs, ~140 files) so it no
longer reads as an obviously small puzzle set, without touching any
win-condition or rapport files.
- Add app:hints:check command + a php-cron container (BusyBox crond)
that nudges players who haven't contacted every teammate 5 minutes
into a session, via a new 'hint' Mercure message type.
- Log the cron command's output to var/log/cron/cron.log and rotate
it (25MB / 90 days) via logrotate, run daily from the same crontab.
- Redirect PHP's error_log and Symfony's prod app/deprecation logs
from stderr-only into var/log/php/*.log (kept alongside stderr),
with the same rotation policy.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
docker/.env feeds MERCURE_EXTRA_DIRECTIVES, a Caddyfile-style config
block, via Compose variable substitution. Quoting a space-separated
value there makes Caddy treat both origins as one single malformed
token rather than two arguments, which crash-loops the Mercure
container on startup. Compose's .env parsing for values with spaces
doesn't require quotes (unlike Symfony's Dotenv), so drop them.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Production's MERCURE_CORS_ALLOWED_ORIGINS only allowed
https://escapepage.com, but nginx has no www redirect
(server_name _;), so the site is also reachable at
https://www.escapepage.com. Visitors on the www host got a CORS
error on the Mercure EventSource connection since the Origin header
didn't match the allow-list. Dev's .env already allowed both; bring
docker/.env.dist in line, and fix its stale MERCURE_PUBLIC_URL
(bare domain instead of the mercure. subdomain actually used).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The project now sends mail via Mailgun (symfony/mailgun-mailer), not
SendGrid, so the tracked template should reflect the real transport
in use.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
.env, .env.dev, .env.prod, .env.test, and docker/.env contained real
production secrets and were tracked in git despite the Symfony
convention of keeping them local-only. Untrack them and ignore them
going forward; docker/.env.dist stays as a template but now uses
placeholder values instead of live credentials.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
setup.sh was forcing --env-file ../.env (root .env with placeholder secret)
instead of letting Docker Compose use docker/.env (real secret). Mercure
config now generates the publisher JWT from MERCURE_JWT_SECRET directly,
removing the need for a separate pre-generated token.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>