Protege o CI em repositório público e prepara a ativação do runner
O push para o Gitea criou o run, mas ele ficou em Waiting: a mensagem da instância é "No matching online runner with label: ubuntu-latest". A falha é na atribuição do job, não no enfileiramento — o gatilho funcionou e a branch está contemplada. O endpoint /actions/tasks devolvia lista vazia porque lista tasks aceitas por runner, não runs criados, e foi isso que confundiu a leitura. Com o diagnóstico fechado, duas coisas importam antes de um runner existir. Primeira: este repositório é público e o CI dispara em pull_request. Sem guarda, um fork traria código de terceiro para rodar na máquina do runner, com Docker à disposição. Os três jobs do ci.yml e o runner-check passam a exigir que o PR venha da própria origem; push continua rodando normalmente. Também entram `permissions: contents: read` e `concurrency` por ref, para não acumular fila de runs esperando runner. Segunda: `runner-check` é o primeiro workflow a rodar quando o runner subir. Não faz checkout, não instala dependência, não usa segredo e não toca rede externa. Só confirma a ligação e inventaria node, npm, docker, psql e python3 — o job `services` precisa de psql e a jornada assíncrona precisa de python3, e é melhor descobrir isso num workflow de dez segundos. A documentação registra a evidência (runs 666 em Waiting e 584 cancelado), recomenda act_runner 0.6.1-dind-rootless com escopo de repositório, e explica por que Docker-in-Docker em vez do socket do host: num repositório público, montar o socket entregaria o Docker da máquina ao workflow. Fica registrado também que os ambientes de aprovação manual do deploy são conceito do GitHub e não têm equivalente no Gitea. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
5b11496cd9
commit
c5790ab301
3 files changed
+217
No files matched your search
@@ -4,10 +4,25 @@ on:
|
|||||||
push:
|
push:
|
||||||
pull_request:
|
pull_request:
|
||||||
|
|
||||||
|
# Mínimo necessário. Nenhum job deste workflow escreve no repositório.
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
# O repositório é público e o runner é uma máquina real. Um pull request de
|
||||||
|
# fork traria código de terceiro para rodar nele, com Docker à disposição.
|
||||||
|
# Enquanto não houver aprovação manual de workflow configurada na instância,
|
||||||
|
# só PR da própria origem executa. Push continua rodando normalmente.
|
||||||
|
# Evita fila de runs esperando runner: um push novo cancela o anterior do
|
||||||
|
# mesmo ref. O que já estava em execução não é interrompido no meio.
|
||||||
|
concurrency:
|
||||||
|
group: ci-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
verify:
|
verify:
|
||||||
name: lint, typecheck, testes e build
|
name: lint, typecheck, testes e build
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
|
||||||
env:
|
env:
|
||||||
ADAPTER_ENV: development
|
ADAPTER_ENV: development
|
||||||
ADAPTER_JOB_MODE: inline
|
ADAPTER_JOB_MODE: inline
|
||||||
@@ -27,6 +42,7 @@ jobs:
|
|||||||
services:
|
services:
|
||||||
name: Postgres e Redis reais
|
name: Postgres e Redis reais
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
|
||||||
services:
|
services:
|
||||||
postgres:
|
postgres:
|
||||||
image: postgres:16-alpine
|
image: postgres:16-alpine
|
||||||
@@ -99,6 +115,7 @@ jobs:
|
|||||||
image:
|
image:
|
||||||
name: imagem Docker Linux
|
name: imagem Docker Linux
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
|
||||||
env:
|
env:
|
||||||
ADAPTER_IMAGE: adaptador-planilhas:ci
|
ADAPTER_IMAGE: adaptador-planilhas:ci
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
name: runner-check
|
||||||
|
|
||||||
|
# Primeiro workflow a rodar quando um runner entrar online. Não instala
|
||||||
|
# dependência, não constrói nada, não toca em rede externa, não usa segredo e
|
||||||
|
# não faz checkout do código: só confirma que a ligação Gitea → runner funciona
|
||||||
|
# e mostra o que o runner tem disponível.
|
||||||
|
#
|
||||||
|
# Dispare pela interface (Actions → runner-check → Run workflow) ou editando
|
||||||
|
# este arquivo.
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
push:
|
||||||
|
paths:
|
||||||
|
- ".github/workflows/runner-check.yml"
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: runner-check-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
ligacao:
|
||||||
|
name: o runner responde
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
# Público: PR de fork não executa enquanto não houver aprovação manual.
|
||||||
|
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
|
||||||
|
steps:
|
||||||
|
- name: Identidade do runner
|
||||||
|
run: |
|
||||||
|
echo "runner.os = ${RUNNER_OS:-?}"
|
||||||
|
echo "runner.arch = ${RUNNER_ARCH:-?}"
|
||||||
|
echo "ref = ${GITHUB_REF:-?}"
|
||||||
|
echo "sha = ${GITHUB_SHA:-?}"
|
||||||
|
echo "evento = ${GITHUB_EVENT_NAME:-?}"
|
||||||
|
echo "---"
|
||||||
|
uname -a
|
||||||
|
echo "usuario: $(id -un) (uid $(id -u))"
|
||||||
|
|
||||||
|
- name: Ferramentas que os outros jobs exigem
|
||||||
|
run: |
|
||||||
|
faltando=""
|
||||||
|
for cmd in node npm docker curl git psql python3; do
|
||||||
|
if command -v "$cmd" >/dev/null 2>&1; then
|
||||||
|
printf ' ok %-8s %s\n' "$cmd" "$($cmd --version 2>&1 | head -1)"
|
||||||
|
else
|
||||||
|
printf ' AUSENTE %-8s\n' "$cmd"
|
||||||
|
faltando="$faltando $cmd"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
echo ""
|
||||||
|
if [ -n "$faltando" ]; then
|
||||||
|
echo "Faltam no runner:$faltando"
|
||||||
|
echo "node e npm são exigidos pelo job verify."
|
||||||
|
echo "docker é exigido pelo job image (precisa de Docker-in-Docker)."
|
||||||
|
echo "psql e python3 são exigidos pelo job services e pela jornada assíncrona."
|
||||||
|
echo ""
|
||||||
|
echo "Isto não falha o workflow de propósito: o objetivo é inventariar."
|
||||||
|
else
|
||||||
|
echo "todas as ferramentas presentes"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Docker funciona de verdade
|
||||||
|
continue-on-error: true
|
||||||
|
run: |
|
||||||
|
if ! command -v docker >/dev/null 2>&1; then
|
||||||
|
echo "docker ausente: o job image não vai rodar neste runner"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
docker version
|
||||||
|
docker run --rm hello-world >/dev/null 2>&1 \
|
||||||
|
&& echo "ok: o runner executa contêiner" \
|
||||||
|
|| echo "docker presente mas não executa contêiner: confira o Docker-in-Docker"
|
||||||
|
|
||||||
|
- name: Resultado
|
||||||
|
run: echo "ligacao Gitea -> runner confirmada"
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
# Gitea Actions — diagnóstico e ativação do runner
|
||||||
|
|
||||||
|
Repositório `cferraz/adaptador-planilhas` em `gitea.fatorxtech.com`, Gitea **1.26.2**.
|
||||||
|
|
||||||
|
## Causa confirmada da ausência de execuções
|
||||||
|
|
||||||
|
**Os runs existem. O que não existe é runner para executá-los.** A falha está na **atribuição**, não no enfileiramento.
|
||||||
|
|
||||||
|
| Evidência | Resultado |
|
||||||
|
| --- | --- |
|
||||||
|
| `ci.yml` detectado por Gitea | sim, listado em Workflows |
|
||||||
|
| Gatilho de `ci.yml` | `on: push` sem filtro de branch — contempla `homologacao` |
|
||||||
|
| Actions no repositório | habilitado (`has_actions: true`) |
|
||||||
|
| Actions na instância | habilitado (a página `/actions` responde e lista workflows) |
|
||||||
|
| Run criado pelo push | **sim** — `ci.yml #2`, run `666`, commit `5b11496cd9`, branch `homologacao`, `2026-10-16 00:05:18 +00:00` |
|
||||||
|
| Estado do run | **Waiting**, duração `0s` |
|
||||||
|
| Mensagem do Gitea | **`No matching online runner with label: ubuntu-latest`** |
|
||||||
|
| Run anterior | `ci.yml #1`, run `584`, commit `419325db12`, branch `main` — **Canceled** |
|
||||||
|
|
||||||
|
Links:
|
||||||
|
|
||||||
|
- https://gitea.fatorxtech.com/cferraz/adaptador-planilhas/actions
|
||||||
|
- https://gitea.fatorxtech.com/cferraz/adaptador-planilhas/actions/runs/666 (Waiting)
|
||||||
|
- https://gitea.fatorxtech.com/cferraz/adaptador-planilhas/actions/runs/584 (Canceled)
|
||||||
|
|
||||||
|
### Por que a API parecia dizer "zero execuções"
|
||||||
|
|
||||||
|
`GET /api/v1/repos/cferraz/adaptador-planilhas/actions/tasks` devolve `{"workflow_runs":[],"total_count":0}`. Esse endpoint lista **tasks**, isto é, jobs que um runner aceitou. Nenhum runner aceitou nada, então a lista é vazia — mas os runs estão criados e esperando. `GET /actions/runs` responde `token is required`, e foi por isso que a primeira leitura passou a impressão errada.
|
||||||
|
|
||||||
|
**Consequência prática: nada precisa ser republicado.** O run `666` está em `Waiting` e começa sozinho no momento em que um runner com a label compatível ficar online.
|
||||||
|
|
||||||
|
## Runners encontrados e labels
|
||||||
|
|
||||||
|
| | |
|
||||||
|
| --- | --- |
|
||||||
|
| Runners com label `ubuntu-latest` **online** | **nenhum** (é o que o Gitea reporta) |
|
||||||
|
| Outros runners na instância | **não determinado** |
|
||||||
|
|
||||||
|
Listar runners exige token de owner ou admin: `GET /api/v1/repos/{owner}/{repo}/actions/runners`, ou a interface em **Settings → Actions → Runners** (nível repositório, organização ou admin). Os números de run (`584`, `666`) sugerem que a instância já teve atividade de Actions em outros repositórios, então **é possível que existam runners com outra label**. Confira antes de instalar um novo:
|
||||||
|
|
||||||
|
- se já houver runner Linux com Docker e outra label (`linux_amd64`, `ubuntu-22.04`, `docker`), **não instale nada**: basta ajustar o `runs-on` dos workflows. Diga qual é a label e a troca é de uma linha em três arquivos;
|
||||||
|
- se não houver, siga a instalação abaixo.
|
||||||
|
|
||||||
|
## Configuração recomendada do runner
|
||||||
|
|
||||||
|
Máquina Linux **dedicada a homologação**, sem nenhuma relação com o Docker de produção do Elo Pro.
|
||||||
|
|
||||||
|
### Por que Docker-in-Docker, e não o socket do host
|
||||||
|
|
||||||
|
O repositório é **público**. Montar `/var/run/docker.sock` do host dá ao workflow controle do Docker da máquina — e, num repositório público, código de terceiro poderia chegar lá por pull request de fork. A imagem `-dind-rootless` sobe um Docker próprio dentro do contêiner do runner: o job `image` constrói e executa contêineres sem alcançar o Docker do host.
|
||||||
|
|
||||||
|
### Instalação
|
||||||
|
|
||||||
|
Versão fixada, compatível com o Gitea 1.26.2 (`0.6.1` é a mais recente publicada; as alternativas semver são `0.1.4` a `0.6.1`):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Token de registro: Settings → Actions → Runners → Create new runner,
|
||||||
|
# no NÍVEL DO REPOSITÓRIO (escopo restrito, não use token de admin).
|
||||||
|
# Grave o token num arquivo com permissão 600 — nunca em commit, nunca em log.
|
||||||
|
umask 077
|
||||||
|
printf '%s' 'COLE_O_TOKEN_AQUI' > /etc/gitea-runner.token
|
||||||
|
|
||||||
|
# 2. Suba o runner com Docker-in-Docker rootless.
|
||||||
|
docker run -d \
|
||||||
|
--name gitea-runner-hml \
|
||||||
|
--restart always \
|
||||||
|
--privileged \
|
||||||
|
-e GITEA_INSTANCE_URL=https://gitea.fatorxtech.com \
|
||||||
|
-e GITEA_RUNNER_REGISTRATION_TOKEN_FILE=/run/secrets/token \
|
||||||
|
-e GITEA_RUNNER_NAME=hml-adaptador \
|
||||||
|
-e GITEA_RUNNER_LABELS='ubuntu-latest:docker://node:24-bookworm' \
|
||||||
|
-v /etc/gitea-runner.token:/run/secrets/token:ro \
|
||||||
|
-v gitea-runner-hml-data:/data \
|
||||||
|
docker.io/gitea/act_runner:0.6.1-dind-rootless
|
||||||
|
```
|
||||||
|
|
||||||
|
Notas que importam:
|
||||||
|
|
||||||
|
- `GITEA_RUNNER_REGISTRATION_TOKEN_FILE` em vez da variável com o valor: o token não aparece em `docker inspect` nem no histórico do shell.
|
||||||
|
- `--privileged` é exigência do Docker-in-Docker. É justamente por isso que a máquina precisa ser dedicada.
|
||||||
|
- A label **tem de ser `ubuntu-latest`**, que é o `runs-on` dos três jobs. O mapeamento `docker://node:24-bookworm` já entrega `node` e `npm` na versão certa.
|
||||||
|
- `psql` e `python3` não vêm nessa imagem. O job `services` usa `psql` e a jornada assíncrona usa `python3`. Duas saídas: trocar o mapeamento para uma imagem que os tenha, ou deixar que o `runner-check` reporte a ausência e então instalá-los num passo do workflow. Rode o `runner-check` primeiro: ele faz esse inventário.
|
||||||
|
- Rede de saída necessária: `gitea.fatorxtech.com` (fila e logs), `github.com` (o Gitea resolve `actions/checkout@v4`, `actions/setup-node@v4` e `docker/*` via proxy), `registry.npmjs.org` e `docker.io`.
|
||||||
|
|
||||||
|
### Conferir que subiu
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker logs -f gitea-runner-hml # espere "runner registered successfully"
|
||||||
|
```
|
||||||
|
|
||||||
|
Depois, em **Settings → Actions → Runners**, o runner aparece online com a label `ubuntu-latest`. O run `666`, que está em `Waiting`, começa sozinho.
|
||||||
|
|
||||||
|
## Ordem de validação
|
||||||
|
|
||||||
|
Nenhum destes foi executado ainda. Não marque nada como aprovado sem o log.
|
||||||
|
|
||||||
|
1. **`runner-check`** — dispare manualmente. Não faz checkout, não instala nada, não usa segredo. Confirma a ligação e inventaria `node`, `npm`, `docker`, `psql`, `python3`.
|
||||||
|
2. **`verify`** — lint, typecheck, 102 testes, build.
|
||||||
|
3. **`services`** — Postgres e Redis efêmeros: as quatro migrations, idempotência, `--dry-run` sem efeito, shim recusado com `ADAPTER_APPLY_IDENTITY_SHIM=0`, `app_user` sem `BYPASSRLS`, `FORCE RLS`, isolamento entre workspaces, auditoria não alterável.
|
||||||
|
4. **`image`** — build Linux, conteúdo da imagem, usuário não-root, ausência de `.env`/docs/testes, os oito scripts operacionais transformando e executando, fail-fast com código diferente de zero na web e no worker, liveness com cabeçalhos, compose até `ready`, heartbeat do worker, jornada assíncrona entre processos e desligamento gracioso exigindo `SIGTERM recebido` em menos de 25 s.
|
||||||
|
|
||||||
|
## Segurança revisada
|
||||||
|
|
||||||
|
| Risco | Estado |
|
||||||
|
| --- | --- |
|
||||||
|
| Repositório **público** com Actions | `ci.yml`, `runner-check` e os três jobs têm guarda: PR de fork **não executa** (`head.repo.full_name == github.repository`). Push continua rodando |
|
||||||
|
| Código não confiável com acesso privilegiado | O runner usa Docker-in-Docker rootless, sem o socket do host. A máquina é dedicada e não alcança o Docker de produção |
|
||||||
|
| Deploy automático | `deploy-staging.yml` tem gatilho único `workflow_dispatch`. Nenhum push, tag ou merge dispara deploy |
|
||||||
|
| Aprovação manual de deploy | Os ambientes `hml-migracao`, `hml-deploy` e `hml-flag` são conceito do GitHub. **O Gitea não os implementa da mesma forma**: se o deploy for rodar aqui, as aprovações deixam de existir e isso precisa ser resolvido antes de apontar o workflow ao servidor |
|
||||||
|
| Permissão do token de Actions | `permissions: contents: read` nos dois workflows |
|
||||||
|
| Fila de runs esperando runner | `concurrency` por ref, com cancelamento do anterior |
|
||||||
|
| SSH de produção | **não configurado**. Nenhum segredo de produção foi adicionado |
|
||||||
|
| Branch `main` | **preservada** em `419325d` |
|
||||||
|
| Token de registro do runner | por arquivo com permissão 600, fora de commit e de log |
|
||||||
|
|
||||||
|
Vale decidir também se este repositório deve seguir público. Auditei os commits e não há segredo algum — só modelos com campos vazios — mas um repositório privado remove de uma vez o risco de PR de fork.
|
||||||
|
|
||||||
|
## Pendências
|
||||||
|
|
||||||
|
**Para o CI rodar:** um runner Linux online com a label `ubuntu-latest`, Docker-in-Docker e saída de rede. Nada além disso.
|
||||||
|
|
||||||
|
**Para homologação, depois do CI verde:** projeto Supabase de homologação com contas de teste e bucket privado; `DATABASE_URL` e a decisão do `ADAPTER_APPLY_IDENTITY_SHIM`; Redis com senha; DSN do Sentry; DNS, TLS e Nginx de `hml-ada.elopro.app`; smoke real e ensaio de rollback. O procedimento está em [`20-homologacao-runbook.md`](20-homologacao-runbook.md) e o estado item a item em [`21-checklist-validacao.md`](21-checklist-validacao.md).
|
||||||
Reference in new issue
Block a user