# 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](https://www.splendidbear.org). --- ## 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-build` brings 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 ```bash git clone https://github.com/splendidbear/mineseeker.git cd mineseeker ``` ### 2. Configure environment ```bash 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 ```bash 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: ```bash sudo systemctl reload caddy ``` ### 4a. Run with Docker ```bash 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) ```bash 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: ```bash 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): ```yaml services: app: environment: MAILER_DSN: smtp://mail:1025?verify_peer=0 TRUSTED_PROXIES: "0.0.0.0/0" mail: image: mailhog/mailhog:latest ports: - "1025:1025" - "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: ```bash 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.yaml` is listed in `.gitignore`. > Never commit it; the production server must only see `compose.yaml` with Postfix. --- ## Deploying to production Releases are automated via Gitea Actions. Pushing a tag that starts with `v` (e.g. `v2026.01`) triggers the workflow at `.gitea/workflows/deploy.yml`. The job runs on a **self-hosted runner** installed on the production server — the server only needs an outbound connection to Gitea, no open SSH port required. The `app` image is rebuilt with the new code; the database and storage containers are untouched so all data is preserved. ### Gitea repository variables and secrets **Variable** (plaintext, editable — **Repository → Settings → Variables**): | Variable | Value | |---|---| | `PROD_APP_DIR` | Absolute path on the server (e.g. `/var/www/mineseeker`) | **Secret** (encrypted, write-only — **Repository → Settings → Secrets**): | Secret | Value | |---|---| | `PROD_ENV_FILE` | Full content of the production `.env` file (see below) | The workflow writes `PROD_ENV_FILE` to `.env` on every deploy, so you never need to manage the file on the server manually. To update a credential, overwrite the secret in Gitea and push a new tag. #### `PROD_ENV_FILE` contents Paste the filled-in `.env` file as the secret value: ```dotenv APP_NAME=mineseeker APP_ENV=prod APP_SECRET="" DATABASE_URL=postgresql://POSTGRES_USER:POSTGRES_PASSWORD@db:5432/POSTGRES_DB?serverVersion=18&charset=utf8 POSTGRES_USER=mineseeker POSTGRES_PASSWORD="" POSTGRES_DB=mineseeker POSTGRES_VERSION=18 MINIO_ROOT_USER=mineseeker MINIO_ROOT_PASSWORD="" MINIO_ENDPOINT=http://minio:9000 MINIO_PUBLIC_URL=https://aws.mineseeker.hu MAILER_DSN=smtp://mail:25?verify_peer=0 MAIL_DOMAIN=mineseeker.hu RECAPTCHA_SITE_KEY="" RECAPTCHA_SECRET_KEY="" MERCURE_URL=https://mineseeker.hu/.well-known/mercure MERCURE_PUBLIC_URL=https://mineseeker.hu/.well-known/mercure MERCURE_JWT_SECRET="" MERCURE_JWT_TOKEN="" MERCURE_SUBSCRIBER_JWT="" APP_PUBLIC_HOSTNAME=mineseeker.hu WEBAUTHN_RP_ID=mineseeker.hu WEBAUTHN_ORIGIN=https://mineseeker.hu # OG Tags & Social Media Sharing (IMPORTANT for Docker deployments) # TRUSTED_PROXIES: IP address (or range) of your reverse proxy (Caddy/Nginx) # This ensures OG image tags use HTTPS URLs instead of HTTP TRUSTED_PROXIES="172.18.0.0/16" TRUSTED_HOSTS="mineseeker.hu,www.mineseeker.hu" ``` ### Production server: one-time setup The server needs Docker, Git, and a self-hosted `act_runner` registered against the Gitea repository. Bun and Composer run inside the multi-stage Dockerfile, so they are not needed on the server. #### 1. Clone the repository ```bash git clone https://gitea.mineseeker.hu/youruser/mineseeker.git /var/www/mineseeker ``` #### 2. Generate Mercure JWT tokens (run once locally) ```bash composer install # only needed for this step make mercure-jwt ``` Copy the three printed values into the `PROD_ENV_FILE` secret. #### 3. First deploy Trigger it by pushing the first tag: ```bash git tag v2026.01 git push origin v2026.01 ``` This writes `.env`, builds the Docker image, starts all services, runs migrations, and initialises the MinIO buckets automatically via `minio_init`. #### 4. Verify ```bash docker compose ps # all services should be healthy/running docker compose logs app # look for "Starting FrankenPHP" ``` ### Releasing ```bash git tag v2026.01 git push origin v2026.01 ``` --- ## Documentation For detailed information about game mechanics, bonus systems, fonts, and other technical details, see the [docs](./docs/) directory: - **[AI Agent Guidelines](./AGENTS.md)** — Comprehensive guide for AI coding agents working on this project - **[Bonus Points System](./docs/game-mechanics/BONUS_POINTS_SYSTEM.md)** — Complete reference for all bonus point types, calculation rules, and implementation details - **[Fonts](./docs/FONTS.md)** — TrueType fonts used for server-side image generation --- ## License LGPL-3.0 — see [LICENSE](LICENSE) for details. © 2026 [SplendidBear](https://www.splendidbear.org)