diff --git a/.env.dist b/.env.dist index f8fa4e9..8cd5dd3 100644 --- a/.env.dist +++ b/.env.dist @@ -10,10 +10,9 @@ APP_SECRET=changethis ###< symfony/framework-bundle ### ###> doctrine/doctrine-bundle ### -# Format described at http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url -# For an SQLite database, use: "sqlite:///%kernel.project_dir%/var/data.db" -# Configure your db driver and server_version in config/packages/doctrine.yaml -DATABASE_URL=mysql://db_user:db_password@127.0.0.1:3306/db_name +# Docker PostgreSQL is exposed on port 15432 — use that for bare-metal dev. +# Replace POSTGRES_USER / POSTGRES_PASSWORD / POSTGRES_DB with the values from your .env. +DATABASE_URL=postgresql://POSTGRES_USER:POSTGRES_PASSWORD@127.0.0.1:15432/POSTGRES_DB?serverVersion=18&charset=utf8 ###< doctrine/doctrine-bundle ### ###> google/recaptcha ### diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml new file mode 100644 index 0000000..a8223fd --- /dev/null +++ b/.gitea/workflows/deploy.yml @@ -0,0 +1,19 @@ +name: Deploy to Production + +on: + push: + tags: + - 'v*' + +jobs: + deploy: + runs-on: self-hosted + steps: + - name: Deploy + run: | + set -e + cd "${{ vars.PROD_APP_DIR }}" + git fetch --tags + git checkout "${{ gitea.ref_name }}" + printf '%s' '${{ secrets.PROD_ENV_FILE }}' > .env + docker compose up -d --build diff --git a/Dockerfile b/Dockerfile index 0d11014..3540f0f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,7 +9,7 @@ COPY vite.config.js ./ COPY assets/ assets/ COPY public/ public/ -RUN bun run build && cp -r node_modules/@fortawesome/fontawesome-free/webfonts public/build/webfonts +RUN bun run build FROM dunglas/frankenphp:latest diff --git a/README.md b/README.md index 08c554b..558035a 100644 --- a/README.md +++ b/README.md @@ -154,6 +154,7 @@ services: mail: image: mailhog/mailhog:latest ports: + - "1025:1025" - "8025:8025" ``` @@ -174,6 +175,111 @@ Open the web UI at **http://localhost:8025** to inspect them. --- +## 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_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://minio.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 +``` + +### 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. + +#### 5. 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`. + +#### 6. 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 +``` + +--- + ## License LGPL-3.0 — see [LICENSE](LICENSE) for details. diff --git a/compose.yaml b/compose.yaml index d295663..2606b5b 100644 --- a/compose.yaml +++ b/compose.yaml @@ -4,6 +4,7 @@ services: context: . dockerfile: Dockerfile restart: unless-stopped + container_name: '${APP_NAME}-app' ports: - "10080:80" environment: @@ -42,6 +43,7 @@ services: minio: image: minio/minio:RELEASE.2025-09-07T16-13-09Z-cpuv1 restart: unless-stopped + container_name: '${APP_NAME}-minio' command: server /data --console-address ":9001" ports: - "9000:9000" @@ -60,6 +62,7 @@ services: minio_init: image: minio/minio:RELEASE.2025-09-07T16-13-09Z-cpuv1 restart: "no" + container_name: '${APP_NAME}-minio-init' depends_on: minio: condition: service_healthy @@ -72,6 +75,7 @@ services: mail: image: boky/postfix:latest restart: unless-stopped + container_name: '${APP_NAME}-mail' environment: ALLOWED_SENDER_DOMAINS: ${MAIL_DOMAIN:-localhost} RELAYHOST: ${MAIL_RELAYHOST:-} @@ -82,6 +86,7 @@ services: db: image: postgres:${POSTGRES_VERSION:-latest}-alpine restart: unless-stopped + container_name: '${APP_NAME}-db' environment: POSTGRES_USER: ${POSTGRES_USER} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} diff --git a/package.json b/package.json index bd70672..642d1a6 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "scripts": { "dev": "vite", "watch": "vite build --watch", - "build": "vite build", + "build": "vite build && cp -r node_modules/@fortawesome/fontawesome-free/webfonts public/build/webfonts", "lint": "eslint assets/js/" } }