Compare commits
7 Commits
1e215ac3d2
...
feat/gitea
| Author | SHA1 | Date | |
|---|---|---|---|
| 26a68e1823 | |||
| ac08b5f064 | |||
| 91c0d412fa | |||
| dd82ca2dca | |||
| 87a019109d | |||
| 1468a23145 | |||
| f730e766f5 |
@@ -18,7 +18,9 @@ env:
|
||||
BCRYPT_ROUNDS: 4
|
||||
CACHE_STORE: database
|
||||
DB_CONNECTION: pgsql
|
||||
DB_HOST: 127.0.0.1
|
||||
# Service hostname on the per-job network (act_runner with empty
|
||||
# container.network). Do not publish host :5432/:8000 — parallel jobs collide.
|
||||
DB_HOST: postgres
|
||||
DB_PORT: 5432
|
||||
DB_DATABASE: amare_test
|
||||
DB_USERNAME: amare
|
||||
@@ -40,12 +42,6 @@ jobs:
|
||||
extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, pdo_pgsql, bcmath, intl, sodium, gd
|
||||
coverage: none
|
||||
|
||||
- uses: actions/cache@v5
|
||||
with:
|
||||
path: ~/.composer/cache/files
|
||||
key: composer-${{ runner.os }}-${{ hashFiles('**/composer.lock') }}
|
||||
restore-keys: composer-${{ runner.os }}-
|
||||
|
||||
- run: composer validate --strict
|
||||
- run: composer install --no-interaction --prefer-dist
|
||||
- run: composer pint:check
|
||||
@@ -74,8 +70,6 @@ jobs:
|
||||
POSTGRES_DB: amare_test
|
||||
POSTGRES_USER: amare
|
||||
POSTGRES_PASSWORD: secret
|
||||
ports:
|
||||
- 5432:5432
|
||||
options: >-
|
||||
--health-cmd "pg_isready -U amare -d amare_test"
|
||||
--health-interval 5s
|
||||
@@ -91,18 +85,6 @@ jobs:
|
||||
extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, pdo_pgsql, bcmath, intl, sodium, gd
|
||||
coverage: pcov
|
||||
|
||||
- uses: actions/cache@v5
|
||||
with:
|
||||
path: ~/.composer/cache/files
|
||||
key: composer-${{ runner.os }}-${{ hashFiles('**/composer.lock') }}
|
||||
restore-keys: composer-${{ runner.os }}-
|
||||
|
||||
- uses: actions/cache@v5
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: npm-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: npm-${{ runner.os }}-
|
||||
|
||||
- run: composer install --no-interaction --prefer-dist
|
||||
- run: npm ci
|
||||
- run: npm run build
|
||||
@@ -126,8 +108,6 @@ jobs:
|
||||
POSTGRES_DB: amare_test
|
||||
POSTGRES_USER: amare
|
||||
POSTGRES_PASSWORD: secret
|
||||
ports:
|
||||
- 5432:5432
|
||||
options: >-
|
||||
--health-cmd "pg_isready -U amare -d amare_test"
|
||||
--health-interval 5s
|
||||
@@ -143,18 +123,6 @@ jobs:
|
||||
extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, pdo_pgsql, bcmath, intl, sodium, gd
|
||||
coverage: none
|
||||
|
||||
- uses: actions/cache@v5
|
||||
with:
|
||||
path: ~/.composer/cache/files
|
||||
key: composer-${{ runner.os }}-${{ hashFiles('**/composer.lock') }}
|
||||
restore-keys: composer-${{ runner.os }}-
|
||||
|
||||
- uses: actions/cache@v5
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: npm-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: npm-${{ runner.os }}-
|
||||
|
||||
- run: composer install --no-interaction --prefer-dist
|
||||
- run: npm ci
|
||||
- run: npm run build
|
||||
@@ -171,8 +139,6 @@ jobs:
|
||||
POSTGRES_DB: amare_test
|
||||
POSTGRES_USER: amare
|
||||
POSTGRES_PASSWORD: secret
|
||||
ports:
|
||||
- 5432:5432
|
||||
options: >-
|
||||
--health-cmd "pg_isready -U amare -d amare_test"
|
||||
--health-interval 5s
|
||||
@@ -188,18 +154,6 @@ jobs:
|
||||
extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, pdo_pgsql, bcmath, intl, sodium, gd
|
||||
coverage: none
|
||||
|
||||
- uses: actions/cache@v5
|
||||
with:
|
||||
path: ~/.composer/cache/files
|
||||
key: composer-${{ runner.os }}-${{ hashFiles('**/composer.lock') }}
|
||||
restore-keys: composer-${{ runner.os }}-
|
||||
|
||||
- uses: actions/cache@v5
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: npm-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: npm-${{ runner.os }}-
|
||||
|
||||
- run: composer install --no-interaction --prefer-dist
|
||||
- run: npm ci
|
||||
- run: npm run build
|
||||
@@ -212,15 +166,28 @@ jobs:
|
||||
|
||||
- name: Run browser tests against FrankenPHP container
|
||||
run: |
|
||||
docker run -d --name amare-web \
|
||||
# Join the per-job network (act_runner creates one when
|
||||
# container.network is empty). No host -p: parallel jobs would
|
||||
# collide on :8000/:5432; DNS names work on the job network.
|
||||
# Container --name is global on the shared docker.sock host —
|
||||
# include run id or leftovers from cancelled jobs Conflict.
|
||||
JOB_CID="$(hostname)"
|
||||
JOB_NET="$(docker inspect -f '{{range $k, $_ := .NetworkSettings.Networks}}{{println $k}}{{end}}' "$JOB_CID" | head -n1)"
|
||||
test -n "$JOB_NET"
|
||||
WEB_NAME="amare-web-${GITHUB_RUN_ID:-$$}"
|
||||
docker rm -f "$WEB_NAME" 2>/dev/null || true
|
||||
|
||||
docker run -d --name "$WEB_NAME" \
|
||||
--network "$JOB_NET" \
|
||||
--network-alias amare-web \
|
||||
-e APP_ENV=testing \
|
||||
-e APP_KEY="${APP_KEY}" \
|
||||
-e APP_URL=http://127.0.0.1:8000 \
|
||||
-e APP_URL=http://amare-web:8000 \
|
||||
-e APP_LOCALE=pt_BR \
|
||||
-e APP_FALLBACK_LOCALE=pt_BR \
|
||||
-e APP_TIMEZONE=America/Sao_Paulo \
|
||||
-e DB_CONNECTION=pgsql \
|
||||
-e DB_HOST=host.docker.internal \
|
||||
-e DB_HOST=postgres \
|
||||
-e DB_PORT=5432 \
|
||||
-e DB_DATABASE=amare_test \
|
||||
-e DB_USERNAME=amare \
|
||||
@@ -228,26 +195,28 @@ jobs:
|
||||
-e SESSION_DRIVER=database \
|
||||
-e CACHE_STORE=database \
|
||||
-e QUEUE_CONNECTION=database \
|
||||
--add-host=host.docker.internal:host-gateway \
|
||||
-v "${GITHUB_WORKSPACE}/storage/app/public:/app/storage/app/public" \
|
||||
-p 8000:8000 \
|
||||
amare-app:ci
|
||||
|
||||
cleanup() { docker rm -f "$WEB_NAME" >/dev/null 2>&1 || true; }
|
||||
trap cleanup EXIT
|
||||
|
||||
for i in $(seq 1 30); do
|
||||
if curl -fsS http://127.0.0.1:8000/up; then
|
||||
if curl -fsS http://amare-web:8000/up; then
|
||||
break
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
|
||||
curl -fsS http://127.0.0.1:8000/up
|
||||
./vendor/bin/pest --testsuite=Browser
|
||||
curl -fsS http://amare-web:8000/up
|
||||
APP_URL=http://amare-web:8000 ./vendor/bin/pest --testsuite=Browser
|
||||
|
||||
- name: Collect failure diagnostics
|
||||
if: failure()
|
||||
run: |
|
||||
mkdir -p artifacts/browser
|
||||
docker logs amare-web > artifacts/browser/container.log 2>&1 || true
|
||||
WEB_NAME="amare-web-${GITHUB_RUN_ID:-$$}"
|
||||
docker logs "$WEB_NAME" > artifacts/browser/container.log 2>&1 || true
|
||||
cp -R storage/logs artifacts/browser/app-logs 2>/dev/null || true
|
||||
|
||||
- name: Upload browser failure artifacts
|
||||
@@ -269,10 +238,21 @@ jobs:
|
||||
|
||||
- name: Verify container healthcheck and storage link
|
||||
run: |
|
||||
docker run -d --name amare-health \
|
||||
# Same per-job network as the step container — no host :8000
|
||||
# publish (collides when capacity > 1). Unique --name: docker.sock
|
||||
# is shared across jobs; leftovers from cancelled runs Conflict.
|
||||
JOB_CID="$(hostname)"
|
||||
JOB_NET="$(docker inspect -f '{{range $k, $_ := .NetworkSettings.Networks}}{{println $k}}{{end}}' "$JOB_CID" | head -n1)"
|
||||
test -n "$JOB_NET"
|
||||
HEALTH_NAME="amare-health-${GITHUB_RUN_ID:-$$}"
|
||||
docker rm -f "$HEALTH_NAME" 2>/dev/null || true
|
||||
|
||||
docker run -d --name "$HEALTH_NAME" \
|
||||
--network "$JOB_NET" \
|
||||
--network-alias amare-health \
|
||||
-e APP_ENV=production \
|
||||
-e APP_KEY="${{ env.APP_KEY }}" \
|
||||
-e APP_URL=http://127.0.0.1:8000 \
|
||||
-e APP_URL=http://amare-health:8000 \
|
||||
-e APP_DEBUG=false \
|
||||
-e DB_CONNECTION=pgsql \
|
||||
-e DB_HOST=127.0.0.1 \
|
||||
@@ -280,16 +260,18 @@ jobs:
|
||||
-e DB_DATABASE=amare \
|
||||
-e DB_USERNAME=amare \
|
||||
-e DB_PASSWORD=secret \
|
||||
-p 8000:8000 \
|
||||
amare-app:ci
|
||||
|
||||
cleanup() { docker rm -f "$HEALTH_NAME" >/dev/null 2>&1 || true; }
|
||||
trap cleanup EXIT
|
||||
|
||||
for i in $(seq 1 30); do
|
||||
if curl -fsS http://127.0.0.1:8000/up; then
|
||||
docker exec amare-health test -L /app/public/storage
|
||||
if curl -fsS http://amare-health:8000/up; then
|
||||
docker exec "$HEALTH_NAME" test -L /app/public/storage
|
||||
exit 0
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
|
||||
docker logs amare-health
|
||||
docker logs "$HEALTH_NAME"
|
||||
exit 1
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
name: Deploy staging
|
||||
|
||||
# Requires Gitea >= 1.25 for workflow_run (1.24.x has no trigger match).
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: [CI]
|
||||
types: [completed]
|
||||
branches: [main]
|
||||
workflows:
|
||||
- CI
|
||||
- ci.yml
|
||||
types:
|
||||
- completed
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -22,20 +28,21 @@ jobs:
|
||||
deploy:
|
||||
name: publish-and-deploy-staging
|
||||
if: >-
|
||||
gitea.event.workflow_run.conclusion == 'success' &&
|
||||
gitea.event.workflow_run.event == 'push' &&
|
||||
gitea.event.workflow_run.head_branch == 'main'
|
||||
gitea.event_name == 'workflow_dispatch' ||
|
||||
(gitea.event.workflow_run.conclusion == 'success' &&
|
||||
gitea.event.workflow_run.event == 'push' &&
|
||||
gitea.event.workflow_run.head_branch == 'main')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout deployed SHA
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ gitea.event.workflow_run.head_sha }}
|
||||
ref: ${{ gitea.event.workflow_run.head_sha || gitea.sha }}
|
||||
|
||||
- name: Set image metadata
|
||||
id: meta
|
||||
run: |
|
||||
SHA="${{ gitea.event.workflow_run.head_sha }}"
|
||||
SHA="${{ gitea.event.workflow_run.head_sha || gitea.sha }}"
|
||||
SHORT_SHA="${SHA:0:7}"
|
||||
IMAGE="${REGISTRY}/${IMAGE_NAME}"
|
||||
IMAGE="$(echo "$IMAGE" | tr '[:upper:]' '[:lower:]')"
|
||||
@@ -49,8 +56,8 @@ jobs:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
# Gitea's GITEA_TOKEN cannot push OCI packages (gitea#23642); a PAT
|
||||
# with read:package/write:package scopes is required instead.
|
||||
username: ${{ secrets.GITEA_REGISTRY_USER }}
|
||||
password: ${{ secrets.GITEA_PAT }}
|
||||
username: ${{ secrets.REGISTRY_USER }}
|
||||
password: ${{ secrets.REGISTRY_PAT }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
@@ -63,8 +70,6 @@ jobs:
|
||||
tags: |
|
||||
${{ steps.meta.outputs.image }}:${{ steps.meta.outputs.sha }}
|
||||
${{ steps.meta.outputs.image }}:staging
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
- name: Deploy staging on Dokploy
|
||||
env:
|
||||
|
||||
@@ -56,8 +56,8 @@ jobs:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
# Gitea's GITEA_TOKEN cannot push OCI packages (gitea#23642); a PAT
|
||||
# with read:package/write:package scopes is required instead.
|
||||
username: ${{ secrets.GITEA_REGISTRY_USER }}
|
||||
password: ${{ secrets.GITEA_PAT }}
|
||||
username: ${{ secrets.REGISTRY_USER }}
|
||||
password: ${{ secrets.REGISTRY_PAT }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
@@ -33,7 +33,7 @@ Promote (manual) → retag same digest as :production (no rebuild)
|
||||
5. Two R2 buckets (or prefixes) and Resend credentials for each environment as needed.
|
||||
6. Domains (or temporary Dokploy/traefik.me hosts) pointing at the VPS with TLS.
|
||||
|
||||
Note: Gitea's `GITEA_TOKEN` cannot push OCI packages ([gitea#23642](https://github.com/go-gitea/gitea/issues/23642)); registry authentication in the workflows uses a PAT (`GITEA_PAT`), not the token.
|
||||
Note: Gitea's `GITEA_TOKEN` cannot push OCI packages ([gitea#23642](https://github.com/go-gitea/gitea/issues/23642)); registry authentication in the workflows uses a PAT (`REGISTRY_PAT`), not the token. Secret names must not use the reserved `GITEA_` prefix (Gitea rejects them as invalid).
|
||||
|
||||
## Create Compose stacks
|
||||
|
||||
@@ -135,26 +135,38 @@ Repository secrets (Gitea → Settings → Actions → Secrets) used by workflow
|
||||
| `DOKPLOY_PRODUCTION_COMPOSE_ID` | Production **Compose** service id (not an Application id) |
|
||||
| `STAGING_URL` | Public origin for staging smoke (e.g. `https://staging.example.com`) |
|
||||
| `PRODUCTION_URL` | Public origin for production smoke |
|
||||
| `GITEA_PAT` | Personal Access Token with `read:package` + `write:package` scopes — used to push images to `git.hellomanoel.com` |
|
||||
| `GITEA_REGISTRY_USER` | Gitea username that owns `GITEA_PAT` (`manoel-freitas`) |
|
||||
| `REGISTRY_PAT` | Personal Access Token with `read:package` + `write:package` scopes — used to push images to `git.hellomanoel.com` (do not name secrets `GITEA_*`; that prefix is reserved) |
|
||||
| `REGISTRY_USER` | Gitea username that owns `REGISTRY_PAT` (`manoel-freitas`) |
|
||||
|
||||
HTTP 404 from `compose.deploy` usually means the compose id is wrong (Application id instead of Compose) or `DOKPLOY_URL` still includes `/api`.
|
||||
|
||||
The automatic `GITEA_TOKEN` runs workflows but **cannot push OCI packages** ([gitea#23642](https://github.com/go-gitea/gitea/issues/23642)); registry authentication therefore uses `GITEA_PAT` + `GITEA_REGISTRY_USER`. No Laravel/`APP_KEY`/DB/R2/Resend secrets belong in Gitea for this pipeline.
|
||||
The automatic `GITEA_TOKEN` runs workflows but **cannot push OCI packages** ([gitea#23642](https://github.com/go-gitea/gitea/issues/23642)); registry authentication therefore uses `REGISTRY_PAT` + `REGISTRY_USER`. No Laravel/`APP_KEY`/DB/R2/Resend secrets belong in Gitea for this pipeline.
|
||||
|
||||
## Workflows
|
||||
|
||||
Workflows live in [`.gitea/workflows/`](../../.gitea/workflows/).
|
||||
|
||||
`actions/cache` and Docker Buildx `type=gha` are **not** used: the act_runner cache server is not reachable from job containers by default (`getCacheEntry` ETIMEDOUT). Re-enable only after configuring a reachable `cache.host`/`external_server` on the runner.
|
||||
|
||||
CI Postgres services must **not** publish host port `5432` (use service hostname `postgres` on the job network). Publishing `5432:5432` on a shared VPS runner fails with `Bind for 0.0.0.0:5432 failed: port is already allocated` when another job/orphan still holds the port.
|
||||
|
||||
Parallel CI jobs need the act_runner `config.yaml` to keep `container.network` **empty** (per-job Docker network + service DNS) and `runner.capacity` ≥ 2. Setting `network: bridge` puts every job on the default bridge and makes parallel Postgres collide. Nested app containers (browser/container jobs) must join that job network by name and must **not** publish host `:8000`. On the current 1 vCPU / ~4 GiB VPS, `capacity: 2` is the safe ceiling.
|
||||
|
||||
### Staging (automatic)
|
||||
|
||||
[`.gitea/workflows/deploy-staging.yml`](../../.gitea/workflows/deploy-staging.yml)
|
||||
[`.gitea/workflows/deploy-staging.yml`](../../.gitea/workflows/deploy-staging.yml) — separate workflow, triggered by `workflow_run` when **CI** completes on `main`.
|
||||
|
||||
Requires **Gitea ≥ 1.25** (`workflow_run` is not implemented as an Actions trigger in 1.24.x). Match both workflow display name `CI` and file id `ci.yml`.
|
||||
|
||||
1. Waits for workflow `CI` success (`workflow_run`) on push to `main`.
|
||||
2. Builds once; pushes `:<full-sha>` and `:staging` to the Gitea registry.
|
||||
3. Calls Dokploy `compose.deploy` and polls until done.
|
||||
4. Runs [`scripts/deploy/smoke.sh`](../../scripts/deploy/smoke.sh) against `STAGING_URL`.
|
||||
|
||||
Manual re-deploy: **Actions → Deploy staging → Run workflow** (`workflow_dispatch`).
|
||||
|
||||
`DOKPLOY_API_KEY` must be the **plaintext** key from Dokploy → Profile → API (starts like `amare…`). Do not paste the hashed `apikey.key` column from Postgres — that yields HTTP 401.
|
||||
|
||||
### Production (manual)
|
||||
|
||||
[`.gitea/workflows/promote-production.yml`](../../.gitea/workflows/promote-production.yml)
|
||||
|
||||
Reference in New Issue
Block a user