Private
Public Access
1
0
Lang b209ad4220
Some checks failed
Deploy to Production / test (push) Failing after 6s
Deploy to Production / deploy (push) Successful in 33s
chg: pkg: the original CI/CD workflow is restored - the work with tests is postponed #10
2026-04-21 18:11:54 +02:00
2016-11-21 13:45:28 +01:00

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-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

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
      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:

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 notecompose.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:

APP_NAME=mineseeker
APP_ENV=prod
APP_SECRET="<openssl rand -hex 32>"

DATABASE_URL=postgresql://POSTGRES_USER:POSTGRES_PASSWORD@db:5432/POSTGRES_DB?serverVersion=18&charset=utf8

POSTGRES_USER=mineseeker
POSTGRES_PASSWORD="<strong password>"
POSTGRES_DB=mineseeker
POSTGRES_VERSION=18

MINIO_ROOT_USER=mineseeker
MINIO_ROOT_PASSWORD="<strong 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="<your reCAPTCHA v3 site key>"
RECAPTCHA_SECRET_KEY="<your reCAPTCHA v3 secret key>"

MERCURE_URL=https://mineseeker.hu/.well-known/mercure
MERCURE_PUBLIC_URL=https://mineseeker.hu/.well-known/mercure
MERCURE_JWT_SECRET="<generated by make mercure-jwt>"
MERCURE_JWT_TOKEN="<generated by make mercure-jwt>"
MERCURE_SUBSCRIBER_JWT="<generated by make mercure-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

git clone https://gitea.mineseeker.hu/youruser/mineseeker.git /var/www/mineseeker

2. Generate Mercure JWT tokens (run once locally)

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:

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

docker compose ps        # all services should be healthy/running
docker compose logs app  # look for "Starting FrankenPHP"

Releasing

git tag v2026.01
git push origin v2026.01

Documentation

For detailed information about game mechanics, bonus systems, fonts, testing, and other technical details, see the docs directory:

  • AI Agent Guidelines — Comprehensive guide for AI coding agents working on this project
  • Bonus Points System — Complete reference for all bonus point types, calculation rules, and implementation details
  • Fonts — TrueType fonts used for server-side image generation
  • Testing Guide — Complete testing setup with Foundry factories, database isolation, and best practices
  • Factory Documentation — Detailed API reference for all test data factories

License

LGPL-3.0 — see LICENSE for details.

© 2026 SplendidBear


Testing & CI/CD

MineSeeker has a comprehensive test suite with 71 automated tests and continuous integration/deployment pipelines.

Quick Start

# Setup test database (first time only)
make test-db-setup

# Run all tests
make test

# Run with documentation output
vendor/bin/phpunit --testdox

Test Suite

  • 71 tests with 227 assertions
  • Controller tests - HTTP endpoints, authentication, routing
  • DTO tests - Data serialization and calculations
  • Entity tests - Domain logic and defaults
  • Service tests - Business logic and external APIs
  • Integration tests - Foundry factories and database isolation

Test execution time: ~6-8 seconds

Continuous Integration

Automated testing runs on every push/pull request:

# .gitea/workflows/ci.yml-bak
✓ PHP 8.3 setup with all extensions
✓ PostgreSQL 18 service container
✓ Composer and npm dependency installation
✓ Asset building with Vite
✓ Database migrations
✓ Full test suite execution
✓ Code linting (ESLint, PHP-CS-Fixer)

Continuous Deployment

Automated deployment on version tags (e.g., v1.2.3):

# .gitea/workflows/deploy.yml
1. Run full test suite (blocks deployment if fails)
2. Checkout tagged version
3. Build Docker image
4. Run database migrations
5. Restart services
6. Health check verification

Deploy to production:

git tag -a v1.2.3 -m "Release version 1.2.3"
git push origin v1.2.3

Documentation


Description
Multiplayer minesweeper game. Inspired on the original Microsoft MSN Messenger's multiplayer game.
https://www.mineseeker.hu
Readme 48 MiB
Languages
PHP 42.8%
SCSS 20.1%
JavaScript 19.8%
Twig 16.4%
Makefile 0.5%
Other 0.4%