5.5 KiB
MineSeeker
A real-time 1v1 multiplayer minesweeper game played in the browser. Two players race on the same hidden minefield — uncover safe cells to score points, but hit a mine and you hand the advantage to your opponent. Games are live and synchronised instantly via a Mercure hub; no page reloads, no polling.
Created by SplendidBear.
Features
- Real-time 1v1 gameplay — moves broadcast instantly over Mercure (server-sent events)
- Guest & registered play — jump in anonymously or create an account for stats and history
- Full authentication stack — email/password, passkeys (WebAuthn), TOTP 2FA with backup codes
- Player profiles — win/loss/draw stats, per-month charts, recent battle history with shareable replay links
- Profile pictures — uploaded to MinIO object storage, thumbnails generated on-the-fly by LiipImagine
- Battle replay sharing — share a direct link to any finished game
- Docker-ready — single
make start-buildbrings up the full production-like stack
Tech stack
| Layer | Technology |
|---|---|
| Backend | PHP 8.5, Symfony 7.4, Doctrine ORM |
| Frontend | React 19, Vite, MUI, SCSS |
| Database | PostgreSQL 18 |
| Real-time | Mercure (built into FrankenPHP / Caddy) |
| File storage | MinIO (S3-compatible) |
| Image processing | LiipImagine + Flysystem |
| Server | FrankenPHP (Caddy + PHP in one binary) |
| Auth | Symfony Security, Scheb 2FA, web-auth/webauthn-framework |
Requirements
Bare-metal development
- PHP >= 8.5 with extensions:
pdo_pgsql,gd,intl,zip,sodium - Composer 2
- Node.js 22 + Bun
- PostgreSQL 18
- Caddy with FrankenPHP and the Mercure module
- MailHog (or any SMTP server on port 1025)
- MinIO (listening on port 9000)
Docker
- Docker Engine 24+
- Docker Compose v2
- Bun (for building frontend assets before
make start-build) - Composer (for
make start-build)
Installation
1. Clone the repository
git clone https://github.com/splendidbear/mineseeker.git
cd mineseeker
2. Configure environment
cp .env.dist .env
Edit .env and fill in every value. Key ones:
| Variable | What to set |
|---|---|
APP_SECRET |
Random 32-byte hex: openssl rand -hex 32 |
POSTGRES_USER/PASSWORD/DB |
Your PostgreSQL credentials |
MINIO_ROOT_USER/PASSWORD |
MinIO admin credentials |
MINIO_ENDPOINT |
http://localhost:9000 (bare-metal) |
MINIO_PUBLIC_URL |
Public URL browsers use to reach MinIO |
RECAPTCHA_SITE_KEY/SECRET_KEY |
Google reCAPTCHA v3 keys for your domain |
MERCURE_JWT_SECRET |
Random secret (generated in step 3) |
MERCURE_JWT_TOKEN |
Signed publisher JWT (generated in step 3) |
MERCURE_SUBSCRIBER_JWT |
Signed subscriber JWT (generated in step 3) |
MAILER_DSN |
smtp://localhost:1025 for MailHog in dev |
3. Generate Mercure JWT tokens
composer install
make mercure-jwt
Copy the printed values into .env and into the publisher_jwt / subscriber_jwt lines of your Caddy Mercure block, then reload Caddy:
sudo systemctl reload caddy
4a. Run with Docker
make start-build
This installs PHP dependencies, builds the frontend assets with Bun, builds the Docker image, and starts all services (app, db, mail, minio, minio_init).
The app is available at http://localhost:10080 (or the domain set in APP_PUBLIC_HOSTNAME).
To apply any code changes later, run the same command again.
4b. Run bare-metal (development)
composer install
bun install
bun run dev # Vite dev server with hot-reload
php bin/console doctrine:migrations:migrate --no-interaction
Start MinIO and MailHog, then open the URL configured in your Caddy vhost.
5. MinIO bucket setup
Docker — handled automatically on first start by the minio_init service. No action needed.
Bare-metal — run once after MinIO is up:
mc alias set local http://localhost:9000 $MINIO_ROOT_USER $MINIO_ROOT_PASSWORD
mc mb local/mineseeker
echo '' | mc pipe local/mineseeker/media/.keep
echo '' | mc pipe local/mineseeker/cache/.keep
# Apply public-read policy for media/ and cache/ — see docker/minio-init.sh for the JSON
Development environment
When running the Docker stack locally you typically want to catch outgoing emails instead of relaying them through a real SMTP server.
The production compose.yaml uses Postfix for actual mail delivery and must not be edited for local overrides.
Use a compose.override.yaml file instead — Docker Compose merges it automatically on top of the base file whenever both are present.
Email: replace Postfix with MailHog
Create compose.override.yaml in the project root (it is git-ignored and never reaches production):
services:
app:
environment:
MAILER_DSN: smtp://mail:1025?verify_peer=0
mail:
image: mailhog/mailhog:latest
ports:
- "8025:8025"
This replaces the mail service image with MailHog and points the application's mailer at its SMTP port (1025).
No other files need to change.
After adding the file, restart the stack:
make start-build
All emails sent by the application are now captured by MailHog. Open the web UI at http://localhost:8025 to inspect them.
Production note —
compose.override.yamlis listed in.gitignore. Never commit it; the production server must only seecompose.yamlwith Postfix.
License
LGPL-3.0 — see LICENSE for details.
© 2026 SplendidBear