Pular para o conteúdo principal

CI/CD — GitHub Actions

Todos os repositórios da org cactus-agents usam GitHub Actions para CI e deploy. Esta página é a referência técnica do pipeline: inventário de workflows, o que o reusable de deploy faz passo a passo, e as mecânicas de multi-account.

:::tip Onde está o quê

Inventário de workflows

front-cactus-core (SDK)

WorkflowTriggerO que faz
ci-release.ymlpull_request (todos) + push em mainJobs cichangesetrelease. Em PR só roda ci. Em push na main, auto-gera changeset dos conventional commits, versiona e publica
delete-merged-branch.ymlpull_request: [closed]Limpeza de branches mergeadas

São exatamente dois arquivos. ci.yml foi deletado e release.yml renomeado para ci-release.yml; auto-changeset.yml foi absorvido pelo mesmo arquivo.

front-web-base (template)

WorkflowTriggerO que faz
ci-deploy.ymlpush e pull_request nos globs main, main-*, stage, stage-*, sports, sports-* + workflow_dispatchJobs quality, config-lint (só PR), resolve-matrix, deploy (chama o reusable do front-ops)
list-environments.ymlworkflow_dispatchViewer read-only da topologia de envs declarada no front-ops. Sem build, sem deploy
lighthouse.ymlworkflow_dispatch (input brand)Chama front-ops/lighthouse-base.yml. Manual-only — perf budget ainda não é gate de deploy
delete-merged-branch.ymlpull_request: [closed]Limpeza de branches mergeadas

:::info Workflow único CI e deploy do front-web-base ficam no mesmo arquivo ci-deploy.yml. O job quality roda em todo trigger; deploy só fora de PR e quando a matriz resolve algum env. :::

Os globs de branch — leia os arrays, não o comentário

O ci-deploy.yml declara os filtros em dois arrays idênticos, e são eles que o GitHub casa:

on:
pull_request:
branches: ['main', 'main-*', 'stage', 'stage-*', 'sports', 'sports-*']
push:
branches: ['main', 'main-*', 'stage', 'stage-*', 'sports', 'sports-*']

Acima deles há um comentário resumindo como "namespaces suportados: main*, stage* e sports*". As duas formas descrevem a mesma coisa — o comentário é o resumo em 3 namespaces, os arrays são os 6 valores operativos. Quando você precisar adicionar cobertura, edite os arrays; é a forma que a mensagem de erro do config-lint também pede ("Add a matching pattern … e.g. main-*").

:::caution dev* e performance* foram removidos dos filtros de branch Os prefixos dev* e performance* saíram dos dois arrays em 2026-08-10, junto com as 7 envs que os usavam — o commit e920c2b no front-ops removeu os grupos dev, performance, react-c4ctus, stage-cache e stage-spa, e moveu 7wins-cc de dev-7wins para main-7wins.

O próprio ci-deploy.yml carrega o aviso in-file: não recoloque o glob sem criar a env correspondente no front-ops, senão o config-lint passa a acusar branch sem env. :::

front-ops (deploy e cache centralizados)

Onze workflows. Cinco expõem workflow_call (consumíveis por outros workflows — deploy, deploy-docs, deploy-affiliates, lighthouse-base e purge-all); o resto é operação manual ou agendada dentro do próprio front-ops.

Workflow (name:)ArquivoTriggerO que faz
Deploy (Reusable)deploy.ymlworkflow_call — input environmentReusable de deploy dos brand workers. Jobs check + deploy
Deploy Docs (Reusable)deploy-docs.ymlworkflow_call — inputs environment, ref (opcional)Deploy do front-cactus-docs. Envs em config/docs/environments/
Deploy Affiliates Front (Reusable)deploy-affiliates.ymlworkflow_call — inputs environment, ref (opcional)Deploy do front de afiliados. Envs em config/affiliates/environments/
Lighthouse CI (front-web-base)lighthouse-base.ymlworkflow_call — input brand (default state77-com); secrets GH_PACKAGES_TOKEN, FRONT_GH_ACTIONS_TOKEN (ambos opcionais)Builda a brand, sobe preview Wrangler local e roda Lighthouse mobile
Bump cache generationbump-cache-generation.ymlworkflow_dispatchreason, target_value, brand_workers (none/single/all), single_environment, redeploy_service_apiAuto-incrementa o token de generation, commita em config/cache/generation.yml e dispara os redeploys
Manual Cache Purgemanual-cache-purge.ymlworkflow_dispatchenvironment, tag_quickpick, tags, glob, segments, scope, dry_runPurge manual por tag/glob/segmento em uma ou todas as camadas
Cache Statuscache-status.ymlworkflow_dispatchenvironment, include_state, tag, globInspeção read-only do estado do cache de uma env
Purge Allpurge-all.ymlworkflow_dispatch e workflow_call — input environmentPurge total de uma env, em "two-pass reverse-order" (múltiplas rodadas, camadas em ordem inversa)
Purge Monitorpurge-monitor.ymlschedule: */5 * * * * + workflow_dispatchBate nas rotas de config/cache/monitor-routes.yml direto no BFF, compara o hash com o run anterior e, em drift, dispara purge-all.yml da brand
Sync purge optionssync-purge-options.ymlpush em main nos paths de config + schedule: 0 9 * * * + workflow_dispatchRegenera os dropdowns environment e tag_quickpick do manual-cache-purge.yml e abre uma PR se houver drift
Delete merged branchdelete-merged-branch.ymlpull_request: [closed]Limpeza de branches mergeadas

:::info Por que sync-purge-options abre PR em vez de commitar Listas workflow_dispatch.options são YAML estático — o GitHub não as popula do filesystem na hora de renderizar a UI. Além disso, o GitHub bloqueia por hard-rule o app github-actions de alterar arquivos em .github/workflows/; por isso o push da branch e a criação da PR usam o PAT FRONT_GH_ACTIONS_TOKEN. Consequência prática: quando você adiciona uma env ou uma tag nova, aparece uma PR automática atualizando os dropdowns — mergeie. :::

Modelo de deploy: caller + reusable

┌──────────────────────────────┐ ┌──────────────────────────────────┐
│ front-web-base (caller) │ │ front-ops (single source) │
│ .github/workflows/ │ │ config/brands/web-base/ │
│ ci-deploy.yml │ │ brand.yml │
│ • quality │ ──────▶ │ cache.yml │
│ • config-lint (PR) │ chama │ environments/<env>/deploy.yml │
│ • resolve-matrix │ │ config/cache/*.yml │
│ • deploy (matrix) │ │ .github/workflows/deploy.yml │
└──────────────────────────────┘ └──────────────────────────────────┘
# ci-deploy.yml no caller — job que chama o reusable
deploy:
name: ${{ matrix.environment }}
needs: [quality, resolve-matrix]
if: needs.resolve-matrix.outputs.deploy_needed == 'true'
strategy:
fail-fast: false
matrix:
include: ${{ fromJson(needs.resolve-matrix.outputs.matrix) }}
uses: cactus-agents/front-ops/.github/workflows/deploy.yml@main
with:
environment: ${{ matrix.environment }}
secrets: inherit

O fork não contém lógica de resolução de worker_name, tokens Cloudflare, política de cache ou pipeline de build/deploy. O único input do reusable é environmentnão existe input ref. Ver branch governance.

Os jobs do caller

JobQuando rodaO que faz
qualitysempre (PR draft é pulado; ready_for_review re-dispara)pnpm install --frozen-lockfile (com NODE_AUTH_TOKEN: GH_PACKAGES_TOKEN) → lintchecktypecheck:citest com CACTUS_FORCE_REGISTRY=1
config-lintsó em pull_request não-draftVer abaixo
resolve-matrixneeds: qualitypush: todas as envs com default_branch == github.ref_name e auto_deploy: true. workflow_dispatch: uma entrada só. pull_request: pulado
deployquando a matriz resolve ≥1 envChama o reusable, um job por env

O que o config-lint valida

Três checagens, com severidades diferentes:

  1. Ownership da brandbrand.yml::repo do diretório escaneado tem que ser igual ao repo caller. Divergência → erro.
  2. Dropdown vs pastas de environment — assimétrico, de propósito:
    • entrada no dropdown que não existe no front-opserro sempre (um dispatch dela quebraria);
    • env do front-ops faltando no dropdown → erro só quando GITHUB_BASE_REF == main; em qualquer outra base branch é warning. Motivo declarado no arquivo: envs novas nascem na main e as branches longas herdam no próximo sync — antes disso, a checagem estrita quebrava toda PR dessas branches.
  3. Cobertura dos globs de branch — todo default_branch declarado no front-ops tem que casar com ao menos um glob de on.push.branches e de on.pull_request.branches. Sem cobertura → erro. Globs que não casam com nada são aceitos (flexibilidade intencional).

:::caution O brand-dir é hardcoded no caller No front-web-base, o job config-lint fixa BRAND_DIR="_ops/config/brands/web-base" e CALLER="cactus-agents/front-web-base" no shell do step. Um fork tem que ajustar esses valores para a própria brand — ver Adicionar brand/fork. :::

O pipeline do reusable deploy.yml, passo a passo

Dois jobs: check (resolve e autoriza) e deploy (33 steps nomeados).

Job check

  1. Checkout ops repo (configs de brand).
  2. Resolve deploy config from brand configs — acha a brand cujo repo: bate com o caller, lê environments/<env>/deploy.yml com fallback em brand.yml::defaults e nos defaults embutidos. Falha nas condições listadas em Deploy → Regras de bloqueio.
  3. Authorization gate (workflow_dispatch) — só quando o evento é workflow_dispatch e deploy_protection.required_team está setado. Verifica membership do ator no team via API. push nunca é gateado.
  4. Verify auto-deploy eligibility (push events) — só em push.

Como o gate vive no job check, uma reprovação aparece na UI como check ❌ → deploy skipped.

Job deploy — configuração do job

timeout-minutes: 30 # era 15; o cutover opcional estende o job
concurrency:
group: deploy-${{ needs.check.outputs.worker_name }}
cancel-in-progress: false # run novo ESPERA na fila
permissions:
contents: read
packages: read
deployments: write
id-token: write # OIDC do collector AWS SSM

:::info Por que a serialização por worker existe Adicionada depois do postmortem stage-7k de 2026-07. Sem ela, dois runs sobrepostos do mesmo worker podem terminar fora de ordem — código velho deployado por último e/ou BUILD_TS invertido em relação à ordem real, fazendo a versão viva se auto-marcar stale no build-currency gate (SSR cache desligado silenciosamente até o deploy seguinte). cancel-in-progress: false é deliberado: nunca matar um wrangler deploy no meio. :::

Steps que rodam sempre

#StepNota
1Checkout caller repono deploy_ref resolvido
2-4Setup pnpm / Setup Node / Install dependenciesNode 24, cache pnpm
5Resolve CF credentialscf_accountFRONT_{XX}_CF_ACCOUNT_ID / _CF_API_TOKEN, mascarados com ::add-mask::
6Resolve API_BASE_URL and CF_WORKER_KEYAPI_BASE_URL dos bindings do Worker. CF_WORKER_KEY vem da org secret — ver aviso abaixo
7Generate .envBRAND_LANGUAGE, ORIGIN_DOMAIN, API_BASE_URL, CF_WORKER_KEY
8Buildpnpm build com as vars acima + GITHUB_SHA
9Checkout ops repo (for cache config)
10Resolve cache policydefaults → brand → env
12Patch wrangler.toml with runtime varsinjeta o bloco [vars] — ver Referência de secrets
13Resolve R2 S3 API credentials
17Deploy to Cloudflare Workersdeploy --name <worker> --keep-vars
24Verify single-version deployment (no stuck split)checa a config na API CF; split confirmado falha o job
25Probe served build id (observational orphan check)checa o que o hostname serve: poll em /api/version comparando buildId. Env atrás de CF Access não é alcançável do runner → warning
26-27Resolve post-deploy cache purge segments / Resolve cache purge secrets
31Purge SSR cache (CF zone purge)
32Warm up cachecurl paralelo, budget de 8s por path, em /, /casino, /casino/live, /cassino, /cassino/ao-vivo, /buscar, /promocoes, /api/version, /api/configurations, /api/appearance
33Summarybrand, env, conta CF, URL, ref, cache

:::danger CF_WORKER_KEY NÃO é lido dos bindings do Worker O step Resolve API_BASE_URL and CF_WORKER_KEY extrai dos bindings apenas API_BASE_URL. CF_WORKER_KEY é resolvido exclusivamente por indireção sobre as org secrets (SECRET_{CF_ACCOUNT}_WORKER_KEYFRONT_{XX}_CF_WORKER_KEY) — não há fallback para o binding. Se você configurar a chave só no Worker, o build sai com CF_WORKER_KEY vazio e sem erro nenhum. :::

Steps opt-in, com o campo que os liga

StepGate
Provision KV namespacepolítica de cache tem algum recurso com storage.snapshot: kv
Ensure R2 bucket exists (idempotent)credenciais R2 presentes. Roda wrangler r2 bucket create front-assets-archive || true
Sync assets to R2 (cross-deploy fallback)credenciais R2 presentes. skip_asset_verify: true desliga só o verify HTTP
Purge service-api cache BEFORE deployserviceApiPurge.enabled na política de cache
Patch entry worker + Deploy entry worker to Cloudflaredeploy_entry_worker: true
Configure AWS credentials (warm collector)run_warm_cutover: true
Run warm collector (AWS SSM front-web-warmup)run_warm_cutover: true
Bump CACHE_GENERATION_SECRET (web-base + entry)run_warm_cutover: true
Purge all — multi-camada (após o bump)run_warm_cutover: true
Skip purge noticeskip_post_deploy_purge: true
Purge platform cache after deploypurge secret resolvido e skip_post_deploy_purge != true
Purge service-api cache AFTER deployserviceApiPurge.enabled e purge secret resolvido

Entry worker (deploy_entry_worker)

Com a flag ligada, o job também publica um segundo Worker enxuto <worker_name>-entry a partir de wrangler.entry.toml do base:

  • o binding de service ORIGIN é reescrito por sed para apontar para o worker principal (service = "<worker_name>");
  • o [vars] do entry é lockstep com o principal, só no subconjunto que ele consome: BUILD_ID, BUILD_TS, CACHE_NAMESPACE, CACHE_GENERATION e os quatro SSR_CACHE_*_TTL. Sem KV, sem policy, sem cron — o entry não usa platform-cache. O lockstep importa porque a chave de cache é composta a partir desses valores;
  • apontar o domínio para o worker -entry é um passo manual separado. A flag só publica.

:::danger wrangler.entry.toml não existe em toda branch O arquivo está presente hoje apenas na branch stage-spa-v2 do front-web-base — que é exatamente o default_branch do único env com deploy_entry_worker: true. Ligar a flag num env cujo default_branch não tem o arquivo faz o step de patch falhar. Confirme que a branch tem o wrangler.entry.toml antes de habilitar. :::

Warm cutover (run_warm_cutover)

Ordem fixa: collector SSM → bump da generation → purge multi-camada.

  1. aws-actions/configure-aws-credentials@v4 assume AWS_WARMUP_ROLE_ARN via OIDC em eu-central-1.
  2. aws ssm send-command --document-name front-web-warmup com --targets Key=tag:front-web-warmup,Values=<tenant> e --parameters buildId=<BUILD_ID>,tenant=<tenant>, e então faz poll de todas as invocações (até ~10min); qualquer status diferente de Success falha o job. O tenant vem do campo homônimo do deploy.yml da env (default = nome da env).
  3. Bump CACHE_GENERATION_SECRET no worker principal e no entry — flipa a generation via secret, sem rebuild.
  4. Purge all multi-camada logo depois, para limpar o conteúdo velho da borda.

Requisitos: o secret AWS_WARMUP_ROLE_ARN (role com ssm:SendCommand / ssm:ListCommandInvocations nas instâncias com a tag front-web-warmup=<tenant>), os org secrets FRONT_{XX}_CACHE_PURGE_SECRET / _CF_WORKER_KEY, e o entry já deployado.

:::caution Dois "warm-up" diferentes O step Warm up cache do deploy (curl nas rotas principais, sempre) não tem nada a ver com o front-service-api-warmup (Worker separado, alimentado por crons de GitHub Actions). Ver Serviços de infraestrutura. :::

Multi-account Cloudflare

Cada environment aponta para uma conta CF via o campo cf_account no environments/<env>/deploy.yml (ou como default em brand.yml). O valor é um prefixo de 2-4 letras maiúsculas.

NomePrefixo
CactusCT
BluetecBT
TesteTS

O workflow resolve FRONT_{PREFIXO}_CF_ACCOUNT_ID e FRONT_{PREFIXO}_CF_API_TOKEN, mascarando os valores com ::add-mask::.

:::info Por que adicionar uma conta exige editar YAML GitHub Actions não permite indexar secrets.* dinamicamente por nome. Cada secret é exposta como uma env var nomeada (SECRET_CT_ID, SECRET_BT_TOKEN, …) e o shell resolve por indireção (${!VAR_NAME}). Além do deploy.yml, cinco outros workflows carregam o próprio registro de secrets por conta — o passo a passo completo está em Adicionar conta CF. :::

Secrets

O catálogo completo — nomes, quem consome, obrigatoriedade e comportamento na ausência — vive em Deploy → Referência de secrets. Resumo do que é org-level:

SecretEscopoUso
GH_PACKAGES_TOKENPAT read:packagesInstalar @cactus-agents/* no CI dos callers (job quality) e no lighthouse-base.yml
FRONT_GH_ACTIONS_TOKENPAT repo, read:packages, read:orgCheckout do front-ops (privado), install de pacotes no deploy, resolução do HEAD SHA via API, authorization gate de team, e os pushes do bump-cache-generation / sync-purge-options / ci-release do core
FRONT_{CT,BT,TS}_*por conta CFAccount ID, API token, worker key, purge secret, R2
AWS_WARMUP_ROLE_ARNRole ARN (OIDC)Único credential não-Cloudflare e não-GitHub do caminho de deploy. Só usado quando run_warm_cutover: true
LHCI_GITHUB_APP_TOKENtoken do Lighthouse CI GitHub Applighthouse-base.yml

Acesso das org secrets

Ao criar um novo fork/repo, adicione-o ao Repository access de cada secret relevante (GitHub → cactus-agents → Settings → Secrets and variables → Actions). Sem isso o workflow do novo repo não vê a secret e o deploy/CI falha.

Permissões dos workflows

Declare o bloco permissions explicitamente. Sem ele, o GITHUB_TOKEN recebe permissões padrão que podem não incluir packages: read:

jobs:
quality:
runs-on: ubuntu-latest
permissions:
contents: read
packages: read

Adicionando CI/CD a um novo fork

Os workflows já vêm no fork. Checklist resumido (passo a passo completo em Deploy → Adicionar brand/fork):

  1. Criar a brand e o primeiro env no front-ops: config/brands/<brand>/brand.yml (com repo:) e config/brands/<brand>/environments/<env>/deploy.yml (com cf_account).
  2. Ajustar BRAND_DIR / CALLER nos jobs do ci-deploy.yml do fork.
  3. Org secrets: adicionar o novo repo ao Repository access de GH_PACKAGES_TOKEN, FRONT_GH_ACTIONS_TOKEN e dos FRONT_{XX}_CF_* da conta.
  4. CF API Token: se a brand usa domínio novo, incluir a zona em Zone Resources do token da conta.
  5. Subir o Worker e configurar API_BASE_URL nos bindings — ver Adicionar environment.
  6. Push no default_branch (ou dispatch manual) → workflows rodam.