Private
Public Access
381 lines
12 KiB
Markdown
381 lines
12 KiB
Markdown
# 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 RustFS 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 | RustFS (S3-compatible; one-time migration from legacy MinIO) |
|
|
| 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)
|
|
- RustFS (API on port 9010, console on port 9011); legacy MinIO source on 9012/9013 during migration
|
|
|
|
### 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` | S3 storage credentials (used by RustFS and legacy MinIO during migration) |
|
|
| `MINIO_ENDPOINT` | `http://localhost:9010` (bare-metal) |
|
|
| `MINIO_PUBLIC_URL` | Public URL browsers use to reach RustFS (including media and LiipImagine thumbnails) |
|
|
| `CAP_API_ENDPOINT` | Full URL of your Cap instance including the site-key path (e.g. `https://cap.example.com/my-site-key/`) |
|
|
| `CAP_SECRET_KEY` | Secret key from your Cap site dashboard (stored in `PROD_ENV_FILE` Gitea secret on prod) |
|
|
| `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 images, and starts all services. RustFS migration runs automatically once before the app and scheduler start.
|
|
|
|
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 RustFS and MailHog, then open the URL configured in your Caddy vhost.
|
|
|
|
### 5. Object storage setup
|
|
|
|
**Docker** — `make start` and `make start-build` run the one-shot migration before starting the app. Production releases pause writers, migrate and verify all MinIO buckets, start the RustFS-backed app, then remove the MinIO container while retaining its volume. See [Object storage migration](docs/MINIO_INITIALIZATION.md).
|
|
|
|
**Bare-metal** — run once after MinIO is up:
|
|
|
|
```bash
|
|
mc alias set local http://localhost:9010 $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="<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://rustfs:9000
|
|
MINIO_PUBLIC_URL=https://aws.mineseeker.hu
|
|
|
|
MAILER_DSN=smtp://mail:25?verify_peer=0
|
|
MAIL_DOMAIN=mineseeker.hu
|
|
|
|
CAP_API_ENDPOINT="https://cap.example.com/your-site-key/"
|
|
CAP_SECRET_KEY="<secret key from your Cap site dashboard>"
|
|
|
|
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
|
|
|
|
```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 images, runs database migrations, and—if the legacy MinIO container exists—pauses app writers, copies and verifies all its buckets in RustFS, then removes the MinIO container after the app is running on RustFS. The MinIO data volume is retained.
|
|
|
|
#### 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, testing, 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
|
|
- **[Testing Guide](./docs/testing/TESTING.md)** — Complete testing setup with Foundry factories, database isolation, and best practices
|
|
- **[Factory Documentation](./docs/testing/FACTORIES.md)** — Detailed API reference for all test data factories
|
|
|
|
---
|
|
|
|
## License
|
|
|
|
LGPL-3.0 — see [LICENSE](LICENSE) for details.
|
|
|
|
© 2026 [SplendidBear](https://www.splendidbear.org)
|
|
|
|
---
|
|
|
|
## Testing & CI/CD
|
|
|
|
MineSeeker has a comprehensive test suite with **71 automated tests** and continuous integration/deployment pipelines.
|
|
|
|
### Quick Start
|
|
|
|
```bash
|
|
# 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:
|
|
|
|
```yaml
|
|
# .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`):
|
|
|
|
```yaml
|
|
# .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:**
|
|
```bash
|
|
git tag -a v1.2.3 -m "Release version 1.2.3"
|
|
git push origin v1.2.3
|
|
```
|
|
|
|
### Documentation
|
|
|
|
- **[Testing Guide](docs/testing/TESTING.md)** - Comprehensive testing documentation
|
|
- **[Factory Reference](docs/testing/FACTORIES.md)** - Foundry factory API
|
|
- **[CI/CD Guide](docs/CI_CD.md)** - Pipeline configuration and workflows
|
|
|
|
---
|