Compare commits

...

3 Commits

Author SHA1 Message Date
1e215ac3d2 docs: alinhar remotes e registry para Gitea
Some checks failed
CI / unit (push) Has been cancelled
CI / feature (push) Has been cancelled
CI / browser (push) Has been cancelled
CI / container (push) Has been cancelled
CI / static (push) Has been cancelled
Origin e deploy passam a documentar git.hellomanoel.com;
GitHub/GHCR ficam como legado/backup.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-12 14:35:20 -03:00
716196dbe0 fix: owner path manoel-freitas/amare no registry Gitea
Conta renomeada admin→manoel-freitas; paths de imagem e docs
alinham com repo real no git.hellomanoel.com.
2026-08-12 14:21:24 -03:00
84f6d7c31a ci: migrate GitHub Actions para Gitea Actions
- Move .github/workflows/ para .gitea/workflows/ (desativa CI no GitHub)
- gitea.* contexts no lugar de github.*; GITHUB_TOKEN -> GITEA_PAT/GITEA_REGISTRY_USER
  (GITEA_TOKEN nao publica pacotes OCI, gitea#23642)
- Registry: ghcr.io -> git.hellomanoel.com (container registry da instancia)
- docs/deployment/dokploy.md: prereqs Gitea (Actions, runner, secrets, Dokploy registry)
2026-08-12 13:54:27 -03:00
10 changed files with 70 additions and 58 deletions

View File

@@ -6,7 +6,7 @@ on:
pull_request:
concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
group: ci-${{ gitea.workflow }}-${{ gitea.ref }}
cancel-in-progress: true
env:

View File

@@ -15,27 +15,27 @@ concurrency:
cancel-in-progress: false
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
REGISTRY: git.hellomanoel.com
IMAGE_NAME: ${{ gitea.repository }}
jobs:
deploy:
name: publish-and-deploy-staging
if: >-
github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.event == 'push' &&
github.event.workflow_run.head_branch == 'main'
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: ${{ github.event.workflow_run.head_sha }}
ref: ${{ gitea.event.workflow_run.head_sha }}
- name: Set image metadata
id: meta
run: |
SHA="${{ github.event.workflow_run.head_sha }}"
SHA="${{ gitea.event.workflow_run.head_sha }}"
SHORT_SHA="${SHA:0:7}"
IMAGE="${REGISTRY}/${IMAGE_NAME}"
IMAGE="$(echo "$IMAGE" | tr '[:upper:]' '[:lower:]')"
@@ -43,12 +43,14 @@ jobs:
echo "short_sha=${SHORT_SHA}" >> "$GITHUB_OUTPUT"
echo "image=${IMAGE}" >> "$GITHUB_OUTPUT"
- name: Log in to GHCR
- name: Log in to Gitea registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# 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 }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

View File

@@ -4,7 +4,7 @@ on:
workflow_dispatch:
inputs:
sha:
description: Full git SHA already published to GHCR (same digest used by staging)
description: Full git SHA already published to the Gitea registry (same digest used by staging)
required: true
type: string
confirm:
@@ -21,8 +21,8 @@ concurrency:
cancel-in-progress: false
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
REGISTRY: git.hellomanoel.com
IMAGE_NAME: ${{ gitea.repository }}
jobs:
promote:
@@ -50,12 +50,14 @@ jobs:
echo "short_sha=${SHORT_SHA}" >> "$GITHUB_OUTPUT"
echo "image=${IMAGE}" >> "$GITHUB_OUTPUT"
- name: Log in to GHCR
- name: Log in to Gitea registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# 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 }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

View File

@@ -22,13 +22,14 @@ Write for the problem at hand, not an imagined future. **DRY**: extract and reus
PHP and Composer are **not on PATH** in this environment, and `vendor/` and `node_modules/` are absent. Every `composer …` / `php artisan …` command in `AGENTS.md` and `README.md` assumes a PHP 8.4+ runtime with Composer 2 installed. Verify the toolchain before promising a command ran.
## Git remote auth — two GitHub accounts
## Git remotes — Gitea origin, GitHub legacy
`origin` is `git@github.com:manoel-freitas/amore-site.git`, owned by the **`manoel-freitas`** account. The machine's default SSH identity is a different account (`manoel-freitas-neto`) that cannot see this repo, so pushes fail with `ERROR: Repository not found.` — an access error that reads like a missing repo.
`origin` is `git@git.hellomanoel.com:manoel-freitas/amare.git` (self-hosted Gitea). CI/CD and the container registry live there (`git.hellomanoel.com`). Verify SSH with `ssh -T git@git.hellomanoel.com` → should greet `Hi there, manoel-freitas!`.
- Correct key: `~/.ssh/id_github_pessoal`. Verify with `ssh -i ~/.ssh/id_github_pessoal -o IdentitiesOnly=yes -T git@github.com` → should greet `Hi manoel-freitas!`.
- The repo has `core.sshCommand = ssh -i ~/.ssh/id_github_pessoal -o IdentitiesOnly=yes` set locally, so plain `git push` works. If that config is lost, restore it instead of editing the remote URL.
- **`gh` authenticates separately**, by token rather than SSH key. As of 2026-08-10 it is logged in as `manoel-freitas`, so `gh pr create` / `gh repo view` work. Confirm with `gh auth status` before assuming: if it reports `manoel-freitas-neto`, that account cannot see this repo and every `gh` call fails on it. Recovering needs an interactive `gh auth login` (or `gh auth switch` with both accounts added), so ask the user to run it.
The remote named `github` is the legacy mirror `git@github.com:manoel-freitas/amore-site.git`. Push there only when intentionally syncing the backup. That GitHub account still needs `~/.ssh/id_github_pessoal` (or an equivalent key) when the machine's default identity is a different GitHub user (`manoel-freitas-neto`) that cannot see the repo.
- Prefer plain `git push` / `git push origin <branch>` against Gitea.
- **`gh`** talks to GitHub only. Use the Gitea web UI or API for PRs on `amare`. If you still need `gh` against the legacy remote, confirm `gh auth status` shows `manoel-freitas`.
## Request spine for the public site

View File

@@ -138,10 +138,10 @@ Após `php artisan db:seed`:
- [docs/adr/](docs/adr/) — ADRs aceitas
- [docs/conventions/php-strict-types.md](docs/conventions/php-strict-types.md) — convenção de strict types
- [docs/operations/atualizacao-de-conteudo.md](docs/operations/atualizacao-de-conteudo.md) — runbook de atualização de conteúdo do site pelo painel admin
- [docs/deployment/dokploy.md](docs/deployment/dokploy.md) — deploy staging/produção no Dokploy + GHCR
- [docs/deployment/dokploy.md](docs/deployment/dokploy.md) — deploy staging/produção no Dokploy + registry Gitea
## Deploy (Dokploy)
Staging publica automaticamente após CI verde em `main` (imagem GHCR por SHA + alias `:staging`). Produção promove a **mesma digest** com workflow manual `Promote production` (sem rebuild).
Staging publica automaticamente após CI verde em `main` (imagem no registry Gitea por SHA + alias `:staging`). Produção promove a **mesma digest** com workflow manual `Promote production` (sem rebuild).
Ver runbook completo: [docs/deployment/dokploy.md](docs/deployment/dokploy.md).

View File

@@ -2073,7 +2073,7 @@ quality → Pint check + PHPStan/Larastan + audits + testes
### 14.3 Branches e ambientes
- PR: testes e preview opcional;
- `main`: build imutável por SHA publicado no GHCR e deploy automático em staging via Dokploy;
- `main`: build imutável por SHA publicado no registry Gitea (`git.hellomanoel.com`) e deploy automático em staging via Dokploy;
- staging: Dokploy Compose executa migração, healthcheck `/up` e smoke pós-deploy (`/up`, `/`, `/admin/login`);
- produção: promoção da mesma imagem aprovada, sem rebuild (retag do digest em `:production`);
- produção requer aprovação humana explícita no MVP (`workflow_dispatch` com confirmação);
@@ -2487,7 +2487,7 @@ Toda operação financeira deve:
| ADR-011 | Cloudflare R2 (S3-compatible) como storage de objetos em produção | Aceita |
| ADR-012 | E-mail transacional via Resend (mailer nativo Laravel) | Aceita |
| ADR-013 | Design system Heritage Editorial para o site público | Aceita |
| ADR-014 | Deploy via Dokploy Compose com imagem imutável por SHA no GHCR | Aceita |
| ADR-014 | Deploy via Dokploy Compose com imagem imutável por SHA no registry Gitea | Aceita |
| ADR-015 | Site público permanece Blade + JS vanilla; Livewire e Alpine ficam restritos ao Filament até o gatilho de §22. Emenda o texto da ADR-002 | Aceita |
| ADR-016 | Lançamento de 31/08/2026 entrega apenas o site institucional (Fases 01); Fases 25 seguem especificadas e adiadas, sem data | Aceita |

View File

@@ -1,6 +1,6 @@
# Shared Compose for Dokploy staging and production.
# Both stacks use the same file with different env:
# APP_IMAGE=ghcr.io/<owner>/<repo>
# APP_IMAGE=git.hellomanoel.com/manoel-freitas/amare
# IMAGE_TAG=staging|production|<git-sha>
# PostgreSQL is a separate Dokploy database service (not defined here).
# Traefik/Dokploy domains should target service `web` port 8000.

View File

@@ -1,11 +1,11 @@
# Deploy Dokploy (staging → production)
Runbook for operating Amare on a VPS with Dokploy connected to GitHub, publishing immutable images to GHCR.
Runbook for operating Amare on a VPS with Dokploy connected to Gitea (git.hellomanoel.com), publishing immutable images to Gitea's container registry.
## Architecture
```
CI (main) → build FrankenPHP image → GHCR :<sha> + :staging
CI (main) → build FrankenPHP image → git.hellomanoel.com registry :<sha> + :staging
→ Dokploy staging compose.deploy
→ smoke /up / /admin/login
@@ -18,7 +18,7 @@ Promote (manual) → retag same digest as :production (no rebuild)
|---|---|
| Compose file | [`docker-compose.deploy.yml`](../../docker-compose.deploy.yml) |
| Processes | `migrate` (one-shot) → `web` / `queue` / `scheduler` |
| Image | `ghcr.io/<owner>/<repo>:<sha>` (+ aliases `:staging`, `:production`) |
| Image | `git.hellomanoel.com/manoel-freitas/amare:<sha>` (+ aliases `:staging`, `:production`) |
| Database | Dokploy PostgreSQL **per environment** (not in the app image) |
| Media | Cloudflare R2 (`FILESYSTEM_DISK=r2`), separate buckets per environment |
| Mail | Resend (`MAIL_MAILER=resend`) |
@@ -26,11 +26,14 @@ Promote (manual) → retag same digest as :production (no rebuild)
## Prerequisites (manual)
1. Dokploy installed on the VPS; GitHub provider connected.
2. GHCR registry in Dokploy (`ghcr.io`) with a PAT that can **read** packages (`read:packages`). Prefer a dedicated bot/token; do not store write tokens on the VPS.
3. Two PostgreSQL services in Dokploy (staging + production), private (no public port).
4. Two R2 buckets (or prefixes) and Resend credentials for each environment as needed.
5. Domains (or temporary Dokploy/traefik.me hosts) pointing at the VPS with TLS.
1. Gitea repository `manoel-freitas/amare` at `https://git.hellomanoel.com`; **Repository Actions enabled** in repo settings; a registered **Gitea Actions runner** (see [docs.gitea.com usage/actions/quickstart](https://docs.gitea.com/usage/actions/quickstart)) with an `ubuntu-latest` label.
2. Dokploy installed on the VPS.
3. Container registry in Dokploy (`git.hellomanoel.com`) with a PAT that can **read** packages (`read:package`). Prefer a dedicated bot/token; do not store write tokens on the VPS. The **write** PAT (`write:package`) lives only in Gitea repo secrets for the pipeline.
4. Two PostgreSQL services in Dokploy (staging + production), private (no public port).
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.
## Create Compose stacks
@@ -44,7 +47,7 @@ Create **two** Dokploy Compose services (same repo, same compose path):
Dokploy Environment for each stack must set:
```bash
APP_IMAGE=ghcr.io/<owner>/<repo>
APP_IMAGE=git.hellomanoel.com/manoel-freitas/amare
IMAGE_TAG=staging # or production
```
@@ -52,11 +55,11 @@ Point Dokploy domain(s) at service **`web`**, port **`8000`**. Do not publish Po
Compose services must join the external Docker network `dokploy-network` (declared in `docker-compose.deploy.yml`) so they can resolve the Dokploy-managed Postgres internal host (e.g. `amare-stg-pez43e`). Set `DB_HOST` to that **Internal Host** from the Dokploy database UI — not a public hostname.
Source can be GitHub (so Dokploy clones the compose file) or Raw paste of `docker-compose.deploy.yml`. Prefer GitHub + fixed compose path so updates stay in sync with `main`.
Source can be Gitea (so Dokploy clones the compose file) or Raw paste of `docker-compose.deploy.yml`. Prefer Gitea + fixed compose path so updates stay in sync with `main`.
## Required Laravel env (Dokploy only)
Set these in Dokploy Environment UI (written to `.env` next to the compose file). **Never** put them in GitHub Actions secrets or image layers.
Set these in Dokploy Environment UI (written to `.env` next to the compose file). **Never** put them in Gitea Actions secrets or image layers.
```env
APP_NAME=Amare
@@ -120,9 +123,9 @@ Upload path does not need R2 CORS with the local temp-disk default. Still useful
Also enable public access / custom domain for `R2_URL` so `<img>` URLs work after save.
## GitHub Actions secrets
## Gitea Actions secrets
Repository secrets used by workflows:
Repository secrets (Gitea → Settings → Actions → Secrets) used by workflows:
| Secret | Purpose |
|---|---|
@@ -132,32 +135,36 @@ Repository secrets used by workflows:
| `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`) |
HTTP 404 from `compose.deploy` usually means the compose id is wrong (Application id instead of Compose) or `DOKPLOY_URL` still includes `/api`.
`GITHUB_TOKEN` (automatic) publishes to GHCR with `packages:write`. No Laravel/`APP_KEY`/DB/R2/Resend secrets belong in GitHub 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 `GITEA_PAT` + `GITEA_REGISTRY_USER`. No Laravel/`APP_KEY`/DB/R2/Resend secrets belong in Gitea for this pipeline.
## Workflows
Workflows live in [`.gitea/workflows/`](../../.gitea/workflows/).
### Staging (automatic)
[`.github/workflows/deploy-staging.yml`](../../.github/workflows/deploy-staging.yml)
[`.gitea/workflows/deploy-staging.yml`](../../.gitea/workflows/deploy-staging.yml)
1. Waits for workflow `CI` success on push to `main`.
2. Builds once; pushes `:<full-sha>` and `:staging`.
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`.
### Production (manual)
[`.github/workflows/promote-production.yml`](../../.github/workflows/promote-production.yml)
[`.gitea/workflows/promote-production.yml`](../../.gitea/workflows/promote-production.yml)
1. Operator runs **Actions → Promote production**.
2. Inputs: full `sha` already on GHCR; `confirm` must be exactly `PRODUCTION`.
1. Operator runs **Gitea → Actions → Promote production**.
2. Inputs: full `sha` already in the Gitea registry; `confirm` must be exactly `PRODUCTION`.
3. Retags the **same digest** as `:production` (no rebuild).
4. Deploys production compose + smoke.
Private repos on GitHub Free do not get Environment required reviewers; human approval is the explicit `workflow_dispatch` + confirmation string. GitHub Pro Environment reviewers are optional later.
Human approval is the explicit `workflow_dispatch` + confirmation string (Gitea does not support GitHub Environment required reviewers).
## First admin and authorized production seeding
@@ -256,10 +263,10 @@ No rebuild. Move the environment alias to a previous SHA digest and redeploy.
### Staging
```bash
# Locally or in a one-off Actions shell with GHCR login
# Locally or in a one-off Actions shell with Gitea registry login
docker buildx imagetools create \
--tag ghcr.io/<owner>/<repo>:staging \
ghcr.io/<owner>/<repo>:<previous-sha>
--tag git.hellomanoel.com/manoel-freitas/amare:staging \
git.hellomanoel.com/manoel-freitas/amare:<previous-sha>
# Then trigger Dokploy deploy (UI Deploy, or):
DOKPLOY_URL=... DOKPLOY_API_KEY=... DOKPLOY_COMPOSE_ID=... \
@@ -297,7 +304,7 @@ Expects HTTP 200 for `/up`, `/`, and `/admin/login`.
## Local validation of Compose
```bash
APP_IMAGE=ghcr.io/<owner>/<repo> IMAGE_TAG=staging \
APP_IMAGE=git.hellomanoel.com/manoel-freitas/amare IMAGE_TAG=staging \
docker compose -f docker-compose.deploy.yml config
```

View File

@@ -2,18 +2,18 @@
## Purpose
Define immutable staging and production promotion through GHCR and Dokploy Compose, including migration, health, smoke, rollback, and backup evidence.
Define immutable staging and production promotion through the Gitea container registry (`git.hellomanoel.com`) and Dokploy Compose, including migration, health, smoke, rollback, and backup evidence.
## Requirements
### Requirement: Staging deploys an immutable application image by commit SHA
The system SHALL deploy staging from a single FrankenPHP application image tagged with the Git commit SHA and published to GHCR (SPEC §14.3, §15.2). Web, queue worker, and scheduler processes MUST use that same image digest. Rebuilds per process on the staging host MUST NOT be the promotion path.
The system SHALL deploy staging from a single FrankenPHP application image tagged with the Git commit SHA and published to the Gitea container registry (SPEC §14.3, §15.2). Web, queue worker, and scheduler processes MUST use that same image digest. Rebuilds per process on the staging host MUST NOT be the promotion path.
#### Scenario: Same image serves all application processes
- **WHEN** a staging deployment is promoted for commit SHA `abc123`
- **THEN** web, queue, and scheduler MUST run from `ghcr.io/<owner>/<repo>:abc123` (or equivalent digest)
- **THEN** web, queue, and scheduler MUST run from `git.hellomanoel.com/<owner>/<repo>:abc123` (or equivalent digest)
- **AND** MUST NOT rebuild distinct images per process
#### Scenario: CI publishes the image before Dokploy deploy
@@ -69,7 +69,7 @@ Production SHALL be promoted from an already-published SHA-tagged image without
#### Scenario: Operator promotes a staging-approved SHA to production
- **WHEN** the operator confirms promotion of commit SHA `abc123`
- **THEN** production web, queue, and scheduler MUST run the same digest previously published as `ghcr.io/<owner>/<repo>:abc123`
- **THEN** production web, queue, and scheduler MUST run the same digest previously published as `git.hellomanoel.com/<owner>/<repo>:abc123`
- **AND** MUST NOT rebuild from source for that promotion
### Requirement: Database backups exist before production cutover

View File

@@ -5,7 +5,7 @@
# DOKPLOY_API_KEY x-api-key value
# DOKPLOY_COMPOSE_ID target compose id
# Optional env:
# DEPLOY_TITLE deployment title (default: GitHub deploy)
# DEPLOY_TITLE deployment title (default: Gitea deploy)
# DEPLOY_TIMEOUT_SEC total wait seconds (default: 900)
# DEPLOY_POLL_SEC poll interval (default: 10)
@@ -20,7 +20,7 @@ DOKPLOY_URL="${DOKPLOY_URL%/}"
DOKPLOY_URL="${DOKPLOY_URL%/api}"
DOKPLOY_URL="${DOKPLOY_URL%/}"
DEPLOY_TITLE="${DEPLOY_TITLE:-GitHub deploy}"
DEPLOY_TITLE="${DEPLOY_TITLE:-Gitea deploy}"
DEPLOY_TIMEOUT_SEC="${DEPLOY_TIMEOUT_SEC:-900}"
DEPLOY_POLL_SEC="${DEPLOY_POLL_SEC:-10}"