Private
Public Access
Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dff39d670b | ||
|
|
fe3338b96c | ||
|
|
721eada72f | ||
|
|
7de1514cd9 | ||
|
|
3e90bbe5cf | ||
|
|
1d8d0ac110 |
@@ -32,12 +32,12 @@ CAP_API_ENDPOINT=http://localhost:3000/changethis/
|
|||||||
CAP_SECRET_KEY=changethis
|
CAP_SECRET_KEY=changethis
|
||||||
###< trycap.dev/cap ###
|
###< trycap.dev/cap ###
|
||||||
|
|
||||||
###> minio/minio ###
|
###> object-storage ###
|
||||||
MINIO_ROOT_USER=changethis
|
STORAGE_ACCESS_KEY=changethis
|
||||||
MINIO_ROOT_PASSWORD=changethis
|
STORAGE_SECRET_KEY=changethis
|
||||||
MINIO_ENDPOINT=http://localhost:9010
|
STORAGE_ENDPOINT=http://localhost:9010
|
||||||
MINIO_PUBLIC_URL=https://your-minio-subdomain.example.com
|
STORAGE_PUBLIC_URL=https://your-storage-subdomain.example.com
|
||||||
###< minio/minio ###
|
###< object-storage ###
|
||||||
|
|
||||||
###> symfony/mailer ###
|
###> symfony/mailer ###
|
||||||
MAILER_DSN=smtp://localhost:1025
|
MAILER_DSN=smtp://localhost:1025
|
||||||
|
|||||||
@@ -33,12 +33,12 @@ jobs:
|
|||||||
cd "${{ vars.PROD_APP_DIR }}"
|
cd "${{ vars.PROD_APP_DIR }}"
|
||||||
docker compose build
|
docker compose build
|
||||||
|
|
||||||
- name: Start services
|
- name: Deploy
|
||||||
env:
|
env:
|
||||||
APP_VERSION: ${{ vars.APP_VERSION }}
|
APP_VERSION: ${{ vars.APP_VERSION }}
|
||||||
run: |
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
cd "${{ vars.PROD_APP_DIR }}"
|
cd "${{ vars.PROD_APP_DIR }}"
|
||||||
docker compose up -d minio
|
# --remove-orphans removes retired service containers, not named volumes.
|
||||||
docker compose rm -sf minio_init
|
docker compose up -d --wait --wait-timeout 180 --remove-orphans
|
||||||
docker compose run --rm minio_init
|
docker compose rm -sf rustfs_init
|
||||||
docker compose up -d
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ This document provides guidelines and context for AI coding agents working on th
|
|||||||
- **Backend:** Symfony 7.2 (PHP 8.3)
|
- **Backend:** Symfony 7.2 (PHP 8.3)
|
||||||
- **Frontend:** React 18, Vite 6
|
- **Frontend:** React 18, Vite 6
|
||||||
- **Database:** PostgreSQL 17 with materialized views
|
- **Database:** PostgreSQL 17 with materialized views
|
||||||
- **Storage:** MinIO (S3-compatible)
|
- **Storage:** RustFS (S3-compatible)
|
||||||
- **Real-time:** Mercure (Server-Sent Events)
|
- **Real-time:** Mercure (Server-Sent Events)
|
||||||
- **Styling:** SCSS, MUI (Material-UI), Emotion
|
- **Styling:** SCSS, MUI (Material-UI), Emotion
|
||||||
- **Fonts:** @fontsource packages (web), Carlito-Bold.ttf (server-side images)
|
- **Fonts:** @fontsource packages (web), Carlito-Bold.ttf (server-side images)
|
||||||
|
|||||||
@@ -1,6 +1,24 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
|
||||||
|
## v2026.4.0-1 (2026-10-24)
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
|
||||||
|
* Remove all minio related stuff - & clean up and unificate the storage related code #16. [Lang]
|
||||||
|
|
||||||
|
|
||||||
|
## v2026.4.0-0 (2026-10-24)
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
|
||||||
|
* Migrate from shitty Minio to RustFS #16. [Lang]
|
||||||
|
|
||||||
|
### Fix
|
||||||
|
|
||||||
|
* Upgrade PHP due to the security considerations #16. [Lang]
|
||||||
|
|
||||||
|
|
||||||
## v2026.3.1-2 (2026-09-21)
|
## v2026.3.1-2 (2026-09-21)
|
||||||
|
|
||||||
### Changes
|
### Changes
|
||||||
|
|||||||
@@ -18,7 +18,10 @@
|
|||||||
header X-Forwarded-Host {host}
|
header X-Forwarded-Host {host}
|
||||||
|
|
||||||
mercure {
|
mercure {
|
||||||
transport_url {$MERCURE_TRANSPORT_URL:bolt:///data/mercure.db}
|
transport bolt {
|
||||||
|
path /data/mercure.db
|
||||||
|
}
|
||||||
|
protocol_version_compatibility 8
|
||||||
publisher_jwt {$MERCURE_JWT_SECRET} HS256
|
publisher_jwt {$MERCURE_JWT_SECRET} HS256
|
||||||
subscriber_jwt {$MERCURE_JWT_SECRET} HS256
|
subscriber_jwt {$MERCURE_JWT_SECRET} HS256
|
||||||
anonymous
|
anonymous
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
|
|
||||||
help:
|
help:
|
||||||
@echo "Available commands:"
|
@echo "Available commands:"
|
||||||
@echo " make start - Start services and initialize MinIO (uses existing images)"
|
@echo " make start - Start services"
|
||||||
@echo " make start-build - Build, start services, and initialize MinIO"
|
@echo " make start-build - Build and start services"
|
||||||
@echo " make stop - Stop running services"
|
@echo " make stop - Stop running services"
|
||||||
@echo " make build - Build Docker images only"
|
@echo " make build - Build Docker images only"
|
||||||
@echo " make down - Stop and remove containers/networks"
|
@echo " make down - Stop and remove containers/networks"
|
||||||
@@ -19,18 +19,14 @@ help:
|
|||||||
@echo " make test - Run PHPUnit tests"
|
@echo " make test - Run PHPUnit tests"
|
||||||
|
|
||||||
start:
|
start:
|
||||||
docker compose up -d minio
|
docker compose up -d --remove-orphans app scheduler
|
||||||
docker compose rm -sf minio_init
|
docker compose rm -sf rustfs_init
|
||||||
docker compose run --rm minio_init
|
|
||||||
docker compose up -d
|
|
||||||
|
|
||||||
start-build:
|
start-build:
|
||||||
composer i
|
composer i
|
||||||
bun run build
|
bun run build
|
||||||
docker compose up -d --build minio
|
docker compose up -d --build --remove-orphans app scheduler
|
||||||
docker compose rm -sf minio_init
|
docker compose rm -sf rustfs_init
|
||||||
docker compose run --rm minio_init
|
|
||||||
docker compose up -d --build
|
|
||||||
|
|
||||||
stop:
|
stop:
|
||||||
docker compose stop
|
docker compose stop
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ Created by [SplendidBear](https://www.splendidbear.org).
|
|||||||
- **Guest & registered play** — jump in anonymously or create an account for stats and history
|
- **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
|
- **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
|
- **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
|
- **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
|
- **Battle replay sharing** — share a direct link to any finished game
|
||||||
- **Docker-ready** — single `make start-build` brings up the full production-like stack
|
- **Docker-ready** — single `make start-build` brings up the full production-like stack
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@ Created by [SplendidBear](https://www.splendidbear.org).
|
|||||||
| Frontend | React 19, Vite, MUI, SCSS |
|
| Frontend | React 19, Vite, MUI, SCSS |
|
||||||
| Database | PostgreSQL 18 |
|
| Database | PostgreSQL 18 |
|
||||||
| Real-time | Mercure (built into FrankenPHP / Caddy) |
|
| Real-time | Mercure (built into FrankenPHP / Caddy) |
|
||||||
| File storage | MinIO (S3-compatible) |
|
| File storage | RustFS (S3-compatible) |
|
||||||
| Image processing | LiipImagine + Flysystem |
|
| Image processing | LiipImagine + Flysystem |
|
||||||
| Server | FrankenPHP (Caddy + PHP in one binary) |
|
| Server | FrankenPHP (Caddy + PHP in one binary) |
|
||||||
| Auth | Symfony Security, Scheb 2FA, web-auth/webauthn-framework |
|
| Auth | Symfony Security, Scheb 2FA, web-auth/webauthn-framework |
|
||||||
@@ -44,7 +44,7 @@ Created by [SplendidBear](https://www.splendidbear.org).
|
|||||||
- PostgreSQL 18
|
- PostgreSQL 18
|
||||||
- Caddy with FrankenPHP and the Mercure module
|
- Caddy with FrankenPHP and the Mercure module
|
||||||
- MailHog (or any SMTP server on port 1025)
|
- MailHog (or any SMTP server on port 1025)
|
||||||
- MinIO (API on port 9010, console on port 9011)
|
- RustFS (API on port 9010, console on port 9011)
|
||||||
|
|
||||||
### Docker
|
### Docker
|
||||||
- Docker Engine 24+
|
- Docker Engine 24+
|
||||||
@@ -75,9 +75,9 @@ Edit `.env` and fill in every value. Key ones:
|
|||||||
|---|---|
|
|---|---|
|
||||||
| `APP_SECRET` | Random 32-byte hex: `openssl rand -hex 32` |
|
| `APP_SECRET` | Random 32-byte hex: `openssl rand -hex 32` |
|
||||||
| `POSTGRES_USER/PASSWORD/DB` | Your PostgreSQL credentials |
|
| `POSTGRES_USER/PASSWORD/DB` | Your PostgreSQL credentials |
|
||||||
| `MINIO_ROOT_USER/PASSWORD` | MinIO admin credentials |
|
| `STORAGE_ACCESS_KEY` / `STORAGE_SECRET_KEY` | RustFS S3 credentials |
|
||||||
| `MINIO_ENDPOINT` | `http://localhost:9010` (bare-metal) |
|
| `STORAGE_ENDPOINT` | RustFS S3 API URL (`http://localhost:9010` for bare-metal) |
|
||||||
| `MINIO_PUBLIC_URL` | Public URL browsers use to reach MinIO |
|
| `STORAGE_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_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) |
|
| `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_SECRET` | Random secret (generated in step 3) |
|
||||||
@@ -104,7 +104,7 @@ sudo systemctl reload caddy
|
|||||||
make start-build
|
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`).
|
This installs PHP dependencies, builds the frontend assets with Bun, builds the Docker images, initializes the RustFS bucket and public media/cache policy, and starts the services.
|
||||||
|
|
||||||
The app is available at `http://localhost:10080` (or the domain set in `APP_PUBLIC_HOSTNAME`).
|
The app is available at `http://localhost:10080` (or the domain set in `APP_PUBLIC_HOSTNAME`).
|
||||||
|
|
||||||
@@ -119,21 +119,15 @@ bun run dev # Vite dev server with hot-reload
|
|||||||
php bin/console doctrine:migrations:migrate --no-interaction
|
php bin/console doctrine:migrations:migrate --no-interaction
|
||||||
```
|
```
|
||||||
|
|
||||||
Start MinIO and MailHog, then open the URL configured in your Caddy vhost.
|
Start RustFS and MailHog, then open the URL configured in your Caddy vhost.
|
||||||
|
|
||||||
### 5. MinIO bucket setup
|
### 5. Object storage setup
|
||||||
|
|
||||||
**Docker** — handled automatically on first start by the `minio_init` service. No action needed.
|
The Docker stack initializes the `mineseeker` bucket and configures public reads for media and thumbnail objects automatically. For bare-metal installs, create that bucket in RustFS and allow public `GetObject` access for `media/*` and `cache/*`.
|
||||||
|
|
||||||
**Bare-metal** — run once after MinIO is up:
|
### Object storage backup and restore
|
||||||
|
|
||||||
```bash
|
`bin/backup-rustfs.sh [backup-directory]` creates a compressed archive of the RustFS data volume (default: `backups/`). It briefly stops the app, scheduler, and RustFS for a consistent backup, then restarts the services that were running. To restore, run `bin/restore-rustfs.sh <backup-file>`; it verifies the archive and requires typing `yes` before replacing the current RustFS data.
|
||||||
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
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -199,6 +193,8 @@ The `app` image is rebuilt with the new code; the database and storage container
|
|||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
|
Before deploying this RustFS-only configuration, update `PROD_ENV_FILE` to use `STORAGE_ACCESS_KEY`, `STORAGE_SECRET_KEY`, `STORAGE_ENDPOINT`, and `STORAGE_PUBLIC_URL` (the old `MINIO_*` names are no longer read).
|
||||||
|
|
||||||
#### `PROD_ENV_FILE` contents
|
#### `PROD_ENV_FILE` contents
|
||||||
|
|
||||||
Paste the filled-in `.env` file as the secret value:
|
Paste the filled-in `.env` file as the secret value:
|
||||||
@@ -215,10 +211,10 @@ POSTGRES_PASSWORD="<strong password>"
|
|||||||
POSTGRES_DB=mineseeker
|
POSTGRES_DB=mineseeker
|
||||||
POSTGRES_VERSION=18
|
POSTGRES_VERSION=18
|
||||||
|
|
||||||
MINIO_ROOT_USER=mineseeker
|
STORAGE_ACCESS_KEY=mineseeker
|
||||||
MINIO_ROOT_PASSWORD="<strong password>"
|
STORAGE_SECRET_KEY="<strong password>"
|
||||||
MINIO_ENDPOINT=http://minio:9000
|
STORAGE_ENDPOINT=http://rustfs:9000
|
||||||
MINIO_PUBLIC_URL=https://aws.mineseeker.hu
|
STORAGE_PUBLIC_URL=https://aws.mineseeker.hu
|
||||||
|
|
||||||
MAILER_DSN=smtp://mail:25?verify_peer=0
|
MAILER_DSN=smtp://mail:25?verify_peer=0
|
||||||
MAIL_DOMAIN=mineseeker.hu
|
MAIL_DOMAIN=mineseeker.hu
|
||||||
@@ -270,7 +266,7 @@ git tag v2026.01
|
|||||||
git push origin 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`.
|
This writes `.env`, builds the Docker images, runs database migrations, initializes RustFS, and starts the application services.
|
||||||
|
|
||||||
#### 4. Verify
|
#### 4. Verify
|
||||||
|
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
set -eu
|
|
||||||
|
|
||||||
PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
|
||||||
cd "$PROJECT_DIR"
|
|
||||||
|
|
||||||
CONTAINER_ID=$(docker compose ps -q minio)
|
|
||||||
if [ -z "$CONTAINER_ID" ]; then
|
|
||||||
echo "Error: The MineSeeker MinIO service is not running."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
VOLUME_NAME=$(docker inspect "$CONTAINER_ID" --format '{{range .Mounts}}{{if eq .Destination "/data"}}{{.Name}}{{end}}{{end}}')
|
|
||||||
if [ -z "$VOLUME_NAME" ]; then
|
|
||||||
echo "Error: Could not determine the MinIO data volume."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
BACKUP_DIR=${1:-"$PROJECT_DIR/backups"}
|
|
||||||
mkdir -p "$BACKUP_DIR"
|
|
||||||
BACKUP_DIR=$(CDPATH= cd -- "$BACKUP_DIR" && pwd)
|
|
||||||
TIMESTAMP=$(date +%Y-%m-%d.%H%M%S)
|
|
||||||
BACKUP_FILE="mineseeker-minio.${TIMESTAMP}.tar.gz"
|
|
||||||
BACKUP_PATH="$BACKUP_DIR/$BACKUP_FILE"
|
|
||||||
|
|
||||||
umask 077
|
|
||||||
|
|
||||||
restart_minio() {
|
|
||||||
docker compose start minio || echo "Warning: Failed to restart MinIO."
|
|
||||||
}
|
|
||||||
trap restart_minio EXIT
|
|
||||||
|
|
||||||
echo "Stopping MinIO for a consistent backup..."
|
|
||||||
docker compose stop minio
|
|
||||||
|
|
||||||
echo "Creating MinIO backup: $BACKUP_PATH"
|
|
||||||
docker run --rm \
|
|
||||||
--volume "$VOLUME_NAME:/data:ro" \
|
|
||||||
--volume "$BACKUP_DIR:/backup" \
|
|
||||||
busybox tar czf "/backup/$BACKUP_FILE" -C /data .
|
|
||||||
|
|
||||||
echo "Backup complete: $BACKUP_PATH"
|
|
||||||
Executable
+54
@@ -0,0 +1,54 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
||||||
|
cd "$PROJECT_DIR"
|
||||||
|
|
||||||
|
CONTAINER_ID=$(docker compose ps -q rustfs)
|
||||||
|
if [ -z "$CONTAINER_ID" ]; then
|
||||||
|
echo "Error: The MineSeeker RustFS service is not running."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
VOLUME_NAME=$(docker inspect "$CONTAINER_ID" --format '{{range .Mounts}}{{if eq .Destination "/data"}}{{.Name}}{{end}}{{end}}')
|
||||||
|
if [ -z "$VOLUME_NAME" ]; then
|
||||||
|
echo "Error: Could not determine the RustFS data volume."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
BACKUP_DIR=${1:-"$PROJECT_DIR/backups"}
|
||||||
|
mkdir -p "$BACKUP_DIR"
|
||||||
|
BACKUP_DIR=$(CDPATH= cd -- "$BACKUP_DIR" && pwd)
|
||||||
|
TIMESTAMP=$(date +%Y-%m-%d.%H%M%S)
|
||||||
|
BACKUP_FILE="mineseeker-rustfs.${TIMESTAMP}.tar.gz"
|
||||||
|
BACKUP_PATH="$BACKUP_DIR/$BACKUP_FILE"
|
||||||
|
TEMP_PATH="$BACKUP_PATH.tmp"
|
||||||
|
|
||||||
|
umask 077
|
||||||
|
APP_WAS_RUNNING=0
|
||||||
|
SCHEDULER_WAS_RUNNING=0
|
||||||
|
RUSTFS_WAS_RUNNING=1
|
||||||
|
[ -z "$(docker compose ps -q app)" ] || APP_WAS_RUNNING=1
|
||||||
|
[ -z "$(docker compose ps -q scheduler)" ] || SCHEDULER_WAS_RUNNING=1
|
||||||
|
|
||||||
|
restart_services() {
|
||||||
|
result=$?
|
||||||
|
rm -f "$TEMP_PATH"
|
||||||
|
[ "$RUSTFS_WAS_RUNNING" -eq 0 ] || docker compose start rustfs || true
|
||||||
|
[ "$APP_WAS_RUNNING" -eq 0 ] || docker compose start app || true
|
||||||
|
[ "$SCHEDULER_WAS_RUNNING" -eq 0 ] || docker compose start scheduler || true
|
||||||
|
return "$result"
|
||||||
|
}
|
||||||
|
trap restart_services EXIT
|
||||||
|
|
||||||
|
echo "Stopping MineSeeker writers and RustFS for a consistent backup..."
|
||||||
|
docker compose stop app scheduler rustfs
|
||||||
|
|
||||||
|
echo "Creating RustFS backup: $BACKUP_PATH"
|
||||||
|
docker run --rm \
|
||||||
|
--volume "$VOLUME_NAME:/data:ro" \
|
||||||
|
--volume "$BACKUP_DIR:/backup" \
|
||||||
|
busybox tar czf "/backup/$BACKUP_FILE.tmp" -C /data .
|
||||||
|
mv "$TEMP_PATH" "$BACKUP_PATH"
|
||||||
|
|
||||||
|
echo "Backup complete: $BACKUP_PATH"
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
set -eu
|
|
||||||
|
|
||||||
PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
|
||||||
cd "$PROJECT_DIR"
|
|
||||||
|
|
||||||
if [ "$#" -ne 1 ]; then
|
|
||||||
echo "Usage: $0 <backup-file>"
|
|
||||||
echo "Example: $0 backups/mineseeker-minio.2026-08-18.120000.tar.gz"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
BACKUP_FILE=$1
|
|
||||||
if [ ! -f "$BACKUP_FILE" ]; then
|
|
||||||
echo "Error: Backup file not found: $BACKUP_FILE"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
BACKUP_DIR=$(CDPATH= cd -- "$(dirname -- "$BACKUP_FILE")" && pwd)
|
|
||||||
BACKUP_NAME=$(basename "$BACKUP_FILE")
|
|
||||||
|
|
||||||
CONTAINER_ID=$(docker compose ps -q minio)
|
|
||||||
if [ -z "$CONTAINER_ID" ]; then
|
|
||||||
echo "Error: The MineSeeker MinIO service is not running."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
VOLUME_NAME=$(docker inspect "$CONTAINER_ID" --format '{{range .Mounts}}{{if eq .Destination "/data"}}{{.Name}}{{end}}{{end}}')
|
|
||||||
if [ -z "$VOLUME_NAME" ]; then
|
|
||||||
echo "Error: Could not determine the MinIO data volume."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Verifying MinIO archive..."
|
|
||||||
docker run --rm \
|
|
||||||
--volume "$BACKUP_DIR:/backup:ro" \
|
|
||||||
busybox tar -tzf "/backup/$BACKUP_NAME" > /dev/null
|
|
||||||
echo "Archive OK."
|
|
||||||
|
|
||||||
printf 'WARNING: This erases all current MineSeeker MinIO data. Type yes to continue: '
|
|
||||||
read -r CONFIRM
|
|
||||||
if [ "$CONFIRM" != 'yes' ]; then
|
|
||||||
echo "Aborted."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
restart_minio() {
|
|
||||||
docker compose start minio || echo "Warning: Failed to restart MinIO."
|
|
||||||
}
|
|
||||||
trap restart_minio EXIT
|
|
||||||
|
|
||||||
echo "Stopping MinIO before restore..."
|
|
||||||
docker compose stop minio
|
|
||||||
|
|
||||||
echo "Restoring MinIO backup: $BACKUP_FILE"
|
|
||||||
docker run --rm \
|
|
||||||
--volume "$VOLUME_NAME:/data" \
|
|
||||||
--volume "$BACKUP_DIR:/backup:ro" \
|
|
||||||
busybox sh -c 'find /data -mindepth 1 -maxdepth 1 -exec rm -rf {} + && tar xzf "/backup/$1" -C /data' sh "$BACKUP_NAME"
|
|
||||||
|
|
||||||
echo "Restore complete."
|
|
||||||
+24
-5
@@ -28,6 +28,12 @@ if [ -z "$DB_USER" ]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
DB_NAME=$(docker inspect "$CONTAINER_ID" --format '{{range .Config.Env}}{{println .}}{{end}}' | sed -n 's/^POSTGRES_DB=//p')
|
||||||
|
if [ -z "$DB_NAME" ]; then
|
||||||
|
echo "Error: Could not read POSTGRES_DB from the db service."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
echo "Verifying PostgreSQL archive..."
|
echo "Verifying PostgreSQL archive..."
|
||||||
docker exec -i "$CONTAINER_ID" pg_restore --list < "$BACKUP_FILE" > /dev/null
|
docker exec -i "$CONTAINER_ID" pg_restore --list < "$BACKUP_FILE" > /dev/null
|
||||||
echo "Archive OK."
|
echo "Archive OK."
|
||||||
@@ -53,13 +59,26 @@ trap restart_services EXIT
|
|||||||
echo "Stopping MineSeeker services that use PostgreSQL..."
|
echo "Stopping MineSeeker services that use PostgreSQL..."
|
||||||
docker compose stop app scheduler
|
docker compose stop app scheduler
|
||||||
|
|
||||||
echo "Restoring PostgreSQL backup: $BACKUP_FILE"
|
echo "Dropping configured database '$DB_NAME'..."
|
||||||
|
docker exec "$CONTAINER_ID" dropdb \
|
||||||
|
--username="$DB_USER" \
|
||||||
|
--if-exists \
|
||||||
|
--force \
|
||||||
|
"$DB_NAME"
|
||||||
|
|
||||||
|
echo "Creating '$DB_NAME' owned by '$DB_USER'..."
|
||||||
|
docker exec "$CONTAINER_ID" createdb \
|
||||||
|
--username="$DB_USER" \
|
||||||
|
--owner="$DB_USER" \
|
||||||
|
--template=template0 \
|
||||||
|
"$DB_NAME"
|
||||||
|
|
||||||
|
echo "Restoring PostgreSQL backup without source ownership/ACLs: $BACKUP_FILE"
|
||||||
docker exec -i "$CONTAINER_ID" pg_restore \
|
docker exec -i "$CONTAINER_ID" pg_restore \
|
||||||
--username="$DB_USER" \
|
--username="$DB_USER" \
|
||||||
--dbname=postgres \
|
--dbname="$DB_NAME" \
|
||||||
--clean \
|
--no-owner \
|
||||||
--create \
|
--no-acl \
|
||||||
--if-exists \
|
|
||||||
--exit-on-error < "$BACKUP_FILE"
|
--exit-on-error < "$BACKUP_FILE"
|
||||||
|
|
||||||
echo "Restore complete."
|
echo "Restore complete."
|
||||||
|
|||||||
Executable
+77
@@ -0,0 +1,77 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
||||||
|
cd "$PROJECT_DIR"
|
||||||
|
|
||||||
|
if [ "$#" -ne 1 ]; then
|
||||||
|
echo "Usage: $0 <backup-file>"
|
||||||
|
echo "Example: $0 backups/mineseeker-rustfs.2026-10-24.120000.tar.gz"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
BACKUP_FILE=$1
|
||||||
|
if [ ! -f "$BACKUP_FILE" ]; then
|
||||||
|
echo "Error: Backup file not found: $BACKUP_FILE"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
BACKUP_DIR=$(CDPATH= cd -- "$(dirname -- "$BACKUP_FILE")" && pwd)
|
||||||
|
BACKUP_NAME=$(basename "$BACKUP_FILE")
|
||||||
|
|
||||||
|
CONTAINER_ID=$(docker compose ps -q rustfs)
|
||||||
|
if [ -z "$CONTAINER_ID" ]; then
|
||||||
|
echo "Error: The MineSeeker RustFS service must be running before restore."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
VOLUME_NAME=$(docker inspect "$CONTAINER_ID" --format '{{range .Mounts}}{{if eq .Destination "/data"}}{{.Name}}{{end}}{{end}}')
|
||||||
|
if [ -z "$VOLUME_NAME" ]; then
|
||||||
|
echo "Error: Could not determine the RustFS data volume."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Verifying RustFS archive..."
|
||||||
|
docker run --rm \
|
||||||
|
--volume "$BACKUP_DIR:/backup:ro" \
|
||||||
|
busybox tar -tzf "/backup/$BACKUP_NAME" > /dev/null
|
||||||
|
echo "Archive OK."
|
||||||
|
|
||||||
|
printf 'WARNING: This erases all current MineSeeker RustFS data. Type yes to continue: '
|
||||||
|
read -r CONFIRM
|
||||||
|
if [ "$CONFIRM" != 'yes' ]; then
|
||||||
|
echo "Aborted."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
APP_WAS_RUNNING=0
|
||||||
|
SCHEDULER_WAS_RUNNING=0
|
||||||
|
RUSTFS_WAS_RUNNING=1
|
||||||
|
[ -z "$(docker compose ps -q app)" ] || APP_WAS_RUNNING=1
|
||||||
|
[ -z "$(docker compose ps -q scheduler)" ] || SCHEDULER_WAS_RUNNING=1
|
||||||
|
RESTORE_COMPLETE=0
|
||||||
|
|
||||||
|
restart_services() {
|
||||||
|
result=$?
|
||||||
|
if [ "$RESTORE_COMPLETE" -eq 1 ]; then
|
||||||
|
[ "$RUSTFS_WAS_RUNNING" -eq 0 ] || docker compose start rustfs || true
|
||||||
|
[ "$APP_WAS_RUNNING" -eq 0 ] || docker compose start app || true
|
||||||
|
[ "$SCHEDULER_WAS_RUNNING" -eq 0 ] || docker compose start scheduler || true
|
||||||
|
elif [ "$result" -ne 0 ]; then
|
||||||
|
echo "Restore did not complete; RustFS and application services remain stopped for safety." >&2
|
||||||
|
fi
|
||||||
|
return "$result"
|
||||||
|
}
|
||||||
|
trap restart_services EXIT
|
||||||
|
|
||||||
|
echo "Stopping MineSeeker writers and RustFS before restore..."
|
||||||
|
docker compose stop app scheduler rustfs
|
||||||
|
|
||||||
|
echo "Restoring RustFS backup: $BACKUP_FILE"
|
||||||
|
docker run --rm \
|
||||||
|
--volume "$VOLUME_NAME:/data" \
|
||||||
|
--volume "$BACKUP_DIR:/backup:ro" \
|
||||||
|
busybox sh -c 'find /data -mindepth 1 -maxdepth 1 -exec rm -rf {} + && tar xzf "/backup/$1" -C /data' sh "$BACKUP_NAME"
|
||||||
|
|
||||||
|
RESTORE_COMPLETE=1
|
||||||
|
echo "Restore complete."
|
||||||
+51
-34
@@ -6,7 +6,7 @@ services:
|
|||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
container_name: '${APP_NAME}-app'
|
container_name: '${APP_NAME}-app'
|
||||||
ports:
|
ports:
|
||||||
- "10080:80"
|
- "${APP_PUBLISHED_PORT:-10080}:80"
|
||||||
environment:
|
environment:
|
||||||
SERVER_NAME: ${SERVER_NAME:-:80}
|
SERVER_NAME: ${SERVER_NAME:-:80}
|
||||||
APP_ENV: prod
|
APP_ENV: prod
|
||||||
@@ -30,10 +30,10 @@ services:
|
|||||||
CAP_SECRET_KEY: ${CAP_SECRET_KEY}
|
CAP_SECRET_KEY: ${CAP_SECRET_KEY}
|
||||||
WEBAUTHN_RP_ID: ${WEBAUTHN_RP_ID:-localhost}
|
WEBAUTHN_RP_ID: ${WEBAUTHN_RP_ID:-localhost}
|
||||||
WEBAUTHN_ORIGIN: ${WEBAUTHN_ORIGIN:-https://localhost}
|
WEBAUTHN_ORIGIN: ${WEBAUTHN_ORIGIN:-https://localhost}
|
||||||
MINIO_ROOT_USER: ${MINIO_ROOT_USER}
|
STORAGE_ACCESS_KEY: ${STORAGE_ACCESS_KEY}
|
||||||
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD}
|
STORAGE_SECRET_KEY: ${STORAGE_SECRET_KEY}
|
||||||
MINIO_ENDPOINT: http://minio:9000
|
STORAGE_ENDPOINT: http://rustfs:9000
|
||||||
MINIO_PUBLIC_URL: ${MINIO_PUBLIC_URL:-http://localhost:9010}
|
STORAGE_PUBLIC_URL: ${STORAGE_PUBLIC_URL:-http://localhost:9010}
|
||||||
# IMPORTANT: Set TRUSTED_PROXIES to your reverse proxy IP in production.
|
# IMPORTANT: Set TRUSTED_PROXIES to your reverse proxy IP in production.
|
||||||
# For Docker on same host, use: 172.17.0.1 (default bridge) or 172.16.0.0/12 (overlay network)
|
# For Docker on same host, use: 172.17.0.1 (default bridge) or 172.16.0.0/12 (overlay network)
|
||||||
# For Kubernetes or external proxy, use the proxy's IP address.
|
# For Kubernetes or external proxy, use the proxy's IP address.
|
||||||
@@ -43,11 +43,21 @@ services:
|
|||||||
- app_var:/app/var
|
- app_var:/app/var
|
||||||
- caddy_data:/data
|
- caddy_data:/data
|
||||||
- caddy_config:/config
|
- caddy_config:/config
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "curl -fsS http://127.0.0.1/ -o /dev/null"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 12
|
||||||
|
start_period: 60s
|
||||||
depends_on:
|
depends_on:
|
||||||
db:
|
db:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
mail:
|
mail:
|
||||||
condition: service_started
|
condition: service_started
|
||||||
|
rustfs:
|
||||||
|
condition: service_healthy
|
||||||
|
rustfs_init:
|
||||||
|
condition: service_completed_successfully
|
||||||
scheduler:
|
scheduler:
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
@@ -75,10 +85,10 @@ services:
|
|||||||
CAP_SECRET_KEY: ${CAP_SECRET_KEY}
|
CAP_SECRET_KEY: ${CAP_SECRET_KEY}
|
||||||
WEBAUTHN_RP_ID: ${WEBAUTHN_RP_ID:-localhost}
|
WEBAUTHN_RP_ID: ${WEBAUTHN_RP_ID:-localhost}
|
||||||
WEBAUTHN_ORIGIN: ${WEBAUTHN_ORIGIN:-https://localhost}
|
WEBAUTHN_ORIGIN: ${WEBAUTHN_ORIGIN:-https://localhost}
|
||||||
MINIO_ROOT_USER: ${MINIO_ROOT_USER}
|
STORAGE_ACCESS_KEY: ${STORAGE_ACCESS_KEY}
|
||||||
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD}
|
STORAGE_SECRET_KEY: ${STORAGE_SECRET_KEY}
|
||||||
MINIO_ENDPOINT: http://minio:9000
|
STORAGE_ENDPOINT: http://rustfs:9000
|
||||||
MINIO_PUBLIC_URL: ${MINIO_PUBLIC_URL:-http://localhost:9010}
|
STORAGE_PUBLIC_URL: ${STORAGE_PUBLIC_URL:-http://localhost:9010}
|
||||||
TRUSTED_PROXIES: ${TRUSTED_PROXIES:-127.0.0.1}
|
TRUSTED_PROXIES: ${TRUSTED_PROXIES:-127.0.0.1}
|
||||||
volumes:
|
volumes:
|
||||||
- app_var:/app/var
|
- app_var:/app/var
|
||||||
@@ -87,42 +97,49 @@ services:
|
|||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
mail:
|
mail:
|
||||||
condition: service_started
|
condition: service_started
|
||||||
|
rustfs:
|
||||||
|
condition: service_healthy
|
||||||
|
rustfs_init:
|
||||||
|
condition: service_completed_successfully
|
||||||
healthcheck:
|
healthcheck:
|
||||||
disable: true
|
disable: true
|
||||||
|
|
||||||
minio:
|
rustfs:
|
||||||
image: minio/minio:RELEASE.2025-09-07T16-13-09Z-cpuv1
|
image: rustfs/rustfs:1.0.3
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
container_name: '${APP_NAME}-minio'
|
container_name: '${APP_NAME}-rustfs'
|
||||||
command: server /data --console-address ":9001"
|
|
||||||
ports:
|
|
||||||
- "9010:9000"
|
|
||||||
- "9011:9001"
|
|
||||||
environment:
|
environment:
|
||||||
MINIO_ROOT_USER: ${MINIO_ROOT_USER}
|
RUSTFS_ACCESS_KEY: ${STORAGE_ACCESS_KEY}
|
||||||
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD}
|
RUSTFS_SECRET_KEY: ${STORAGE_SECRET_KEY}
|
||||||
|
RUSTFS_ADDRESS: ':9000'
|
||||||
|
RUSTFS_CONSOLE_ADDRESS: ':9001'
|
||||||
|
RUSTFS_CONSOLE_ENABLE: 'true'
|
||||||
volumes:
|
volumes:
|
||||||
- minio_data:/data
|
- rustfs_data:/data
|
||||||
|
ports:
|
||||||
|
- "${RUSTFS_PUBLISHED_API_PORT:-9010}:9000"
|
||||||
|
- "${RUSTFS_PUBLISHED_CONSOLE_PORT:-9011}:9001"
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
|
test: ["CMD", "curl", "-f", "http://localhost:9000/health"]
|
||||||
interval: 5s
|
interval: 5s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 10
|
retries: 20
|
||||||
start_period: 10s
|
start_period: 15s
|
||||||
minio_init:
|
rustfs_init:
|
||||||
image: minio/minio:RELEASE.2025-09-07T16-13-09Z-cpuv1
|
build:
|
||||||
profiles: ['bootstrap']
|
context: ./docker/storage-init
|
||||||
|
entrypoint: ["python"]
|
||||||
|
command: ["/usr/local/bin/storage-init.py"]
|
||||||
restart: "no"
|
restart: "no"
|
||||||
container_name: '${APP_NAME}-minio-init'
|
container_name: '${APP_NAME}-rustfs-init'
|
||||||
depends_on:
|
depends_on:
|
||||||
minio:
|
rustfs:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
environment:
|
environment:
|
||||||
MINIO_ROOT_USER: ${MINIO_ROOT_USER}
|
TARGET_ENDPOINT: http://rustfs:9000
|
||||||
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD}
|
STORAGE_ACCESS_KEY: ${STORAGE_ACCESS_KEY}
|
||||||
volumes:
|
STORAGE_SECRET_KEY: ${STORAGE_SECRET_KEY}
|
||||||
- ./docker/minio-init.sh:/minio-init.sh:ro
|
STORAGE_BUCKET: mineseeker
|
||||||
entrypoint: ["/bin/sh", "/minio-init.sh"]
|
|
||||||
mail:
|
mail:
|
||||||
image: boky/postfix:latest
|
image: boky/postfix:latest
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
@@ -157,11 +174,11 @@ services:
|
|||||||
retries: 10
|
retries: 10
|
||||||
start_period: 10s
|
start_period: 10s
|
||||||
ports:
|
ports:
|
||||||
- "5452:5432"
|
- "${POSTGRES_PUBLISHED_PORT:-5452}:5432"
|
||||||
volumes:
|
volumes:
|
||||||
app_var:
|
app_var:
|
||||||
postgres_data:
|
postgres_data:
|
||||||
caddy_data:
|
caddy_data:
|
||||||
caddy_config:
|
caddy_config:
|
||||||
postfix_spool:
|
postfix_spool:
|
||||||
minio_data:
|
rustfs_data:
|
||||||
|
|||||||
+4
-4
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "splendid-bear/mineseeker",
|
"name": "splendid-bear/mineseeker",
|
||||||
"version": "2026.3.0",
|
"version": "2026.4.0",
|
||||||
"license": "GPL-3.0-or-later",
|
"license": "GPL-3.0-or-later",
|
||||||
"author": "https://www.splendidbear.org",
|
"author": "https://www.splendidbear.org",
|
||||||
"bugs": "https://source.splendidbear.org",
|
"bugs": "https://source.splendidbear.org",
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
"prefer-stable": true,
|
"prefer-stable": true,
|
||||||
"private": true,
|
"private": true,
|
||||||
"require": {
|
"require": {
|
||||||
"php": ">=8.5",
|
"php": ">=8.5.12",
|
||||||
"ext-gd": "*",
|
"ext-gd": "*",
|
||||||
"ext-iconv": "*",
|
"ext-iconv": "*",
|
||||||
"ext-json": "*",
|
"ext-json": "*",
|
||||||
@@ -33,8 +33,8 @@
|
|||||||
"symfony/framework-bundle": "8.1.*",
|
"symfony/framework-bundle": "8.1.*",
|
||||||
"symfony/http-client": "8.1.*",
|
"symfony/http-client": "8.1.*",
|
||||||
"symfony/mailer": "8.1.*",
|
"symfony/mailer": "8.1.*",
|
||||||
"symfony/mercure": "^0.8",
|
"symfony/mercure": "^0.9",
|
||||||
"symfony/mercure-bundle": "^0.5",
|
"symfony/mercure-bundle": "^0.6",
|
||||||
"symfony/messenger": "8.1.*",
|
"symfony/messenger": "8.1.*",
|
||||||
"symfony/monolog-bundle": "^4.0",
|
"symfony/monolog-bundle": "^4.0",
|
||||||
"symfony/scheduler": "8.1.*",
|
"symfony/scheduler": "8.1.*",
|
||||||
|
|||||||
Generated
+984
-837
File diff suppressed because it is too large
Load Diff
@@ -4,12 +4,12 @@ liip_imagine:
|
|||||||
default:
|
default:
|
||||||
flysystem:
|
flysystem:
|
||||||
filesystem_service: mineseeker.media.storage
|
filesystem_service: mineseeker.media.storage
|
||||||
cache: minio_cache
|
cache: object_storage_cache
|
||||||
resolvers:
|
resolvers:
|
||||||
minio_cache:
|
object_storage_cache:
|
||||||
flysystem:
|
flysystem:
|
||||||
filesystem_service: mineseeker.cache.storage
|
filesystem_service: mineseeker.cache.storage
|
||||||
root_url: '%env(MINIO_PUBLIC_URL)%/mineseeker/cache'
|
root_url: '%env(STORAGE_PUBLIC_URL)%/mineseeker/cache'
|
||||||
cache_prefix: 'liip'
|
cache_prefix: 'liip'
|
||||||
filter_sets:
|
filter_sets:
|
||||||
avatar_thumb:
|
avatar_thumb:
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ mercure:
|
|||||||
default:
|
default:
|
||||||
url: '%env(MERCURE_URL)%'
|
url: '%env(MERCURE_URL)%'
|
||||||
public_url: '%env(MERCURE_PUBLIC_URL)%'
|
public_url: '%env(MERCURE_PUBLIC_URL)%'
|
||||||
|
protocol_version: '0.x'
|
||||||
jwt:
|
jwt:
|
||||||
secret: '%env(MERCURE_JWT_SECRET)%'
|
secret: '%env(MERCURE_JWT_SECRET)%'
|
||||||
publish: '*'
|
publish: '*'
|
||||||
|
|||||||
@@ -29,14 +29,14 @@ services:
|
|||||||
arguments:
|
arguments:
|
||||||
$cacheDir: '%kernel.project_dir%/var/og-cache'
|
$cacheDir: '%kernel.project_dir%/var/og-cache'
|
||||||
$fontPath: '%kernel.project_dir%/assets/fonts/Carlito-Bold.ttf'
|
$fontPath: '%kernel.project_dir%/assets/fonts/Carlito-Bold.ttf'
|
||||||
$minioMediaStorage: '@mineseeker.media.storage'
|
$mediaStorage: '@mineseeker.media.storage'
|
||||||
|
|
||||||
Aws\S3\S3Client:
|
Aws\S3\S3Client:
|
||||||
arguments:
|
arguments:
|
||||||
- version: 'latest'
|
- version: 'latest'
|
||||||
region: 'us-east-1'
|
region: 'us-east-1'
|
||||||
endpoint: '%env(MINIO_ENDPOINT)%'
|
endpoint: '%env(STORAGE_ENDPOINT)%'
|
||||||
use_path_style_endpoint: true
|
use_path_style_endpoint: true
|
||||||
credentials:
|
credentials:
|
||||||
key: '%env(MINIO_ROOT_USER)%'
|
key: '%env(STORAGE_ACCESS_KEY)%'
|
||||||
secret: '%env(MINIO_ROOT_PASSWORD)%'
|
secret: '%env(STORAGE_SECRET_KEY)%'
|
||||||
|
|||||||
@@ -1,40 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
set -e
|
|
||||||
|
|
||||||
echo "[minio-init] Waiting for MinIO..."
|
|
||||||
until mc alias set local http://minio:9000 "$MINIO_ROOT_USER" "$MINIO_ROOT_PASSWORD" > /dev/null 2>&1; do
|
|
||||||
sleep 2
|
|
||||||
done
|
|
||||||
|
|
||||||
echo "[minio-init] Configuring bucket..."
|
|
||||||
|
|
||||||
if mc ls local/mineseeker > /dev/null 2>&1; then
|
|
||||||
echo "[minio-init] Already initialized, skipping."
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "[minio-init] Creating bucket 'mineseeker'..."
|
|
||||||
mc mb local/mineseeker
|
|
||||||
|
|
||||||
echo "[minio-init] Creating directories..."
|
|
||||||
echo '' | mc pipe local/mineseeker/media/.keep
|
|
||||||
echo '' | mc pipe local/mineseeker/cache/.keep
|
|
||||||
|
|
||||||
echo "[minio-init] Applying anonymous read policy for media/ and cache/..."
|
|
||||||
printf '%s' '{
|
|
||||||
"Version": "2012-10-17",
|
|
||||||
"Statement": [
|
|
||||||
{
|
|
||||||
"Effect": "Allow",
|
|
||||||
"Principal": { "AWS": ["*"] },
|
|
||||||
"Action": ["s3:GetObject"],
|
|
||||||
"Resource": [
|
|
||||||
"arn:aws:s3:::mineseeker/media/*",
|
|
||||||
"arn:aws:s3:::mineseeker/cache/*"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}' > /tmp/policy.json
|
|
||||||
mc anonymous set-json /tmp/policy.json local/mineseeker
|
|
||||||
|
|
||||||
echo "[minio-init] Done."
|
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
FROM python:3.13-slim
|
||||||
|
|
||||||
|
RUN pip install --no-cache-dir boto3==1.43.118
|
||||||
|
|
||||||
|
COPY init.py /usr/local/bin/storage-init.py
|
||||||
|
|
||||||
|
ENTRYPOINT ["python", "/usr/local/bin/storage-init.py"]
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
"""Create the application bucket and public object-read policy on RustFS."""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
|
||||||
|
import boto3
|
||||||
|
from botocore.config import Config
|
||||||
|
from botocore.exceptions import ClientError
|
||||||
|
|
||||||
|
|
||||||
|
BUCKET = os.environ.get("STORAGE_BUCKET", "mineseeker")
|
||||||
|
s3 = boto3.client(
|
||||||
|
"s3",
|
||||||
|
endpoint_url=os.environ["TARGET_ENDPOINT"],
|
||||||
|
aws_access_key_id=os.environ["STORAGE_ACCESS_KEY"],
|
||||||
|
aws_secret_access_key=os.environ["STORAGE_SECRET_KEY"],
|
||||||
|
region_name="us-east-1",
|
||||||
|
config=Config(signature_version="s3v4", s3={"addressing_style": "path"}),
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
s3.head_bucket(Bucket=BUCKET)
|
||||||
|
except ClientError as error:
|
||||||
|
status = error.response.get("ResponseMetadata", {}).get("HTTPStatusCode")
|
||||||
|
code = error.response.get("Error", {}).get("Code", "")
|
||||||
|
if status != 404 and code not in {"NoSuchBucket", "404", "NotFound"}:
|
||||||
|
raise
|
||||||
|
s3.create_bucket(Bucket=BUCKET)
|
||||||
|
|
||||||
|
policy = {
|
||||||
|
"Version": "2012-10-17",
|
||||||
|
"Statement": [
|
||||||
|
{
|
||||||
|
"Effect": "Allow",
|
||||||
|
"Principal": "*",
|
||||||
|
"Action": ["s3:GetObject"],
|
||||||
|
"Resource": [
|
||||||
|
f"arn:aws:s3:::{BUCKET}/media/*",
|
||||||
|
f"arn:aws:s3:::{BUCKET}/cache/*",
|
||||||
|
],
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
s3.put_bucket_policy(Bucket=BUCKET, Policy=json.dumps(policy))
|
||||||
|
print(f"RustFS bucket {BUCKET!r} is initialized", flush=True)
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
# MinIO Initialization
|
|
||||||
|
|
||||||
The `minio_init` Compose service creates the `mineseeker` bucket and applies
|
|
||||||
its public read policy. It is an idempotent bootstrap task: after the bucket
|
|
||||||
exists, it exits without changing it.
|
|
||||||
|
|
||||||
It belongs to the `bootstrap` Compose profile, so normal `docker compose up`
|
|
||||||
does not create a stopped `mineseeker-minio-init` container.
|
|
||||||
|
|
||||||
`make start`, `make start-build`, and the production deployment workflow run
|
|
||||||
the bootstrap task automatically with `docker compose run --rm minio_init`.
|
|
||||||
They also remove any stopped legacy initializer container first. The temporary
|
|
||||||
container is removed as soon as the task succeeds.
|
|
||||||
|
|
||||||
To run it manually when needed:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker compose run --rm minio_init
|
|
||||||
```
|
|
||||||
+2
-2
@@ -60,8 +60,8 @@ Quick command reference for testing:
|
|||||||
### [Scheduled Tasks](./SCHEDULER.md)
|
### [Scheduled Tasks](./SCHEDULER.md)
|
||||||
Symfony Scheduler tasks and the Docker worker that runs them.
|
Symfony Scheduler tasks and the Docker worker that runs them.
|
||||||
|
|
||||||
### [MinIO Initialization](./MINIO_INITIALIZATION.md)
|
### Object storage
|
||||||
Automatic, one-shot setup of the MinIO bucket and its access policy.
|
RustFS is initialized automatically by the Docker stack. Configure its S3 credentials and public media URL in `.env`.
|
||||||
|
|
||||||
### [CI/CD Integration Guide](./CI_CD.md)
|
### [CI/CD Integration Guide](./CI_CD.md)
|
||||||
Comprehensive guide for continuous integration and deployment:
|
Comprehensive guide for continuous integration and deployment:
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "mine-seeker",
|
"name": "mine-seeker",
|
||||||
"version": "2026.3.0",
|
"version": "2026.4.0",
|
||||||
"author": "https://www.splendidbear.org",
|
"author": "https://www.splendidbear.org",
|
||||||
"license": "GPL-3.0-or-later",
|
"license": "GPL-3.0-or-later",
|
||||||
"bugs": "https://source.splendidbear.org",
|
"bugs": "https://source.splendidbear.org",
|
||||||
|
|||||||
@@ -178,7 +178,7 @@ class ProfileController extends AbstractController
|
|||||||
$cacheManager->remove($oldPath, 'avatar_thumb');
|
$cacheManager->remove($oldPath, 'avatar_thumb');
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Upload original to MinIO media/avatar/ */
|
/** Upload original avatar to object storage. */
|
||||||
$stream = fopen($file->getPathname(), 'rb');
|
$stream = fopen($file->getPathname(), 'rb');
|
||||||
try {
|
try {
|
||||||
$mediaStorage->writeStream($newPath, $stream);
|
$mediaStorage->writeStream($newPath, $stream);
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ final class BattleCardGenerator
|
|||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly string $cacheDir,
|
private readonly string $cacheDir,
|
||||||
private readonly string $fontPath,
|
private readonly string $fontPath,
|
||||||
private readonly FilesystemOperator $minioMediaStorage,
|
private readonly FilesystemOperator $mediaStorage,
|
||||||
private readonly LoggerInterface $logger,
|
private readonly LoggerInterface $logger,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
@@ -193,12 +193,12 @@ final class BattleCardGenerator
|
|||||||
{
|
{
|
||||||
$avatarImg = null;
|
$avatarImg = null;
|
||||||
|
|
||||||
/** Try to load avatar from MinIO if path exists*/
|
/** Try to load avatar from object storage if a path exists. */
|
||||||
if ($avatarPath) {
|
if ($avatarPath) {
|
||||||
try {
|
try {
|
||||||
/** Remove 'avatar/' prefix if it exists since storage already has media/ prefix*/
|
/** Remove 'avatar/' prefix if it exists since storage already has media/ prefix*/
|
||||||
$path = str_starts_with($avatarPath, 'avatar/') ? $avatarPath : 'avatar/' . $avatarPath;
|
$path = str_starts_with($avatarPath, 'avatar/') ? $avatarPath : 'avatar/' . $avatarPath;
|
||||||
$avatarData = $this->minioMediaStorage->read($path);
|
$avatarData = $this->mediaStorage->read($path);
|
||||||
$avatarImg = imagecreatefromstring($avatarData);
|
$avatarImg = imagecreatefromstring($avatarData);
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
/** Failed to load avatar, will use initials*/
|
/** Failed to load avatar, will use initials*/
|
||||||
|
|||||||
Reference in New Issue
Block a user