No description
Find a file
Pablo c6d848f222 Runner do Forgejo Actions, com as instruções para subir
Separa o runner da forge. O Forgejo e o Caddy ficam na VM, no repositório
gibeira; o runner roda na máquina de quem desenvolve, porque o job é que
come RAM — build do Nuxt mais Postgres de testcontainers passam de 3 GB
por alguns minutos a cada push, contra uma VM de 2 GB que ficaria parada
o resto do dia.

O runner-config.example.yml é o arquivo pronto, não a colagem do
generate-config: aquele exemplo já traz um bloco `runner:`, e acrescentar
os rótulos num segundo bloco dá chave duplicada, que YAML recusa.

O runner-config.yml de verdade fica fora do repositório — é ele que
carrega o segredo do registro, e a configuração não expande ambiente, de
modo que não há como manter o segredo em outro lugar.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015BmUugDy3EBa7XihKRaCMq
2026-08-29 15:10:57 -03:00
runner Runner do Forgejo Actions, com as instruções para subir 2026-08-29 15:10:57 -03:00
.gitignore Runner do Forgejo Actions, com as instruções para subir 2026-08-29 15:10:57 -03:00
docker-compose.yml Runner do Forgejo Actions, com as instruções para subir 2026-08-29 15:10:57 -03:00
README.md Runner do Forgejo Actions, com as instruções para subir 2026-08-29 15:10:57 -03:00

O runner da forge

Este repositório é só o runner do Forgejo Actions. A forge — Forgejo e Caddy — roda na VM e mora em gibeira, dentro de ci/. O runner mora à parte porque ele roda em outro lugar: na sua máquina.

A separação é de recursos, não de arrumação. Na VM ficam Forgejo (~300 MB) e Caddy (~20 MB), e ela é uma BV1-2-40 de 2 GB. Quem come RAM é o job: build do Nuxt mais Postgres de testcontainers passam de 3 GB por alguns minutos a cada push. Botar isso na VM significa pagar o dobro por hardware que fica parado 23 horas por dia. Na sua máquina é CPU que já está ligada e não é cobrada por hora.

O preço não é dinheiro, é disponibilidade. CI só existe com a sua máquina ligada. Quando isso incomodar — ou quando o tofu apply de produção passar a rodar aqui, com as credenciais de produção aqui — é hora de subir a VM para BV2-4-40 e mover o runner para lá. É o mesmo docker compose up deste repositório, na VM maior. Nada aqui presume a sua máquina.

Como ele alcança a forge

O runner não abre porta e não recebe conexão. Ele pergunta à forge, por HTTPS de saída, se há job — a cada 30 s. Por isso funciona atrás de NAT, sem IP fixo e sem nada liberado no roteador. A única coisa que ele precisa é chegar em https://git.souzapablo.dev.br.

O par docker-in-docker existe para o runner ter um daemon Docker onde criar os containers de job. É um daemon separado de propósito: job é código com acesso ao daemon que o cria, e o daemon da sua máquina é a sua máquina.

1. Registrar, na VM

O registro é feito na forge, com um segredo de 40 hex que você mesmo gera — sem clicar em nada, sem token de uso único:

# na VM, em /opt/forge
SECRET=$(openssl rand -hex 20)
docker compose exec -u git forgejo \
  forgejo forgejo-cli actions register --name local --scope me --secret "$SECRET"

Guarde as duas coisas: o UUID que ele imprime e o $SECRET que você gerou. Não é um par que se recupera depois. Se perder, registre de novo.

2. Configurar, na sua máquina

cp runner/runner-config.example.yml runner/runner-config.yml
chmod 600 runner/runner-config.yml
$EDITOR runner/runner-config.yml          # url, uuid e token

O modelo já decide o resto — rótulo, capacidade, intervalo de busca — e comenta o porquê de cada um. Falta só a conexão.

O runner-config.yml fica fora do repositório pelo .gitignore: é ele que carrega o segredo. O .example é o que se versiona. Mesma divisão do .env.example e do .env lá no gibeira.

Confira o dono do diretório antes de subir:

id -u        # se for 1000, não faça nada: já está certo

A imagem do runner roda como uid 1000, que é o do primeiro usuário comum na maioria dos Linux. Se o seu não for, e só nesse caso:

sudo chown -R 1000:1000 runner

3. Subir

docker compose up -d
docker compose logs -f runner

O que você quer ver, em ordem:

Starting runner daemon
runner: local, with version: v13.0.0, with labels: [docker], ephemeral: false, declared successfully
[poller] launched

4. Conferir

O runner aparece verde em https://git.souzapablo.dev.br/admin/actions/runners. Um push em qualquer repositório com workflow dispara o job, e o estado da última execução sai daqui sem abrir o navegador:

curl -s 'https://git.souzapablo.dev.br/api/v1/repos/me/<repo>/actions/tasks?limit=1' \
  | python3 -c 'import json,sys; r=json.load(sys.stdin)["workflow_runs"][0]; print(r["name"], r["status"])'

Três armadilhas, todas já pagas

A configuração não expande ambiente. ${GIT_HOST} no YAML não vira nada: o runner lê o arquivo cru. O que estiver entre chaves chega literal e ele recusa a configuração inteira — malformed url "${GIT_HOST}". O segredo vai no arquivo, e é por isso que o arquivo é ignorado pelo git.

generate-config não dá um arquivo pronto. Ele imprime o exemplo comentado de 277 linhas, com um bloco runner: já dentro. Colar um segundo runner: no fim para acrescentar os rótulos dá chave duplicada, que YAML não aceita, e o runner morre em laço de reinício:

Error: invalid configuration: cannot parse config file "runner-config.yml":
  line 77: mapping key "runner" already defined at line 21

O runner-config.example.yml deste repositório já é o arquivo pronto. Use o generate-config só para consultar a lista completa de opções.

O uid da imagem é 1000, não 1001. Um chown -R 1001:1001 runner deixa o runner sem conseguir abrir a própria configuração — open runner-config.yml: permission denied — e o erro só não aparece se o arquivo estiver legível por todos, que é exatamente o que ele não pode estar.

O que ainda não está decidido

container.docker_host está em "-": nenhum socket do Docker é montado dentro do container de job. O que não é montado não é escapada, e nenhum job de hoje precisa. Quando o pipeline trouxer testcontainers, este é o campo que vira automount — e a decisão de segurança que vem junto merece ser tomada na hora, não herdada por descuido.