Changelog
Todas as mudancas notaveis neste projeto sao documentadas neste arquivo.
O formato e baseado em Keep a Changelog, e este projeto adere ao Semantic Versioning.
[2.34.7] - 2026-06-09
Modificado
- O bloco
Next steps:do output adota o mesmo guia de trilho│dos blocos de detalhe.
[2.34.6] - 2026-06-09 — output mais legivel
Modificado
- Erros saem com a tag
ERROR(vermelho), alinhada as linhasINFO/WARN. Antes o erro era impresso comoError: ...sem cor, facil de nao perceber no meio do output. - Blocos de detalhe (
status,health,tokens test,canary status,debug last-error) ganham um guia de trilho│a esquerda + labels esmaecidos, com uma linha em branco antes, pra localizar o bloco sob o header[OK]/[FAIL].
Corrigido
runner -Vexibe a tag git apenas quando ela bate com a versao do build (antes podia mostrar a tag da release anterior).
[2.34.1] - 2026-06-06 — yank dos comandos templates e apikey
Os comandos runner templates e runner apikey introduzidos em 2.34.0
foram removidos por estarem no escopo errado: o catálogo de templates
e o ciclo de vida de API keys são responsabilidade do deployally, não
do runner. Os dois mundos são contextos independentes — o CCS é o único
agregador. Esses comandos vão portar pro deployally v3 em release
separado do deployally client.
O envelope JSON canônico v2.0, o streaming JSONL pra deploy/fetch e os comandos portados (list, status, deploy, fetch, reconcile, ...) ficam intactos.
Removido
runner templates {list, locate, check, identify}— vão portar pro deployally v3.runner apikey {create, list, show, rotate, revoke}— vão portar pro deployally v3.- Audit log
/opt/runner/logs/apikey-events.jsonl— não é mais escrito pelo runner (passa pra responsabilidade do deployally).
Mantido da v2.34.0
Envelope canonico v2.0 (`-o json`)
Toda resposta one-shot vira um unico objeto JSON com campos garantidos:
{
"success": true,
"timestamp": "2026-06-06T14:23:45Z",
"schema_version": "2.0",
"client": { "name": "runner", "version": "2.34.1" },
"command": "list",
"data": { ... }
}Em erro, success: false, data omitido, bloco error: { code, message, details }
com codigo estavel (container_not_found, validation_failed, auth_required, ...).
Detalhes em Output formats.
Streaming JSONL (deploy, fetch, validate, reconcile)
Comandos longos emitem uma linha JSON por evento em -o json, tagueada
por event_type:
deploy_phase(pull/create/start/healthcheck/cleanup/rollback)fetch_progress(resolve/download/extract)validate_step(info/warning/error por step)reconcile_step(drift detectado + acao)
Cada evento e parseavel independente (jq -c, line-by-line Python). Ordem
de emissao preservada.
Deprecado
--jsonpor subcomando (runner fetch --json,runner stage list --json) fica deprecated em favor do flag global-o json/--json. Continua aceito com warning em stderr ate v3.0.0.
# Antes
runner fetch --deploy --json
# Depois
runner -o json fetch --deployMigracao
Atualizar crons:
# /etc/cron.d/runner
*/5 * * * * /opt/runner/runner -o json fetch --deploy >> /var/log/runner-fetch.json 2>&1Scripts que parseavam o output antigo continuam funcionando (envelope v2.0
inclui os mesmos campos top-level), mas ganharam metadados novos
(schema_version, client, timestamp).
[2.33.1] - 2026-06-04
Corrigido
runner --versionreportava um commit de build desatualizado no hash em algumas releases. Agora reflete o commit correto. Sem mudanca funcional.
[2.33.0] - 2026-06-03 — classificacao automatica de env/secrets
O runner wizard e o runner add passam a classificar environment: e secrets: sozinhos ao gerar o .deploy.yml, sem perguntar variavel por variavel.
Adicionado
Classificacao automatica de env/secrets
Ao gerar o .deploy.yml, o Runner decide sozinho quais variaveis sao environment: (plain) e quais sao secrets:, e como resolver cada uma. Deterministico, sem IA. Detalhes em Classificacao automatica de env/secrets.
- Prefixo publico (
NEXT_PUBLIC_*,VITE_*,REACT_APP_*...) vai paraenvironment:, mesmo comKEYno nome. Nomes de secret vao parasecrets:. - Auto-geravel (
JWT_SECRET,APP_KEY...) vira${GENERATE:hex:64}. Externo (STRIPE_SECRET_KEY,SMTP_PASSWORD...) vira{{::KEY}}+required:. Banco e ambiguos: pergunta no modo interativo, ou prompt +required:no nao-interativo. - Secret auto-geravel gera valor novo para producao, sem herdar o valor de desenvolvimento.
- Tela de revisao consolidada no modo interativo, e comentarios
# DETECTED:documentando cada decisao no manifesto.
Bloco `required:` e fail-fast
Secrets externos sem valor entram em required:. O deploy aborta cedo, antes de tocar o Docker, se alguma variavel obrigatoria faltar ou estiver vazia.
Corrigido
runner wizardem repo com Dockerfile agora geratype: docker-build+build:. Antes geravaimage:com a imagem base e nao buildava a app.- O
.deploy.ymlgerado pelo wizard agora inclui o blocoversion:. Antes o deploy falhava comversion.file is empty. runner addnao gera mais secrets como placeholder vazio (KEY: ""). Passa a usar a classificacao automatica.
[2.32.0] - 2026-06-03 — UX report do SDR (8 fixes)
Onboarding de monorepo pnpm com 2 apps (Fastify API + Next.js standalone) produziu um UX report detalhado de 11 atritos. Esta release endereca 8 deles via fix de codigo (3 ja estavam OK e operadores apenas nao sabiam).
Adicionado
Multi-service deploy (P0)
runner add --from-compose <compose-path> bulk-registra todas as apps
do compose num call. Detecta services via wizard discovery, requer
manifests em <service>/.deploy.yml, gera um ckey shared automatico
threadado pra cada runner add. Skip silencioso de infrastructure
(redis, postgres, etc.).
runner wizard --from-compose docker-compose.yml --repo <repo> --branch main
git commit -am 'add manifests' && git push
runner add --repo <repo> --branch main --from-compose docker-compose.yml
# → registra N apps com 1 ckey shared, sem --ckey literal repetidoManifest `required:` + pre-flight bail (P2)
Campo novo required: [VAR1, VAR2] no topo do .deploy.yml. runner deploy bailha fatal antes de tocar Docker se algum key falta ou esta
vazio em .env/.secrets. Fim do healthcheck loop de 173s quando
operador esquece de setar uma var obrigatoria.
required:
- NEXT_PUBLIC_SUPABASE_ANON_KEY
- DB_PASSWORDrunner env validate <app> sem --required cai no required: do
manifest automaticamente. runner deploy --allow-missing-required
bypassa pra emergencia.
Bulk env loader (P4 + P5)
runner env load <app> [--file <path>] carrega .env-format de uma
vez. Sem --file, walks cwd → <app>/pull/ por candidates
.env*/.secrets* (.env.example/.template/.sample excluidos).
Classify cada key via detector (override com --all-secret/--all-plain).
Auto-sync de peers com ckey shared: walks state dir, fingerprint
do ckey, propaga writes pra peers com fingerprint igual. Confirm
prompt (--yes pra CI) + --no-propagate pra opt-out.
runner env load sdr-api --file ./sdr-prod.env --yes
# → carrega 10 keys + propaga pra sdr-web (mesmo ckey)Auto-propagation de NEXT_PUBLIC_* (P9)
Apps frontend nao precisam mais declarar NEXT_PUBLIC_* duas vezes
(build.args: E environment:). Keys cujo nome bate com prefixo
publico conhecido — NEXT_PUBLIC_, VITE_, REACT_APP_,
NUXT_PUBLIC_, EXPO_PUBLIC_, PUBLIC_ — automaticamente migram de
build.args pro environment efetivo. Tokens privados (NPM_TOKEN,
GH_TOKEN, DATABASE_URL) NAO propagados — ficam confinados ao
build. environment: explicito sempre ganha.
Modificado
`runner inspect-args` redacta secrets (P6)
Antes printava DB_PASSWORD={{::DB_PASSWORD}} literal. Agora:
Default (sem --insecure) |
Com --insecure |
|---|---|
| plain → valor verbatim | plain → valor verbatim |
secret → <set> |
secret → valor real |
| missing + default no YAML → default | mesmo |
missing + sem default → <empty> |
mesmo |
Reusa o global --insecure (mesmo flag que ckeys reveal).
`bootstrap_push` respeita `--manifest-path` (P1)
Operador registra apps/api/.deploy.yml com --manifest-path e o
upstream agora recebe a copia em apps/api/.deploy.yml (nao em
.deploy.yml raiz duplicado como antes). Fim do runner-bot empurrando
manifest duplicates.
Advisories nao-fatais no `runner add`
DNS sanity check (P10)
Walks every instance domain, resolve via system resolver, detecta IP
publico do servidor via ipify/ifconfig.me (timeout 2s). WARN
nao-fatal quando: domain nao resolve (NXDOMAIN) ou resolve pra IP
diferente. Match e test domains (.invalid/.test/.local/wildcards)
ficam silent. --skip-dns-check pra CI / quando operador sabe que DNS
ainda esta sendo configurado.
Monorepo build.context (P7)
Detection convention-based: se repo tem marker no root
(pnpm-workspace.yaml, turbo.json, lerna.json, nx.json,
rush.json) E build.context canonicalizado != root → WARN nao-fatal
apontando pro fix tipico (build.context: ../.. quando manifest esta
em apps/<svc>/.deploy.yml).
Itens do report que ja estavam OK (sem fix necessario)
- P3 lock conflict cron vs manual deploy: erro claro em
pipeline/lock.rs:104-156, cron silencia gracefully. - P8 DNS alias estavel web → api:
network_aliases:existe desde v2.24.0. Operadores nao sabiam.
Suite
853 tests passing (+~63 desde v2.31.0).
Detalhes tecnicos
Changelog completo de boundaries (modules novos: dns_check,
monorepo_check, environment::load, commands::env_load,
fetch::from_compose) no
CHANGELOG.md do client.
[2.31.0] - 2026-05-29 — Wizard UX refactor
Reescrita do runner wizard pra corte de fluxo e UX clara. Greenfield Dockerfile-only agora vira deploy em 2 perguntas (target <60s).
Adicionado
- Pre-flight gate antes de qualquer pergunta. Bail com hint actionable quando repo não pode ser deployado (sem Dockerfile + sem compose, sem
.git, manifest existe sem--force-overwrite). - Q0 source discovery: prefere
--path, fallback--repoclone, fallback cwd com.git, último recurso prompt interativo. - Token discovery em 4 tiers:
--token>$GITHUB_TOKEN> vault (runner tokens) > prompt interativo. ?em qualquer prompt mostra explicação curta inline (mata o "preciso conhecer o Runner pra responder").- Handoff estruturado no fim: arquivos gerados, ckey + backup hint, dois caminhos pra mexer em secret (CLI ou encoder web), próximos passos.
--resumeflag pra retomar wizard interrompido (lê.runner/wizard-answers.ymlv2).
Modificado
- Set de perguntas reduzido a 5 (Q1: project, Q2: instance+domain, Q3 condicional: multi-service picker, Q4 condicional: placeholders, Q5 condicional: ckey strategy).
- Managed assets (redis/mysql/postgres) no compose multi-service ficam mostrados mas NÃO pré-selecionados.
runner addforçaforce_overwrite=trueno wizard interno (clone fresh sempre substitui).
Compatibilidade
--accept-defaultscontinua funcionando (non-interactive).--from-composecontinua funcionando.runner addinterno usa o novo wizard sem regressão.- Web wizard schema preservado.
wizard-answers.ymlv1 carrega normalmente; salva como v2 no próximo write.
[2.30.13] - 2026-05-29 — Fix: `{{::AppDomain?}}` agora expande em runtime
Bug correlato à v2.30.12: o placeholder {{::Var?default}} do wizard não expandia em runtime quando aparecia em instances.<X>.domain. v2.30.12 silenciou o vazamento na notificação; v2.30.13 corrige a raiz.
Corrigido
InstanceConfig::effective_routes_resolved(novo método emsrc/config/mod.rs) — resolve{{::Var?default}}em cadadomaincontra.env+.secretsda app. Routes que resolvem pra string vazia são DROPADAS em vez de gerarHost(\{{::AppDomain?}}`)` literal que nunca matcha.- Traefik routing (
pipeline/container.rsTraefikMode::LocaleExternal) — agora chamaeffective_routes_resolvedem vez deeffective_routes. Antes, app comdomain: "{{::AppDomain?}}"semAppDomainno.envdeployava OK, container subia healthy, mas Traefik não matchava e a app ficava 404 em produção — silent miscompile. - Notificação Telegram (
pipeline/deploy_single.rs) —notify_domainresolve antes de chegar no event payload. Fallback: se resolução erra ou ainda contém{{::, omite a linha em vez de vazar o template.
Erro mais útil
Quando o placeholder não resolve e não tem default, o runner agora aborta com mensagem actionable em vez de deployar broken:
Instance 'X' has no domain or routes configured (after resolving
{{::Var?...}} templates against .env/.secrets). Either set the variable
(`runner env set <app> --key <K> --value <V>`) or remove the template
from `.deploy.yml`.Testes
5 testes de regressão em src/config/tests.rs cobrindo: drop quando placeholder + sem env + sem default; resolve via .env value; resolve via template default; domínio literal preservado; path_prefix mantido após resolução. Total: 742 passing.
[2.30.12] - 2026-05-28 — Notificações redesenhadas (env badge + branch + PR detection + TTL)
Operadores rodando multiplos projetos num único canal Telegram não conseguiam distinguir ambientes/branches/PRs no fluxo de mensagens. Esta release redesenha o template pra colocar o sinal visual logo na primeira linha.
Adicionado
- Badge de ambiente na primeira linha:
🟢 PROD,🟡 STAGING,🔵 DEV,🟣 PR-N,⚪ <outro>. Classificação automática a partir do nome da instance (production/prod→ PROD;staging-*/homolog→ STAGING;dev*/development→ DEV;pr-Noupr_numberno state → PR-N). - Linha
🌿 branchmostrando de qual branch saiu o deploy (lido do state). - Detecção de origem PR via regex no commit subject — distingue:
PR #N— merge commit padrão do GitHubPR #N— squash merge (trailer(#N)no subject)PR #N (preview)— PR deploy isolado (runner add --pr N)⚠️ direct push— empurrou direto na branch (provavelmente pulou review)
- Linha
⏰ Auto-destroy em Xhsó para PR deploys com TTL ativo. Calculado em runtime a partir destate.created_at + state.ttl_hours. - Novo evento
PrExpireddisparado pelorunner cleanup --expiredquando destrói um PR vencido. Avisa o dev que o ambiente que ele estava testando sumiu (vs ele descobrir via 404).
Modificado
- Versão compacta:
🏷️ v1.2.3 ← v1.2.2(era duas linhas separadas). - Casing dos títulos: sentence-case (
Deploy concluídoem vez deDeploy Concluído). - Domínio com placeholder do wizard: se o template contém
{{...}}(placeholder não expandido em runtime), a linha🔗 Domínioé OMITIDA em vez de aparecer literal no Telegram.
Bug correlato (separado, próxima release)
- Placeholders
{{::AppDomain?}}do wizard ainda não expandem em runtime quando lidos do.deploy.yml. v2.30.12 silencia a linha pra evitar UX confusa; o fix da expansão fica pra v2.30.13.
[2.30.11] - 2026-05-27 — Fix: deploy quebra com `version.file=""`
Fecha bug crítico reportado pelo customer Agency-Arms (sweepstake-backend): deploy falha com Failed to read version file: ".../pull/" — Is a directory (os error 21) quando o .deploy.yml tem version: { source: file, file: "" }. Bloqueia QUALQUER deploy da app.
Corrigido
pipeline/prepare.rs::extract_versionganha o guard contraversion.filevazio /././quefetch/git.rs::extract_version_with_diagnosticjá tinha desde v2.23.19. Antes,app_path.join("pull").join("")resolvia prapull/(diretório),exists()retornava true, eread_to_stringquebrava com o erro opaco "Is a directory (os error 21)". Agora bail!a antes do read com mensagem actionable:version.file is empty or '.' — set a concrete file in .deploy.yml or change to version.source: git-commit.- Defense-in-depth: troca
exists()→is_file()em todos os candidate paths (pull/, src//current/, root, legacy current/) pra nunca passar um diretório pra read_to_string. - Wizard (
wizard/compose/mapper.rs) para de gerarfile: ""em manifests novos. Agora escrevefile: "package.json"(o default do serde) mesmo quandosource: git-commit. Previne o footgun se o operator editarsourcedepois.
Como atualizar
runner self-update
runner -V # confirma 2.30.11
runner deploy <app> --force # tenta de novoBugs fechados
- Report do customer Agency-Arms: deploy de sweepstake-backend (Laravel) falhava em version detection independente do
version.sourceconfigurado.
[2.26.10] - 2026-05-22 — Bug 19 layer 2: wizard preserva `${VAR}` literal
Fecha o caso de borda final do Bug 19, surfaceado durante validacao end-to-end no tester.
Modificado
expand_compose_varsno wizard mapper — bare${VAR}agora preserva o literal quando shell env nao tem o valor, em vez de substituir por string vazia (o quedocker composefaz no parse-time). Razao: runner tem ciclo de vida por-app (runner env set/runner secrets setsetam valores APOS a geracao do manifest). Preservando o literal, a runtime expansion (deploy-time, v2.26.9) tem chance de substituir contra.env/.secrets. Pre-fix, reconcile destruia esses literais (viravaargs: K: '') e o silent miscompile do Bug 19 voltava.
Divergencia da spec docker-compose
docker compose substitui empty pra vars unset (parse-time strict). Runner agora preserva o literal nesse caso. Justificado pelo ciclo de vida por-app que docker compose nao tem. Bake-in continua funcionando: shell env setado antes de runner wizard --from-compose ainda e substituido no manifest.
[2.26.9] - 2026-05-22 — Bug 19: `build.args` propagation + `${VAR}` expansion
Fecha Bug 19 reportado pelo Agency-Arms 24h apos o cutover do meet completar: bundles Vite/Next/CRA buildados sem os VITE_* build-args, app crasha no browser (Invalid supabaseUrl).
Adicionado
- Wizard expansion em
build.args(mapper.rs::map_image_or_build) — aplicaexpand_compose_vars()em cada valor debuild.args, paridade com a v2.26.3 (Bug 11) que ja cobriaenvironment:. Novos manifests gerados pelorunner wizard --from-composesaem com${VAR}substituido pelos valores do shell env. - Runtime expansion em
build.args(container.rs::start_container) — apos a resolucao de{{::Var}}existente, aplica tambemexpand_compose_vars_from_app()(helper novo emenvironment::) para substituir${VAR}contra.secrets > .env > shell env. Manifests committados pre-v2.26.9 (com literais${VITE_*}) sao expandidos transparentemente no deploy.
Precedencia de resolucao em build-time
{{::Var}}→ estado do runner (.env/.secrets/CLI overrides) — pre-existente.${VAR}→.secrets>.env> shell env — novo em v2.26.9.- Literal — passthrough.
Por que dois layers
Wizard expansion = paridade com environment: (Bug 11 pattern). Runtime expansion = lifecycle por-app: operador pode setar valor via runner env set APOS gerar manifest, e o deploy pega.
[2.26.8] - 2026-05-21 — Orphan bundle auto-recover + git fetch token
Fecha os 2 ultimos workarounds manuais que apareceram no cutover end-to-end do meet. Combinado com a v2.26.7, o ciclo runner add + secrets set + reconcile + deploy agora roda 100% scriptado — sem rm, sem git remote set-url, sem sed.
Corrigido
- Orphan
secrets.encauto-rename —try_restore_from_repo(chamado pelorunner add) agora renomeia o bundle indecifravel prasecrets.enc.orphan-<timestamp>quando a ckey nao bate. Pre-fix, o bundle ficava no lugar e as proximas chamadasrunner secrets set/generate/pastefalhavam comaead::Error(Bug 2 v2.26.1 so deixou a mensagem descritiva, sem auto-recovery). Plaintext.env/.secretsficam intocados. Operator pode recuperar renomeando de volta se achar a ckey original. git fetchpropaga token viaGitAuthHelper—fetch_to_pull(Phase 0 do deploy) efetch_single_app(CLIrunner fetch, caminho legado comapp_path/.git) agora usamgit_fetch_secure(auth, ...)que injetaGIT_ASKPASS. Pre-fix, opull/.git/configtinha o placeholderhttps://x-access-token@github.com/...sem password real →GIT_TERMINAL_PROMPT=0fazia fetch de repo privado falhar silenciosamente. Resolucao de token: state.github_token → decrypt → fallbackGITHUB_TOKENenv.
Adicionado
git_fetch_secure(auth, repo_path, branch)empipeline/prepare.rs— variante autenticada dogit_fetch. Use em todos os call sites de fetch contra remote privado.
[2.26.7] - 2026-05-21 — Manifest-vs-mapper drift check (Bug 12 final fix)
Fecha a classe inteira de "Bug 12 reentry" (operator deploya container em network errada porque manifest esta stale relativo ao wizard-answers.yml). 7 tentativas de cutover do meet expuseram este caso recorrente.
Causa raiz
O drift check existente (check_compose_drift_or_halt) so comparava o hash do compose service contra o stored hash do manifest. Isso pega quando docker-compose.yml muda estruturalmente, mas NAO pega quando:
wizard-answers.ymlmuda (project_prefix adicionado, system retypado, domain editado) e o compose service fica byte-identico.- Manifest foi committado por uma versao antiga do runner (pre-v2.26.3, antes do project-prefix) e operator nunca rodou reconcile.
Resultado real: cutover do meet teve manifest com networks: [meet] (pre-prefix) committado no upstream + wizard-answers.yml com project_prefix: meet. Mapper produziria [meet_meet] mas o compose-hash batia → container caia em meet (errado, sem alias entre services).
Adicionado
check_manifest_matches_mapper()roda apos o compose-hash check existente. Re-rodamap_servicecom owizard-answers.ymlatual, compara cada campo mapper-derivado doInstanceDeployConfigcom o que esta no disco, halta com diff por campo + comando exato de fix.
Exemplo de halt
Error: manifest drift detected for service 'agent' — re-running the wizard mapper
with the current `wizard-answers.yml` would produce a DIFFERENT `.deploy.yml`
than what's on disk.
Fields divergentes (current → expected):
port:
current: 8787
expected: 80
networks:
current: ["meet"]
expected: ["meet_meet"]
network_aliases:
current: {}
expected: {"meet_meet": ["agent"]}
environment:
(... lista completa de chaves ...)
Fix: runner reconcile meet-agent --applyCampos checados (27)
project, system, app_type, image, build, port, networks, network_aliases, volumes, ports, environment, expose, dns, tmpfs, devices, gpus, platform, entrypoint, command, hostname, init, stop_signal, stop_grace_period, pull_policy, resources, security, logging.
Campos explicitamente NAO checados (operator-customizable)
secrets, config_templates, hosts, env_sync, source_dir.
Tests
570/570 verde, com 2 testes novos reproduzindo o cenario meet:
halts_when_manifest_has_pre_prefix_network_but_answers_has_project_prefix— asserta halt + diff mencionanetworks+ comandorunner reconcile.passes_when_manifest_matches_mapper_output— asserta zero false-positive quando manifest = mapper output.
[2.26.4] - 2026-05-21 — Multi-deploy lifecycle (Bugs 12 re-open + 14, 16, 17)
Fecha bugs do ciclo "reconcile → deploy → reconcile" que apareceram durante a 4a tentativa de cutover do meet (Agency-Arms). Reportado em /opt/runner/BUGS_REPORT_meet_v2.26.3_attempt_2026-05-21.md.
Corrigido
Bug 12 (re-open) —
runner reconcile --applyagora sobrevive aogit reset --hardque o proximorunner deployfaz empull/. Pos-reset, seapp_path/.deploy.ymltemstructure_hash_v2que bate com o fingerprint do compose atual, ele e copiado de volta sobrepull/<manifest>antes do refresh. Hash-gated: updates legitimos no upstream (operador commitou novo manifest) continuam ganhando.Sintoma pre-fix: numa cadeia de deploys multi-service (livekit → agent → frontend), o primeiro service entrava na network correta (
<prefix>_<net>) mas os subsequentes pegavam a network velha (<net>sem prefix) porque o reconcile deles tinha sido desfeito pelo fetch.Bug 14 —
runner deploynao limpa maispull/no fim. Pre-fix, "Cleaned up pull/ directory" rodava sempre, quebrando o proximorunner reconcile(que precisa depull/pra ler o compose). Loop: deploy → pull gone → reconcile fails → fetch bootstraps slowly → repeat. Pull/ agora persiste entre deploys; fetch reusa viagit fetch + git reset(incremental, cheap). Economia de disco (1 shallow clone) nao compensava o custo operacional.Bug 17 — Quando um first-deploy falha, o container quebrado e removido ANTES do
runner deployretornar erro. Pre-fix, ficava UP segurando portas e bloqueava rollback prodocker compose up(Bind for 0.0.0.0:PORT failed: port is already allocated). Removal e best-effort (warn-only se falhar).
Adicionado
runner deploy --force-unlock(Bug 16) — remove.deploy.lockmanualmente quando a stale-detection (PID gone OR > LOCK_MAX_AGE) nao dispara. A mensagem de erro do lock ja apontava pra essa flag desde versoes anteriores; agora ela existe de fato.
Cascata (Bug 15)
Bug 15 ("livekit-agents Invalid URL") deve sumir com Bug 12 re-open fixado — provavelmente era DNS fail mascarado pela lib do agent. Sera revalidado na proxima tentativa.
Bugs fechados
/opt/runner/BUGS_REPORT_meet_v2.26.3_attempt_2026-05-21.md— Bugs 12 (re-open), 14, 16, 17. Bug 15 marcado pra revalidacao.
[2.26.3] - 2026-05-21 — Multi-service compose unblock (Bugs 9–13)
Fecha o cluster de bugs que travava cutover de monorepos com docker-compose multi-service. Reportado em /opt/runner/BUGS_REPORT_meet_v2.26.2_attempt_2026-05-21.md (tentativa 3 de cutover do projeto Agency-Arms meet).
Adicionado
command:no schema do.deploy.yml(Bug 10) — aceita Vec<String> ou shell-string. Wizard--from-composetraduzcompose.services.X.commandautomaticamente. Container pipeline passa prodocker runapos entrypoint:command: - --config - /etc/livekit.yaml - --bind - 0.0.0.0- Expansao compose-spec de variaveis (Bug 11) — wizard expande
${VAR},${VAR:-default},${VAR-default},${VAR:?error}lendo do shell env, exatamente comodocker composefaria no parse. Pre-fix, a string literal${AGENT_PORT:-8787}chegava no container e quebrava parsers que esperavam tipos primitivos. - Networks compartilhadas por
project_prefix(Bug 12) — wizard project-scopa networks user-defined (meet→<prefix>_meet, igualdocker compose), com service-name registrado como DNS alias em cada network compartilhada. Apps do mesmo wizard run resolvem-se por nome curto. Networks reservadas (mysql,redis,postgres,mongo, ...) e built-ins (public,bridge,host) passam unchanged.
Modificado
- Precedencia env:
.env/.secrets(estado operador, local) agora sobrescrevemenvironment:(config-as-code, manifest) (Bug 13). Pre-fix,runner env setera no-op para qualquer chave declarada no manifest porque-e KEY=VALsempre venceu--env-file. Manifest agora vale como default; operador override em prod ganha. runner fetchresolve repo de state OR manifest (Bug 9, re-open) — appstype: imagegerados pelo wizard frequentemente omitemgithub:no manifest (a imagem nao precisa de clone no build).--repopassado aorunner addescreve emstate.repo, mas fetch ignorava isso e retornavano_gitem silencio. Agora fetch resolve repo destate.repoOR.deploy.yml::github.repo(state ganha).
Bugs fechados
/opt/runner/BUGS_REPORT_meet_v2.26.2_attempt_2026-05-21.md— Bugs 9 (re-open), 10, 11, 12, 13
[2.26.2] - 2026-05-21 — source_dir + volume + fetch fixes (Bugs 7/8/9)
Fecha bugs descobertos na 2a tentativa de cutover do meet. Reportado em /opt/runner/BUGS_REPORT_meet_v2.26.1_attempt_2026-05-21.md.
Modificado (breaking-ish, mas matches compose semantics)
source_dir: nullagora usapull/inteiro (Bug 7) — pre-fix, image-mode apps comsource_dir: nulldisparavam auto-detect que adivinhava errado (escolheudocs/num repo com markdown na pasta, deixandosrc/<v>/sem o codigo). Default agora = repo root, matching docker-compose. Heuristica antiga continua disponivel viasource_dir: auto.
Corrigido
- Volume bind path resolve a
source_path(Bug 8) —./livekit.prod.yaml:/etc/livekit.yamlagora aponta prasrc/<instance>/<version>/livekit.prod.yaml(onde o source copy mora) quando o arquivo existe ali, com fallback praapp_pathpara runtime state dirs (./data,./logs). Pre-fix, todos os caminhos relativos resolviam aapp_pathque esta vazio pos-copy — container recebia mount vazio. - Fetch fallback pra
GITHUB_TOKENenv var (Bug 9, parcial) — quando state token e ausente ou indecifravel (apps registrados com--insecure, ou apos rotacao de mkey), fetch agora usaGITHUB_TOKENdo env como fallback. Hint acionavel ao falhar:runner edit <app> --token <pat>.
[2.26.1] - 2026-05-21 — Reconcile bug fixes (Bugs 1–5)
Fecha 5 bugs reportados em /opt/runner/BUGS_REPORT_reconcile_migration_2026-05-21.md durante 1a tentativa de cutover do meet (Agency-Arms).
Corrigido
- Bug 1:
runner add --manifest-path agent/.deploy.ymlagora validabuild.dockerfilerelativo ao subdir do manifest, nao ao clone root. - Bug 2:
runner secrets setem bundle orfao (ckey rotacionada) agora imprime erro descritivo com 2 caminhos de recovery em vez deaead::Errorcru. - Bug 3:
runner fetchfaz clone inicial depull/quando ausente (apps em estadono_gitapos--insecureou falha deinitial_pull_clone). - Bug 4 (critical):
runner reconcile --applyagora escreve em ambosapp_path/.deploy.ymlepull/<manifest>, erefresh_manifest_from_pullfaz skip quando os bytes batem. Pre-fix, o reconcile era silenciosamente desfeito pelo proximo deploy. - Bug 5 (critical):
runner reconcileagora detecta a lista de compose files usada na geracao viawizard-answers.yml::compose_files. Apps gerados comwizard --from-compose -f a -f bre-mergem automaticamente sem operador passar-fde novo.
Adicionado
WizardAnswers.compose_files: Vec<String>— persistido pelo wizard, lido por reconcile + drift check.
[2.26.0] - 2026-05-20 — Multi-file compose + fail-soft wizard + `--ignore-eccentric`
Fecha os bugs do report /opt/runner/BUGS_REPORT_compose_2026-05-20.md que sobraram apos v2.25.1:
- Bug 1 (multi-file compose) —
runner wizard --from-composeagora aceita-f file.yml -f override.yml, com semantica de merge identica aodocker compose(override last wins, suporta!override,!reset). - Bug 2 (HALT total) — Halt eh agora per-service por default (fail-soft). Service
riskycomprivileged: truenao bloqueia mais o manifest do serviceclean. Flag--strictrestaura o comportamento halt-all-or-nothing da v2.25. --ignore-eccentric— escape hatch para "eu sei o que estou fazendo, gera mesmo assim". Aceita lista comma-separated de field names ouall.
Adicionado
-f / --compose-file <PATH>repeatable na CLI.runner wizard --from-compose -f base.yml -f prod.ymlmescla per compose-spec antes da discovery.- Auto-detect de
docker-compose.override.{yml,yaml}ao lado da base: se existir e nenhum-ffor passado, o override é auto-mesclado com notice em stderr ("Detected override: X — auto-merging (pass -f explicitly to skip)"). - Engine de merge (
wizard/compose/merge.rs): deep merge de maps, concat de sequencias por default, replace com!override, drop com!reset, scalars later-wins. --ignore-eccentric <fields>— fields comoprivileged,sysctls,group_addouall.--strict— restaura halt-all-or-nothing.- ComposeRunResult ganha
eccentric_halts: Vec<(String, Vec<EccentricFinding>)>populado mesmo quando manifests sao gerados (relata os services pulados).
Modificado
- Halt fail-soft por default: comportamento previo (v2.25) era halt-all-or-nothing — qualquer service com eccentric finding bloqueava o batch inteiro. Agora services com halts sao listados em
eccentric_haltse os services limpos seguem para a geracao normal. Migration path: quem dependia do comportamento antigo (ex: CI que verificava 0 manifests on halt) adiciona--strict. ComposeRunOptions.compose_file: Option<PathBuf>esta deprecated (mantido para compat interna). Usecompose_files: Vec<PathBuf>ou deixe vazio pra auto-detect.
Compatibilidade
- v1 fingerprint baseline test inalterado.
- 6 fixtures de regressao v2.24 inalterados.
- Single-file compose (sem
-f, sem override sibling) → output identico a v2.25.x. - Apps geradas em v2.25.x com
structure_hash_v2: continuam funcionando, drift check inalterado. - 562 tests passam (+21 sobre v2.25.1).
Validacao
Bug report repro (operator Agency-Arms, repo meet):
agentcomdeploy.resources.limits.{cpus, memory}+livekit-server+frontend(definido apenas no override) → 3 manifests gerados apos v2.26.0 (era 0 em v2.24.2).
[2.25.1] - 2026-05-20 — Wizard nao halta mais em cap_add/cap_drop/devices/deploy.resources
Fix critico: o eccentric check do runner wizard --from-compose ainda estava haltando em cap_add, cap_drop, devices e deploy.resources — campos que agora sao suportados nativamente pelo .deploy.yml em v2.25.0 (via security.cap_add, security.cap_drop, devices, resources + gpus). O check legado bloqueava o uso real da feature.
Corrigido
- Removidos da lista de halt:
cap_add,cap_drop,devices,deploy.resources. - Tests atualizados pra refletir o novo comportamento.
- Continuam haltando (corretamente nao suportados):
privileged,sysctls,network_mode: host,pid/ipc/uts/userns_mode,group_add,extra_hosts,profiles,extends,deploy.replicas,deploy.placement.
[2.25.0] - 2026-05-20 — Compose translator catches up to v2.24.0
runner wizard --from-compose agora traduz os 30+ campos novos da v2.24.0 que estavam sendo silenciosamente descartados.
Adicionado
- Parser: leitura de
mem_limit,mem_reservation,memswap_limit,cpus,cpu_shares,cpuset,pids_limit,shm_size,ulimits(formassinglee{soft, hard}),read_only,security_opt,logging.driver/logging.options,hostname,dns,expose,stop_signal,stop_grace_period,pull_policy,platform. Bloco swarmdeploy.resources.{limits,reservations}agora estruturado em vez de opaco. - Mapper: tradução completa pros blocos novos do
.deploy.yml. Precedência: swarmdeploy.resources.limits.{memory,cpus}vencemem_limit/cpuscurtos quando ambos presentes. no-new-privileges:trueemsecurity_opté roteado pro campo boolsecurity.no_new_privileges, não duplicado emsecurity_opts.- GPU:
deploy.resources.reservations.devices.driver: nvidiamapeia pragpus: all(ougpus: device=Nquandocountnumérico).
Fingerprint v2
- Novo
structure_hash_v2no headerx-runner-source, substituindostructure_hashem manifestos gerados pelo wizard a partir do v2.25.0. - Apps antigas com
structure_hash(v1) continuam usando o algoritmo v1 — zero risco de drift halt falso-positivo após upgrade. - v2 captura presença de cada bloco novo como booleano estrutural. Valores (ex:
memory: 256mvsmemory: 512m) NÃO entram no hash, seguindo o princípio v1 de "estrutura entra, tuning sai". check_compose_drift_or_haltfaz dispatch automatico: lêstructure_hash_v2se presente, senão cai prostructure_hashlegado.
Migration
Apps geradas com v1 hash são upgradeadas naturalmente quando o operador roda runner reconcile <app> --apply. Output mostra fingerprint: upgrading v1 → v2 (new v2.24 fields now tracked). Sem urgência — apps v1 funcionam indefinidamente com o algoritmo legado.
Compatibilidade
- Suite de regressão lockando v1 fingerprint contra fixture representativa garante zero regressão no algoritmo legado.
- Apps
.deploy.ymlautoradas à mão (semx-runner-source): inalteradas. - 541 tests passam (+17 sobre v2.24.2): parser, mapper, fingerprint v2, dispatcher.
[2.24.2] - 2026-05-20 — Help do CLI limpo
Patch UX: runner -h agora mostra uma linha curta por comando em vez de despejar exemplos e parágrafos. Os exemplos continuam disponíveis via runner <cmd> --help.
Modificado
- Cada variante de
Commandsemcli/mod.rsganhou um docstring de uma linha como primeira linha, seguido de blank line, depois detalhes/exemplos. Clap usa a primeira linha em-he o bloco inteiro em--help. --insecureglobal flag tambem ganhou linha curta no topo.- Comandos sem nenhum docstring (
add,deploy,rollback,template,validate,upgrade,logs,stage,generate-config,instances,versions,weights,weight,promote,destroy,cleanup,migrate,canary,mcp,ports) ganharam descricao.
Antes vs Depois
Antes:
init Examples: runner init runner init --set github.default_user=devborlot --set paths.apps_dir=/data/apps ...Depois:
init Initialize runner configuration (config.yml + master key)Exemplos preservados em runner init --help.
[2.24.1] - 2026-05-20 — runner inspect-args
Patch: novo comando runner inspect-args <app> imprime o vetor de args que seria passado pro docker run no próximo deploy, sem executar nada. Read-only.
Adicionado
runner inspect-args <app>— preview dos args do docker run. Útil pra validarresources/security/logging/...da v2.24.0 antes de pushar pro dist branch.- Flag
--instance <name>pra forçar instance específica. - Flag
--jsonpra output como JSON array.
Uso
runner inspect-args meu-app
runner inspect-args meu-app --instance staging
runner inspect-args meu-app --jsonInternamente reusa a mesma função (preview_run_args) que a suite de regressão usa pra lockar goldens — operador vê exatamente o que o runner emitiria.
[2.24.0] - 2026-05-20 — Container resources, security, logging & lifecycle
Release minor com pacote grande de controles que faltavam no .deploy.yml. 100% backward compat — defaults preservam byte-a-byte o comportamento da v2.23.x. Apps em produção sem alteração no .deploy.yml produzem docker run idêntico.
Adicionado
resources:—memory,memory_swap,memory_reservation,cpus,cpu_shares,cpuset,pids_limit,shm_size,ulimits(map). Limita RAM, CPU, PIDs.security:—user,read_only,no_new_privileges,cap_add,cap_drop,security_opts. Hardening de container.logging:—driver(json-file/journald/syslog/none) +options(max-size, max-file). Para rotação de logs em produção.- Lifecycle —
init(defaultfalse),entrypoint,stop_signal,stop_grace_period,pull_policy(ausente = daemon default). - Network avançado —
hostname,dns,expose,network_aliases(map network→aliases). - Storage / Hardware —
tmpfs,devices,gpus,platform.
Compatibilidade
- Schema parsing: todos os campos novos com
#[serde(default)]ouOption<T>..deploy.ymlantigo carrega sem erro. - Runtime: ausente = sem flag emitido. App registrada hoje deploya idêntica a v2.23.x.
- Suite de regressão lockando args do
docker runde 5 fixtures de produção (warmd-front, warmd-sys, warmd-worker, ccs-scanner, minimal). Toda task de Wave A/B teve que manter goldens inalterados. wizard/compose/fingerprint.rsintacto — apps geradas viarunner wizard --from-composemantémstructure_hashestável (sem drift halt).
Side effect determinístico
Labels e env vars no docker run agora são emitidos em ordem alfabética (eram em ordem aleatória de HashMap). Docker não liga pra ordem, mas o output ficou determinístico — útil pra debug e diff.
Validação em campo
Smoketest: deploy de warmd-front com binary v2.24-dev. docker inspect HostConfig diff vs v2.23.20: vazio (exceto pela ordem de 2 bind volumes, causada por estado do disco entre deploys, não pelo binary).
[2.23.20] - 2026-05-20 — Auto-prune de dangling images + build cache
Bug em campo: servidores com muitos deploys em modo build: (Dockerfile) acumulavam imagens <none> (camadas intermediarias) e build cache do buildkit indefinidamente. Um docker system prune manual recuperou 23GB de um servidor. O cleanup_old_versions que ja rodava no phase_7 removia o {project}:{version} tagueado mas nao tocava em dangling/cache.
Adicionado
docker.auto_prune_dangling: boolnoconfig.yml(defaulttrue). Apos a remocao das versoes antigas em appsbuild:, rodadocker image prune -f. So remove untagged<none>sem container associado — seguro.docker.auto_prune_build_cache: stringnoconfig.yml(default"72h"). Apos build-mode deploys, rodadocker builder prune -a -f --filter until=72h. Aceita"72h","168h","7d","all", ou vazio (desliga).- Logs
Pruned dangling images — Total reclaimed space: XGBePruned build cache (until=72h) — Total: ...quando ha algo a remover.
Comportamento
Apps em modo image: (registry pull) nao sao afetadas — nao geram cache nem dangling local. So apps com build: no .deploy.yml disparam o prune.
Equivalencia manual
# Equivalente do que roda automatico em build-mode deploy:
docker image prune -f
docker builder prune -a -f --filter until=72hQuem prefere controlar manualmente: auto_prune_dangling: false + auto_prune_build_cache: "" desligam e voce roda runner cleanup --images --build-cache 72h quando quiser.
[2.23.19] - 2026-05-19 — Bugfix triple no fetch reporter
Tres bugs identificados em campo no fetch --json/cron:
Corrigido
current_version: nullem apps deployed —build_no_git_resultemfetch/resolve.rshardcodavacurrent_versionecontainer_versioncomoNone. Apps healthy reportavam null no log do cron pra ambos os campos, mesmo comrunner listmostrando a versao correta. Agora popula dostate.current_versione viadocker inspect(get_running_container_version).version config: cannot read '/data/apps/<app>/': Is a directory—extract_version_with_diagnosticemfetch/git.rsusavapath.exists(), que aceita diretorios. Quandoversion.fileno.deploy.ymlera vazio ou., oapp_path.join("")resolvia pro proprio diretorio e ofs::read_to_stringquebrava. Agora rejeita early com erro claro e usapath.is_file()em todos os candidate paths (defense-in-depth).- Apps em error loop infinito — quando um repo era removido do GitHub (caso
print), o runner tentavagit ls-remotea cada 5min e falhava sem nenhuma acao corretiva. Adicionado contadorconsecutive_fetch_errorsno state que incrementa em cada erro de fetch e zera em sucesso. Quando cruza o thresholdrunner_config.max_consecutive_fetch_errors(default 10), o app virastatus: pausede e skipado pelo cron.
Adicionado
runner unpause <app>— clear do estado pausado. Resetaconsecutive_fetch_errorselast_error, volta status praactive. Use depois de corrigir o problema upstream (repo movido, network voltou).max_consecutive_fetch_errorsnoconfig.yml— threshold global pra auto-pause. Default 10. Set0desabilita.erroragora populado emno_gitresults — quando ols-remotefalha, a mensagem de erro aparece noerror:do FetchResult e emstate.last_error(antes ficava so no log viawarn!).
[2.23.18] - 2026-05-19 — Env sync: remoto soberano com hash check
O secrets.enc do repositorio agora e a fonte de verdade para .env/.secrets. Se o hash do secrets.enc remoto (em pull/.runner/secrets.enc) diferir do local, o runner decripta e sobrescreve .env/.secrets automaticamente durante o deploy.
Isso resolve o cenario onde secrets sao atualizados em outro servidor (ou via runner secrets set remoto) e o servidor local ficava com .env desatualizado indefinidamente.
Adicionado
env_syncno.deploy.yml(per-app): controla se.env/.secretssao regenerados a partir dosecrets.encremoto.remote(default): remoto e soberano — hash diferente = regenera.env/.secretsskip: local e soberano — nunca sobrescreve.env/.secretspor mudanca remota
env_sync_defaultnoconfig.yml(global): define o default para todas as apps no servidor. Util para servidores de staging onde.enve ajustado manualmente.- Phase 1.4 (
sync_secrets_from_repo): novo passo no pipeline entre template rendering e env provisioning. Compara SHA256 dosecrets.encremoto vs local.
Comportamento
env_sync |
secrets.enc mudou? |
Resultado |
|---|---|---|
remote |
Sim | Decripta e sobrescreve .env/.secrets |
remote |
Nao | Nenhuma acao |
skip |
Sim ou Nao | Nenhuma acao (log informativo) |
Precedencia
- Per-app
env_sync: skipno.deploy.ymlsempre vence - Se per-app e
remote(default/omitido), usaenv_sync_defaultdoconfig.yml - Se ambos omitidos, comportamento e
remote(retrocompativel com v2.23.17 para apps semsecrets.encno repo)
Nota
.deploy.yml sempre sincroniza do remoto independente do env_sync — ele controla o contrato do deploy (portas, networks, healthcheck) e deve estar sempre atualizado.
[2.23.17] - 2026-05-19 — Fix: `phase_8_push_secrets` usa GIT_ASKPASS pro auth
Bug em campo (deploy warmd/sys+worker, prod): o último passo do pipeline (phase_8_push_secrets, sincronia opcional do .runner/secrets.enc de volta no repo quando o plaintext hash muda) chamava git push sem GIT_ASKPASS. Resultado:
[7/7] [ROUTING] Configurando Traefik...
Password for 'https://x-access-token@github.com':
WARN git push failed: remote: Invalid username or token.
fatal: Authentication failed for 'https://github.com/devborlot/warmd-sys.git/'Git abria prompt interativo, GitHub rejeitava ("Password authentication is not supported for Git operations"), o phase falhava com warn (não-fatal) e o secrets.hash nunca era escrito. Resultado: todo deploy futuro tentava push de novo e falhava igual.
Fix
commit_and_push_secrets:
- Recebe
state_dircomo parâmetro (phase_8_push_secretspassa viaRunnerConfig). - Carrega state file do app, decrypta
github_tokenvia mkey. - Constrói
GitAuthHelpercom esse token e chamaauth.git_command(&["push"])em vez deCommand::new("git").args(["push"]). - Sem token disponível: pula com warn sugerindo
runner secrets push <app>manual.
Reusa a mesma infra de auth de bootstrap_push e runner secrets push — askpass via env var, token nunca em ps/git-config.
Impacto
- Deploys pre v2.23.17 já completados continuam funcionais (containers rodando, Traefik configurado). Só o sync do bundle ficou pendente.
- Após
runner self-update, o próximorunner deploy --forceconsegue completar phase_8 sem precisar de intervenção manual.
[2.23.16] - 2026-05-18 — Wizard preserva ordem do `.deploy.yml` nos prompts
Patch UX: o wizard interativo (runner add em modo interativo) agora apresenta os prompts de environment: e secrets: na mesma ordem que aparecem no .deploy.yml. Antes saía em ordem aleatória do HashMap.
Bug
HashMap<String, String> no Rust randomiza ordem de iteração (anti hash-flooding). Quando o wizard fazia for (key, value) in environment pra emitir os prompts, as chaves saíam embaralhadas. Operador escrevia:
environment:
BASE_DOMAIN: "{{::Base Domain?warmd.mundexico.com}}"
MYSQL_HOST: "{{::MySQL Host?mysql}}"
MYSQL_PORT: "{{::MySQL Port?3306}}"
MYSQL_USER: "{{::MySQL User?warmd}}"
MYSQL_DATABASE: "{{::MySQL Database?warmd}}"
REDIS_HOST: "{{::Redis Host?redis}}"
REDIS_PORT: "{{::Redis Port?6379}}"
REDIS_DB: "{{::Redis DB?4}}"
ADMIN_USER: "{{::Admin User?admin}}"E o wizard pedia em ordem aleatória (MySQL Host, MySQL User, Admin User, Redis Port, Base Domain, ...). Difícil agrupar mentalmente "agora preencho todos os MYSQL_*, depois os REDIS_*" — cada campo aparecia em local imprevisível.
Fix
environment e secrets em InstanceDeployConfig agora são IndexMap<String, String> (crate indexmap com feature serde). IndexMap preserva ordem de inserção e o serde_yaml respeita a ordem do YAML no deserialize. Resultado: o for...in do wizard itera na sequência exata que o operador escreveu no .deploy.yml.
Outras estruturas (overrides, lookups internos) continuam HashMap — só o source-of-order virou IndexMap.
[2.23.15] - 2026-05-18 — Healthcheck: TCP fallback `/dev/tcp` + Traefik probe respeita `mode` e injeta `hostname`
Patch endereçando 2 bugs críticos reportados em produção (deploy reserva/bolixebackend), ambos família healthcheck.
Fix — TCP healthcheck com fallback `bash /dev/tcp` para Java/JRE Ubuntu
Bug: imagens Java/JRE baseadas em Ubuntu (ex.: eclipse-temurin:17-jre) têm bash mas não têm nc. O healthcheck TCP do runner gerava só sh -c 'nc -z 127.0.0.1 PORT', retornando exit 127 (command not found), container ficava unhealthy eternamente. Família relacionada ao fix de v2.23.12 (Alpine sem bash), agora cobrindo o outro lado: Ubuntu sem nc.
Fix: chain de fallback no comando emitido:
sh -c '(nc -z 127.0.0.1 PORT 2>/dev/null) || (timeout 2 bash -c "exec 3<>/dev/tcp/127.0.0.1/PORT" 2>/dev/null)'Cobertura:
| Base image | nc | bash | Funciona |
|---|---|---|---|
| Alpine (busybox) | ✓ | ✗ | sim, via nc |
| Debian/Ubuntu padrão | ✗ | ✓ | sim, via /dev/tcp |
| eclipse-temurin:17-jre | ✗ | ✓ | sim, via /dev/tcp |
| Python slim | ✗ | ✓ | sim, via /dev/tcp |
| Distroless/scratch | ✗ | ✗ | precisa mode: cmd ou HEALTHCHECK no Dockerfile |
timeout 2 previne hang se /dev/tcp ficar esperando syn-ack indefinidamente.
Fix — Traefik dynamic respeita `mode: tcp` e injeta `hostname` correto
Bug parte a: mesmo com .deploy.yml declarando healthcheck.tcp: 8080, o runner gerava Traefik dynamic com healthCheck.path: /health HTTP. Backend TCP-only respondia 404 ao probe → Traefik marcava DOWN → 503 ao cliente.
Bug parte b: o probe HTTP do Traefik (quando legítimo) usava o nome do container como Host header (ex.: reserva_production_9b5c0b9:8080). Spring Boot/Tomcat são RFC 7230 strict — recusam Host com underscore retornando 400 → service marcado DOWN → 503 ao cliente. Cliente real passava porque vinha com Host: bolixebackend.emploia.com.br (via passHostHeader).
Fix parte a: o pipeline agora usa resolve_mode() em vez de só checar path.is_empty(). Traefik só recebe healthCheck no modo HTTP; tcp/cmd/exit ficam sem probe Traefik (o HEALTHCHECK do container ainda valida internamente).
Fix parte b: adiciona campo hostname ao healthCheck do Traefik dynamic, e o runner injeta automaticamente o domain da instância:
- Single-route: emite
healthCheck.hostname: <domain>. - Multi-route: cada serviço usa o
route.domainpróprio (em monorepo com N rotas, cada uma tem Host diferente). - Canary: ambos stable e canary versions usam o mesmo domain.
YAML gerado (exemplo single-route):
http:
services:
reserva-production-v1-0-3:
loadBalancer:
servers:
- url: http://reserva_production_9b5c0b9:8080
healthCheck:
path: /api/health
interval: 10s
timeout: 5s
hostname: bolixebackend.emploia.com.br # ← v2.23.15[2.23.14] - 2026-05-18 — `secrets push` preserva upstream + compose translator polish
Patch com 3 bug fixes — 1 crítico no secrets push reportado em campo, 2 de polish no wizard --from-compose detectados em field test (adspot).
Fix crítico — `runner secrets push` não sobrescreve mais `.deploy.yml` upstream
Bug: secrets push clonava o repo upstream e cegamente copiava o .deploy.yml local por cima do upstream antes de commitar. Operador que editou o manifest direto pelo GitHub web (ou de outra máquina) tinha suas edições silenciosamente revertidas pela cópia local stale.
Fix: o push agora detecta o estado do upstream:
Upstream tem .deploy.yml? |
Comportamento v2.23.13 | Comportamento v2.23.14 |
|---|---|---|
| Não (bootstrap) | Copia local → seeda repo | Copia local → seeda repo (igual) |
| Sim (operador editou) | Sobrescreve com local (bug!) | Preserva upstream (fix) |
Quando upstream já tem .deploy.yml, secrets push foca estritamente em .runner/secrets.enc. A mensagem do commit reflete o que foi staged:
- Bootstrap:
chore: bootstrap .deploy.yml + encrypted secrets - Push só de secrets:
chore: update encrypted secrets
Edits feitas pelo operador (GitHub web, branch local, outra máquina) sobrevivem.
Fix — `port` extraído de label Traefik quando `ports:` vazio
Containers que só roteiam via Traefik (sem publicar porta no host) tinham services.X.ports vazio no compose → mapper caía no default 80. O bug pegava qualquer Flask/Bun/API que escuta em porta não-80.
Agora o mapper procura traefik.http.services.<id>.loadbalancer.server.port=N em labels: como fallback. Resultado típico:
| Service | port v2.23.13 | port v2.23.14 |
|---|---|---|
| sys (Flask) | 80 | 5000 |
| api (Rust) | 80 | 8080 |
| app (Bun) | 80 | 3000 |
Ports explícitos em services.X.ports: continuam ganhando — o label só preenche quando ports é ausente.
Fix — `project_prefix` resolve paths relativos
runner wizard --from-compose --path . passava ./docker-compose.yml que tem parent() = "." e file_name() == None. O fallback caía em monorepo hardcoded sempre. Agora canonicaliza o path antes:
v2.23.13: project_prefix: monorepo (sempre)
v2.23.14: project_prefix: <nome real do dir cwd>Ainda pendentes
- Extração de
domainde labels TraefikHost(X)— precisa parser de rule mais cuidadoso (casos compostosHost(a) || Host(b)). - Heurísticas de
guess_systempra "app" →fronte imagensoven/bun→front.
Ambos são polish — operador interativo preenche via prompts.
[2.23.13] - 2026-05-18 — Compose translator + monorepo multi-sistema
Pacote grande de features focado em monorepos com múltiplos sistemas. Permite registrar N apps do mesmo repo apontando pra .deploy.ymls diferentes em subdirs, com geração assistida via runner wizard --from-compose que traduz docker-compose.yml em N manifestos.
Escopo fundamentado (gravado em pedra)
Runner é deployer single-host Docker. .deploy.yml é a linguagem nativa. Aceita docker-compose.yml como input alternativo via wizard porque o modelo mental coincide. Não aceita Kubernetes, Nomad, ECS — use Helm/Argo/Flux pra K8s.
Feature 1 — `build.target` no schema
build:
context: .
target: production # ← novo, multi-stage Dockerfile
args:
NODE_VERSION: "22"Mapeia 1:1 com docker build --target X.
Feature 2 — `runner add --manifest-path `
runner add --repo user/monorepo --branch main --manifest-path frontend/.deploy.yml
runner add --repo user/monorepo --branch main --manifest-path backend/.deploy.ymlImplica --force-duplicate. Mensagem do erro duplicate_repo agora menciona --manifest-path como opção válida.
Bonus fix: após git pull, o runner agora re-copia o manifest do pull/ pra app_path/.deploy.yml. Pré-fix essa cópia era estática no runner add e mudanças upstream em .deploy.yml eram silenciosamente ignoradas em todos os apps.
Feature 3 — `runner wizard --from-compose`
Tradutor que lê docker-compose.yml e gera N .deploy.yml em subdirs:
cd /path/to/monorepo
runner wizard --from-compose
# Discovery: lista services, classifica (app vs infra)
# Prompts: confirma quais wizardizar + pede domain/system por serviço
# Output: N .deploy.yml em subdirs + .runner/wizard-answers.ymlCobertura prática: ~85% dos compose files reais.
Suportado: image/build (com target/args), command, environment, env_file, ports, volumes (bind), networks, depends_on (entre wizardizados), labels (Traefik), healthcheck.
Halt incondicional (security/correctness): cap_add, devices, privileged, sysctls, network_mode: host, build.secrets, build.platforms, deploy.replicas>1, etc.
Drop com warning (operational tuning): restart, init, tmpfs, build.cache_from/to.
Feature 4 — Drift check pré-deploy
.deploy.yml gerado carrega header x-runner-source com structure_hash. Antes de cada deploy, runner re-calcula hash do service no compose atual e HALT em mismatch — container atual fica rodando intocado.
$ runner deploy monorepo-backend
ERROR: compose drift detected for service 'backend':
stored: sha256:a3f2...
current: sha256:b9c1...
docker-compose.yml mudou estruturalmente desde a geração do manifest.
Container atual continua rodando intocado.
Próximos passos:
1. Inspecionar mudança no compose
2. Regenerar manifest: `runner reconcile <app>`
3. Re-deployar
Feature 5 — `runner reconcile [--apply]`
Re-aligna .deploy.yml com docker-compose.yml atual:
runner reconcile monorepo-backend # dry-run + report
runner reconcile monorepo-backend --apply # escreve manifest
runner reconcile monorepo-backend --json # output estruturadoFluxo: lê manifest atual → carrega compose do pull/ → re-roda mapper com wizard-answers.yml cached → renderiza novo YAML → diff vs atual → aplica (com --apply) ou só reporta.
Limitações conhecidas
- Canary não suportado em manifests compose-derived
docker-compose.override.ymlfora do MVP- Sem 3-way merge se editar
.deploy.ymlà mão — reconcile sobrescreve. Edite o compose. - Named volumes dropam com warning — runner usa bind mounts
477 testes passando.
[2.23.12] - 2026-05-18 — Multi-route + Fix HEALTHCHECK Alpine-compat
Multi-route por instance
Container expondo múltiplos serviços (ex: backend na 8000 + front na 5173) agora pode rotear via Traefik para domínios distintos sem precisar splitar em dois containers. Novo campo routes: no .deploy.yml:
instances:
production:
routes:
- domain: api.cliente.com
port: 8000
- domain: app.cliente.com
port: 5173
path_prefix: /admin # opcional, por-routeBackward compat: domain: top-level continua valendo (vira a primeira route implícita).
Validação: rotas que compartilham domain precisam de path_prefix único.
Healthcheck composto: container HEALTHCHECK probe-a todas as portas, qualquer falha = unhealthy.
Cobertura: file provider, docker_labels e External — todos os três modos.
Limitação: canary não suportado em multi-route.
Fix — HEALTHCHECK quebrava em imagens Alpine
Containers Alpine ficavam eternamente unhealthy com bash: not found (ExitCode 127). Runner usava bash + /dev/tcp que não existem em Alpine. Trocado por wget → curl em sh POSIX:
# HTTP:
sh -c '(wget -q -O /dev/null http://127.0.0.1:PORT/PATH || curl -fsS http://127.0.0.1:PORT/PATH > /dev/null) 2>/dev/null'
# TCP:
sh -c 'nc -z 127.0.0.1 PORT'Funciona em Alpine, Debian, Ubuntu, Rocky, distroless-with-shell. Para imagens distroless sem shell e FROM scratch: declare HEALTHCHECK no Dockerfile próprio.
Recomendação — `localhost` em healthchecks custom
Em Alpine, getent hosts localhost retorna ::1 antes de 127.0.0.1 e wget busybox prefere IPv6. Pra healthchecks que você escreve no Dockerfile ou em healthcheck.cmd: do .deploy.yml: sempre use 127.0.0.1 literal, nunca localhost. O runner já usa 127.0.0.1 em tudo que gera automaticamente.
405 testes passando.
[2.23.11] - 2026-05-18 — Fix REPO VER no `fetch` + `traefik.mode: docker_labels`
Fix — REPO VER reflete versão remota no `runner fetch`
A coluna REPO VER da tabela impressa por runner fetch [--deploy] mostrava a versão deployada em vez da versão que está no remoto (i.e., a versão que o próximo deploy vai publicar). Em apps no layout v1.2+ (sem .git no app root), o fetch só fazia git ls-remote e nunca baixava o version.file do remoto.
Correção: novo helper baixa version.file de raw.githubusercontent.com/{repo}/{commit}/{file} (com Authorization: Bearer quando há token), timeout 5s. Tabela agora prefere new_version/new_commit com fallback pra current_version/old_commit.
Feature — `traefik.mode: docker_labels`
Em /opt/runner/config.yml:
traefik:
mode: docker_labels # antes: local | external | none
network: publicResolve o caso de Traefik em network: host (que não tem DNS Docker). Em vez de escrever dynamic file, emite labels Traefik no container; o provider docker do Traefik descobre o IP via socket Docker. Bonus: routing reage a eventos start/stop do container automaticamente.
Requisitos no Traefik:
providers:
docker:
endpoint: "unix:///var/run/docker.sock"
exposedByDefault: falseE /var/run/docker.sock:/var/run/docker.sock:ro montado no container do Traefik.
Limitação: canary não suportado em docker_labels (weighted service não cabe em labels per-container).
390 testes passando.
[2.23.10] - 2026-05-16 — Quality: handlers do main quebrados em sub-funções
Continuação do refactor da v2.23.9. Cada handler extraído (cmd_debug, cmd_add, cmd_tokens, cmd_ckeys, cmd_edit, cmd_cleanup) foi quebrado em sub-funções nomeadas, levando CCN de cada um abaixo de 30:
cmd_cleanup(CCN 104) → 10 funçõescleanup_*_phase+ helperscmd_edit(58) →edit_apply_*,validate_branch_change,git_checkout_and_resetcmd_ckeys(45) → 1 função por SecretsCommands variantcmd_tokens(38) →cmd_tokens_list/test/revealcmd_add(37) →resolve_add_token/ckey,register_pr_app,find_parent_app_for_repocmd_debug(31) →resolve_debug_target_app/project_instance,cmd_debug_test
Restam 2 CRITICALs (main 517 e cmd_secrets 161) que precisam refactor estrutural maior — deferidos.
385 testes passando.
[2.23.9] - 2026-05-16 — Quality: refactor 12 funções CRITICAL + build perf
Refactor
/proj/security/quality flagou 11 funções com CCN (Cyclomatic Complexity Number) acima de 30 (CRITICAL pela regra do Uncle Bob). Esse release quebra cada uma em fases nomeadas:
- Pipeline:
deploy_phases(CCN 67→<30),deploy(37→<30),start_container(54→<30) - Fetch/Registro:
add_app(CCN 91→<30),fetch_single_app(44→24) - Environment:
provision_env_files(37→<30) - Secrets:
bootstrap_push(39→<30) - Notify:
setup_telegram(33→<30) - Validator:
fmtDisplay impls quebrados em 7 funçõeswrite_* - main.rs: 4510 linhas → 2374 linhas. 8 handlers extraídos (
cmd_secrets,cmd_cleanup,cmd_edit,cmd_debug,cmd_status,cmd_ckeys,cmd_tokens,cmd_add).
Sem mudança de comportamento — 385 testes passando.
Build performance
lto = "thin"+codegen-units = 16no Cargo.toml — cold build multiarch ~46% mais rápido. Binário cresce ~2MB (trade-off aceitável).sccacheintegrado nobuild_multiarch.sh(apenas no build nativo; containers cross ignoram).
[2.23.8] - 2026-05-16 — Security: bump reqwest 0.12 + remove tokio-process
Modificado
Bump
reqwest 0.11 → 0.12. Stack TLS antiga (rustls 0.21 → rustls-webpki 0.101.7) tinha GHSA-82j2-j2ch-gfr8 (DoS via malformed CRL, CVSS 7.5). Agorarustls 0.23 → rustls-webpki 0.103.13(patched). API pública compat — zero mudança nos call sites.Removido
tokio-process 0.2. Crate abandonada que arrastavacrossbeam-utils 0.6.6/0.7.2, vulneráveis a CVE-2022-23639 (CVSS 7.5). Não tinha uso direto no código — substituída há tempos portokio::processbuilt-in.
Resultado SCA
Antes: 4 HIGH (/proj/security scanner score 60, REPROVADO).
Depois: 0 HIGH, score 100, APROVADO.
385 testes — todos passando.
[2.23.7] - 2026-05-16 — State files com permissão 0640
Modificado
State files (
/opt/runner/state/*.yml) agora têm perm0640em vez de0644. Esconde metadata sensível (repo URL, timestamps, encrypted token+ckey) de usuários não-root no host. A criptografia já protegia os valores, mas não há motivo pra deixar metadata aberta.Migração lazy:
load_statecorrige perm de files legacy (0644) pra0640na primeira leitura. Não loosen perms já mais restritivas — operador que manualmente setou0600mantém isso.
Postura de acesso resultante
| Path | Perm | Quem lê |
|---|---|---|
/opt/runner/.keys/master.key |
0600 root:root |
só root |
/opt/runner/state/*.yml |
0640 root:devs |
root + grupo devs |
/data/apps/<app>/.env |
0644 |
qualquer (env público) |
/data/apps/<app>/.secrets |
0600 |
só root |
/data/apps/<app>/.runner/secrets.enc |
herda umask | qualquer (encriptado) |
Forçar migração de todas as apps:
runner status --json >/dev/null
ls -la /opt/runner/state/[2.23.6] - 2026-05-16 — Audit log de acesso a secrets
Adicionado
Audit trail JSONL para operações em
runner secrets. Cadashow/set/generate/delete/paste-encodedescreve uma linha em/opt/runner/logs/secrets-access.jsonl(perm0640 root:devs):{ "timestamp": "2026-05-16T17:58:58Z", "action": "show", "app": "meu-app", "keys": ["DB_PASSWORD", "JWT_SECRET"], "user": "root", "uid": 0, "hostname": "ns3177045", "pid": 1067444 }Garantia: campo
keysregistra apenas nomes, nunca valores. Auditável end-to-end comcat/jqsem vazar secrets. Separado deinsecure-events.jsonl(que registra bypass de security guards) — este cobre acesso legítimo mas sensível.
Análise rápida
jq -c 'select(.action == "show")' /opt/runner/logs/secrets-access.jsonl
jq -r '"\(.timestamp) \(.user) \(.action) \(.app)"' /opt/runner/logs/secrets-access.jsonl[2.23.5] - 2026-05-16 — Fix `secrets delete` da última key
Corrigido
runner secrets deleteda última key falhava comNo .env or .secrets content to encrypt. Causa:write_app_secretstentava encriptar bundle vazio, erunner_core(corretamente) refuse. Agora detecta caso vazio e remove.runner/secrets.encem vez de encriptar zero bytes. Próximosecrets setrepopula normalmente. Multi-key delete (caso comum) já funcionava na v2.23.4.Detectado em teste isolado pós-release da v2.23.4.
[2.23.4] - 2026-05-16 — `runner secrets delete`
Adicionado
runner secrets delete <app> <key>— fecha gap CRUD do subcommand-gruposecrets. Mesmo padrão dockeys delete(v2.23.0). Procura a key em.enve.secrets, remove de onde encontrar, reescreve os 3 stores atomicamente.--force/-y/--yesskipa confirmation. Key ausente é no-op (exit 0).Resolve o resíduo
TEST_CONVERGENCE_KEY=hello-2026reportado em campo após validação da v2.23.3.
[2.23.3] - 2026-05-16 — 7 bugs do field report (deploy emploia)
Após uma sessão de deploy real que levou 3h por causa de bugs do runner, 7 itens críticos do report do operador foram fechados.
Corrigido — críticos
Traefik path validation — antes, deploy retornava
✓ DEPLOY CONCLUÍDOmas o domínio ficava 404 com cert default quandopaths.traefik_dynamicapontava pra dir que o container Traefik não montava. Agora o runner inspeciona os mounts dotraefikcontainer antes do deploy e bail com hint específico:traefik_path_unreachable: container 'traefik' does not see /etc/traefik/dynamic. Detected mount: /var/lib/docker/volumes/traefik_data/_data (host) -> /etc/traefik (container). Fix: runner settings set paths.traefik_dynamic /var/lib/docker/volumes/traefik_data/_data/dynamicStores convergentes em
secrets paste-encoded— antes, encodar secrets viapaste-encodedatualizava só.runner/secrets.enc, mas o deploy lia.env/.secretsplaintext (stale) e sobrescrevia o bundle. Resultado: container subia com tudo vazio. Agora as 3 fontes são escritas atomicamente.build.contexthonrado — antes era silenciosamente ignorado, runner sempre procuravaDockerfileno root e enviava o repo inteiro como contexto. Agoraeffective_context = source/<build.context>e o Dockerfile é resolvido relativo a ele (docker semantics standard).Connection strings auto-detectadas como secret —
DATABASE_URL,DB_URI,POSTGRES_URL,MONGO_URI,REDIS_URL,AMQP_URL,SMTP_URLe variantes agora vão automaticamente pro.secretscifrado em vez de vazarem pro.envplaintext. (BASE_URL/API_URLcontinuam tratados como públicos.)
Corrigido — altos
version.fileaceita subpath confirmado via testes de regressão. Mensagem de erro agora mostra os 4 paths absolutos tentados pra diagnóstico imediato.runner addfaz clone inicial prapull/— antes app ficava emstatus: no_gitpermanente até primeiro deploy. Agora shallow clone (--depth=1) ao final deadd_app, best-effort.
Adicionado
runner tokens reveal <app> --insecure— mirror dorunner ckeys reveal. Audit-logged. Use case: operações git ad-hoc quando scriptando ao redor do runner.
runner tokens reveal meu-app --insecure
runner tokens reveal meu-app --insecure --json | jq .plaintext[2.23.2] - 2026-05-15 — Fix `.gitignore` traps em `secrets push`
Corrigido
secrets pushfalhava em repos com.runner/no.gitignore(template comum). Causa: o helper adicionava!.runner/secrets.enccomo negação, mas em git uma negação não funciona se o diretório parent está ignorado. Agora o runner reescreve.runner/→.runner/*(glob form) antes de adicionar a negação.Auto-fix de proteção plaintext em vez de refusar push — se o repo não tem
.env/.secretsno.gitignore, runner injeta os 4 patterns (.env,.env.*,.secrets,.secrets.*) no mesmo commit do bootstrap, em vez de bailar e pedir pro operador fixar manualmente.Fallback
git add -f .runner/secrets.encse padrões exóticos de.gitignoreainda travarem o staging.
[2.23.1] - 2026-05-15 — `runner secrets push`
Adicionado
runner secrets push <app>— push manual de.deploy.yml+.runner/secrets.encpro repo do app. Usa o token já encriptado no state (zero credentials por flag/env). Resolve o caso "registrei o app, runner gerou os files localmente, mas o repo ainda não tem". Antes da v2.23.1 isso só acontecia após o primeiro deploy bem-sucedido.Encrypted-only por design: whitelist de staging (
.deploy.yml+.runner/secrets.encapenas),.gitignorecheck, refuse se.runner/secrets.encnão puder ser produzido.Auto-bootstrap no
runner add— quando--ckeye token estão disponíveis e--deploynão foi passado, runner tenta obootstrap_pushao final deadd. Failure não falha o add — operador pode retry viarunner secrets push.
[2.23.0] - 2026-05-15 — UX cleanup + ckeys CRUD
Bump menor por novas subcommands. Bugs do feedback do operador, reportados em sessão anterior.
Corrigido
runner addnão cria mais ckey órfã. Antes,runner ckeys add <project>seguido derunner addcriava DUAS ckeys, a primeira ficava órfã sem como remover. Agoraadd_appconsultavault::ckeys::find_for_project()e:- Sem
--ckey+ vault tem uma → herda silenciosamente - Com
--ckey+ vault tem uma → warna + instrui delete
- Sem
runner ckeys delete <id>novo subcommand. Completa o CRUD do vault.runner secrets showheader agora indica source:--- secrets (from .runner/secrets.enc) ---em vez de--- .secrets ---.runner manifest --resolvedrenderiza{{::Var}}no.deploy.ymlusando.env/.secrets/secrets.enc. Templates sem valor ficam como<UNRESOLVED:KEY>.runner secrets set/generate/sharesubstitui opaste-encoded --mode {manual,generate,shared}confuso. Antigo continua funcionando com deprecation warning.runner generate-configagora emitesecrets:block separado em vez de placeholder{{::KEY}}emenvironment:.--yes/-yaliases pra--forceemunregister,destroy,ckeys delete.Mensagem misleading
"pull/ on first fetch"→"on first deploy"(clone só acontece emrunner deploy).
[2.22.10] - 2026-05-15 — Fix deploy-loop em falha pós Phase 1.6
Corrigido
Deploy-loop quando deploy falha após
secrets.encser commitado. Bug de v2.22.6: pipeline encriptava.env/.secretsem Phase 1.6 e já faziagit pushdo.runner/secrets.enc— antes do pre-flight, build e health check. Quando o deploy falhava depois, o commit ficava no remote. O cronfetch --deploydetectava "novo commit", tentava deploy de novo, falhava, commitava… loop infinito.Fix:
persist_to_repoquebrado em dois:write_local_secrets_enc— escreve apenasapp_path/.runner/secrets.enclocalmente (Phase 1.6). Não toca git, então falha posterior não polui o repo.commit_and_push_secrets— copia o blob propull/,git add/commit/push. Roda em Phase 8 (após Phase 7 cleanup), só quando deploy validou.
Plus: hash SHA-256 do plaintext (
.env+.secrets) é comparado contra o último push bem-sucedido (app_path/.runner/secrets.hash). Pula commit se nada mudou — antes o nonce AES-GCM aleatório fazia o ciphertext sempre diferir, gerando commit a cada deploy mesmo sem mudança real.
Sintoma em produção
hogi-worker ficou em loop com 12+ commits chore: update encrypted secrets em ~30min, todos seguidos de deploys falhados pelo mesmo pre-flight check (secrets C6BANK em branco). Após v2.22.10, deploy falha silenciosamente pro repo — operador resolve o problema sem ruído de commits.
Compatibilidade
Recuperação zero-touch (runner add --ckey num servidor novo) continua funcionando idêntico — secrets.enc é gravado localmente em todas as tentativas, só não vai pro remoto enquanto o deploy não validar.
[2.22.9] - 2026-05-15 — Remote operation: restart + container logs
Adicionado
runner restart <app> [-i instance] [--timeout N] [--json]— recycle do container que está servindo a app, sem ir pelo pipeline de deploy. Útil pra pegar mudanças de env (apósrunner env set) ou destravar um processo preso. Resolve o container via state file →current_version→{project_und}_{instance}_{version_und}.runner container logs <app> [-i instance] [-n tail] [--json]— logs do container em execução, sem precisar de SSH +docker logs. Permite operação remota via CCS ou orquestrador. Strip automático dos 8-byte headers do docker log stream.-n alllê tudo.
Motivação
Análise de contrato CCS ↔ runner detectou que o operador remoto não conseguia fazer debug/recycle sem SSH direto ao servidor. Estes 2 comandos fecham o gap pra 90% dos casos de operação remota.
[2.22.8] - 2026-05-15 — `runner deploy-all` top-level
Adicionado
runner deploy-all [--force] [--verbose] [--json]— alias top-level prarunner deploy --all. CCS e outros orquestradores chamamrunner deploy-all --jsonesperando comando explícito; antes o runner só tinhadeploy --alle o CCS quebrava comunrecognized subcommand. Output JSON incluisuccess,deployed,failed,timestamp.
Adicionado (infraestrutura)
tests/ccs_contract_test.sh— script de validação do contrato entre o CCS client e o runner CLI. Testa ~30 comandos que o CCS invoca, validando: existência de subcommands, JSON parse no stdout, exit codes, convenção de flags. Uso:./tests/ccs_contract_test.sh(local) ou./tests/ccs_contract_test.sh ssh production(remoto).
[2.22.7] - 2026-05-12 — `ckeys reveal` + export fixes
Adicionado
runner ckeys reveal <app-or-id> --insecure— comando novo para revelar a ckey plaintext de um app ou ckey id. Gated por--insecure. Aceita nome do app (lê do state file), storage id (ck_<8hex>) ou formatted id (ck_xxxx…xxxx). Use case: recovery quando precisa re-registrar um app num servidor novo mas a ckey ficou só encriptada no servidor velho.
Corrigido
runner ckeys exportagora aceita nome do app. Antes só achava standalone ckeys em/opt/runner/.keys/ckeys/*.json. Apps com ckey legacy no state file retornavam "not found". Fallback adicionado.runner export --include-secrets-sealedretornavanull. Olhava em.secrets.enc(path legacy). Canônico desde v2.10+ é.runner/secrets.enc. Agora tenta os dois.
[2.22.6] - 2026-05-12 — Pre-flight + persist_to_repo fixes
Corrigido
- Pre-flight scanner:
.env/.secretsem/data/apps/{app}/não disparam mais warning. Desde v2.15 esse path é a área de runtime do runner — não é git working tree. Os arquivos são artefatos legítimos gerados pelo runner (passados via--env-fileao docker), não candidatos a commit. Check agora skipa quandoapp_path/.git/não existe. persist_to_repo: ogit add .runner/secrets.encrodava em/data/apps/{app}/(fatal: not a git repository). Agora redireciona prapull/(git working tree real). Auto-propagação desecrets.encpro repo remoto viagit pushfunciona — sem mais step manual de copiar via scp.
[2.22.5] - 2026-05-12 — `--build-cache` default
Corrigido
runner cleanup --images --build-cachesem valor agora usa default72h(alinhado com a doc). Antes falhava com "a value is required" apesar da doc dizer "Without value: prunes cache older than 72h". Aceita: omitido (72h),168h,all.
[2.22.4] - 2026-05-12 — Template resolution: secrets.enc restore + docker --env
Corrigido
- B1:
secrets.encrestore não provisionava templates do.deploy.yml. Quandorunner adddecifravasecrets.encdo repo, restaurava o.envantigo (que podia ter sóPROJECT+INSTANCE) e skipava o env wizard. Agora roda em merge mode: adiciona keys do.deploy.ymlenvironment:que estejam faltando no.envrestaurado, resolvendo templates com defaults. - B2:
docker runinjetava template literal via--env. O pipeline injetava todas as entries deconfig.environmentcomo--env KEY=VALUEnodocker run, incluindo valores com{{::...}}não resolvidos. Como--envtem precedência sobre--env-file, o template literal sobrescrevia o valor correto do.env. Agora entries com{{::são skippadas (mesmo padrão deGENERATE). - C: multi-template começando com
{{::(v2.22.3):"{{::A}},{{::B}}"era parseado como single Prompt full-match. Agoraparse_env_value_specverifica que oinnernão contém outro{{::antes de tratar como Prompt — strings multi-template viramCompositecorretamente.
[2.22.2] - 2026-05-12 — Template resolution no deploy pipeline
Corrigido
runner fetch --deploy(cron) deixava{{::Var?default}}literal no.envporqueprovision_env_filessó resolviaGENERATE, nunca{{::}}. Agora step 5 do provisioning resolve templates com defaults (non-interactive). Exemplo:BACKEND_DOMAIN: "{{::BackendDomain?backend.staging.sweepapex.com}}"gera valor correto no.env.--env "HostBind=X"para template emports:era descartado. Era consumido apenas para resolverenvironment:/secrets:. Se o label não existia como key em nenhum dos dois, era silenciosamente perdido. Agora overrides não consumidos são escritos no.envpara queports:ebuild.args:possam resolvê-los viaresolve_value_from_app_files.
[2.22.1] - 2026-05-12 — Template resolution no `runner add`
Corrigido
runner add --env "TenantSlug=X"com.deploy.ymltendoTENANT_SLUG: "{{::TenantSlug?demo}}"agora resolve corretamente. Antes o lookup era feito só pelo nome da variável (TENANT_SLUG), ignorando o label do template (TenantSlug).- Templates compostos em
environment:/secrets:como"https://{{::Domain?example.com}}/api"agora são expandidos. Antes só templates full-match (valor inteiro ={{::...}}) eram processados — compostos ficavam literais no.env. - Templates em
ports:(ex:ports: ["{{::HostBind?10.108.0.5:80}}:80"]) agora resolvem a partir do.env/.secretsantes de passar ao docker.
[2.22.0] - 2026-05-12 — Native `path_prefix` routing
Resumo
Apps que precisam compartilhar host com outro servico (frontend + sidecar MCP, painel admin sob /admin, API publica sob /api) ganharam suporte nativo via instances.<inst>.path_prefix. Antes da v2.22.0, esse cenario exigia escrever um arquivo Traefik dinamico a mao em /opt/traefik/dynamic/ — o runner so emitia regras Host() puras.
Adicionado
`instances..path_prefix` — co-hospedagem em sub-rota
Quando definido, a regra Traefik vira:
Host(`{domain}`) && PathPrefix(`{path_prefix}`)com priority: 100 (vence o router catch-all do host vizinho).
instances:
production:
domain: wizard.runnerci.com
path_prefix: /mcp
source:
type: dist
keep_versions: 3Caso de uso real (em producao): o frontend wizard serve wizard.runnerci.com/; o MCP server wizard-mcp serve wizard.runnerci.com/mcp/*. Ambos containers separados, mesmo dominio.
Comportamento:
- Vazio/omitido/whitespace-only → regra fica apenas
Host()(legacy, sem priority) - Todos os tres caminhos de geracao Traefik honram o prefix: single-version, canary, snippet externo
Validacao
- 348 unit tests pass (era 343 v2.21.0; +5 do v2.22):
path_prefix_renders_when_set,path_prefix_omitted_when_empty(runner-core schema)test_path_prefix_appends_to_rule_with_priority,test_path_prefix_empty_falls_back_to_host_only(runner-client traefik)path_prefix_round_trips(wizard YAML → runner parser contract)
Backwards compatibility
.deploy.ymlsempath_prefix:→ comportamento identico v2.21.x- Apps em producao com workaround manual (arquivo Traefik hand-edited) podem migrar adicionando
path_prefix:e removendo o arquivo apos o proximo deploy
[2.21.0] - 2026-05-07 — Multi-app follow-ups
Resumo
5 features que completam MVPs/slots deixados em v2.20.0 + 1 UX safety net + 1 doc fix. Construído sobre o feedback de um deploy multi-service complexo em produção.
Mudança comportamento notável: runner add em repo já registrado agora falha com duplicate_repo exit 2. Workaround compatível: --force-duplicate. Documentado abaixo.
Adicionado
A — Interpolation `{{::Var}}` em `build.args:`
v2.20.0 entregou MVP literal (passa valor cru pro --build-arg). v2.21.0 fecha: agora interpola o mesmo {{::Var}} que environment: usa, resolvendo de .env e .secrets em build time.
build:
args:
VITE_SUPABASE_URL: "{{::SUPABASE_URL}}"
VITE_LIVEKIT_WS_URL: "{{::LIVEKIT_WS_URL?wss://default.com}}"Order: .secrets primeiro (mais específico), .env fallback. Suporta {{::Key?default}}. Sem TTY no build → não pede prompt; se var não resolve, erro instrutivo apontando pra runner env set.
B — Healthcheck `depends_on:` cross-app
Resolve o caso multi-app comum: meu-agent não deveria subir antes do meu-livekit estar healthy. Antes era manual (deploys sequenciais com vista nos logs).
instances:
production:
depends_on:
- app: meu-livekit
condition: healthy # healthy | started
timeout: 300s
- app: meu-redis
instance: production # opcional, default = mesma instance
condition: startedComportamento: start_container polla docker inspect por cada dep até condition met OR timeout. Bypass: --insecure ignora. Erro instrutivo: dependency_timeout: <app>/<instance> not ready after Ns.
C — `traefik.external.{snippet, write_to}` — slot ocupado
v2.20.0 entregou só o bypass do write local. v2.21.0 enriquece o slot:
# /opt/runner/config.yml
traefik:
mode: external
external:
write_snippet_to: "/opt/traefik-snippets/{app}.yml"
print_snippet: trueOutput em modo external:
print_snippet: true(default) → snippet em stderr depois do deploy, copy-pasteablewrite_snippet_to: <path>→ também salva em arquivo (Ansible sync){app}placeholder no path expande pro project slug
D — `runner add` detecta repo duplicado
UX safety net pro caso real: operador registrou 2 vezes sob nomes diferentes (meu-app-livekit + meu-livekit). Runner aceitou silenciosamente, gerando state divergente.
runner add --repo usuario/meu-app --branch main --ckey "..." ...
Error: duplicate_repo: Repo usuario/meu-app já está registrado
como `meu-app-livekit` (branch: deploy/livekit, status: registered).
Opções:
--force-duplicate permite criar SEGUNDA app pro mesmo repo
runner unregister meu-app-livekit --force remove a anteriorBypass: runner add --force-duplicate ... (raro; testes A/B, multi-instance experimental).
E — `generate-config` template completo
Operador olhou runner generate-config e concluiu erradamente que ports:/secrets:/type: image não existiam. Template embedded estava incompleto. Agora mostra TODOS os campos válidos do schema, com comentários esclarecendo opcionalidade + version markers (v2.20.0+, v2.21.0+).
Validação
- 340 unit tests pass (era 328 v2.20.0; +12 do v2.21)
- Matriz CI/CD: 82 pass / 0 fail / 71 n/a em ~184s (9 estados × 16 ops)
- 3 ops novas no matrix:
build_args_interp(state B): valida{{::Var}}resolution end-to-enddepends_on(state B): valida wait timeout em dep não-existenteduplicate_repo(state A): validarunner add --repo Xre-aceito falha comduplicate_repo
traefik_external(já em v2.20) ganhou snippet asserts: confirma stderr emit em mode=external
Backwards compatibility
.deploy.ymlsemdepends_on:→ Vec vazio, comportamento idêntico v2.20.x.deploy.ymlcom{{::Var}}embuild.args:agora interpola (era literal em v2.20 — comportamento mais correto, sem quebra esperada)config.ymlsemtraefik.external:→ sub-block opcional, só usado em mode=externalrunner addem repo já registrado muda comportamento: era silently OK, agoraduplicate_repoexit 2. Workaround:--force-duplicate
Backlog v2.22+
- Sol 2 post_deploy_check schema (smoke pós-deploy configurável)
- Service discovery dinâmico (env var templating tipo
${app.endpoint}) - W6 smoke probe (rodar imagem brevemente + detect listen port real)
- UDP-aware allocator (random strategy só pensa TCP hoje)
runner list --orphans(states sem deploy_count > 0)
Backlog v3.0 (acumulando breaking)
- Compose-style multi-service em 1 manifesto
status --jsoncleanup — remover legacy top-level fields
[2.20.0] - 2026-05-06 — Multi-app polish + CCS schema
Resumo
5 features opt-in que fecham fricções identificadas em deploys multi-service reais (LiveKit + Python agent + Vite frontend) + schema split do runtime status --json pra CCS dashboard. Zero breaking changes — apps v2.19.x rodam sem mudança.
Adicionado
A — `build.args:` no `.deploy.yml` (Vite/Next/Angular zero-touch)
build:
context: frontend
dockerfile: Dockerfile
args: # ← NOVO
VITE_SUPABASE_URL: "https://..."
VITE_LIVEKIT_WS_URL: "wss://..."
BUILD_ENV: productionCada entry vira --build-arg KEY=VAL no docker build. Mata o workaround "build local + commit dist/" pra apps com env vars compile-time. MVP v2.20.0: valores literais. Interpolation {{::Var}} (igual environment:) é follow-up — resolve_value helper público ainda não existe.
B — Auto-create docker network
Quando .deploy.yml declara networks: [meet] e a network não existe, runner cria automaticamente antes do docker run. Idempotente. Built-ins (bridge, host, none) são skipped. Consolidado num único path em containers::ensure_network.
C — `traefik.mode: external | none` (bypass)
# /opt/runner/config.yml
traefik:
mode: external # none | local (default) | external| Mode | Comportamento |
|---|---|
local (default) |
Escreve em <traefik_dynamic_dir> (v2.19.x) |
external |
Skip do write local — operador trata roteamento externo |
none |
Mesmo bypass — apps internas sem HTTP público |
v2.20.0 = só bypass. Slot reservado pra ações futuras (snippet output, write_to). Default local preserva backcompat 100%.
D — `status --json` schema split (aditivo)
Payload do runner status <app> --json ganha 3 blocos lógicos:
{
"current_version": "abc1234",
"manifest": { "project": "...", "repo": "...", "branch": "...", "ckey_fingerprint": "ck_...", ... },
"config": { "type": "...", "port": ..., "ports": [...], "networks": [...], "hosts": {...}, ... },
"runtime": { "current_version": "...", "status": "...", "network": { ... v2.19.0 } },
"_deprecated_top_level": true, // marker pra CCS migrar
// legacy top-level fields preserved (project, system, app_type, instances, state)
}CCS dashboard pode migrar gradualmente: data.manifest !== undefined → caminho novo. Cleanup de legados em v3.0 (postergado).
E — `hosts:` map (DNS aliases via `--add-host`)
# .deploy.yml
hosts:
host.docker.internal: host-gateway
internal-api: 10.0.0.5
livekit-pub: 10.108.0.5Cada entry vira --add-host KEY:VAL no docker run. Resolve host.docker.internal em Linux Docker (que não tem por default). Map (não array) — last-wins em duplicatas, override fácil em re-deploy.
Refatorado
containers::ensure_networkganhou skip pra docker built-ins (bridge,host,none). Single source of truth — pipeline não duplica mais a lógica.
Validação
- 328 unit tests pass (era 309 v2.19.0; +19 do v2.20)
- Matriz CI/CD: 79 pass / 0 fail / 47 n/a em ~170s (9 estados × 13 ops)
- 4 ops novas no matrix:
build_args: valida injection em build-mode states (B/D/I)network_autocreate: valida criação automática de network novatraefik_external: valida que mode=external não escreve local filehosts_check: valida--add-hostnodocker inspect
status_schema.pyagora hard check (era permissivo) — falha semanifest/config/runtimefaltarem
Backwards compatibility
.deploy.ymlsembuild.args:→ BTreeMap vazio, zero--build-argflags.deploy.ymlsemhosts:→ BTreeMap vazio, zero--add-hostflagsconfig.ymlsemtraefik.mode:→ defaultlocal→ comportamento idêntico v2.19.x.deploy.ymlcomnetworks: [X]e X não existe → v2.19.x quebrava no docker run; v2.20.x cria- Consumer do
status --jsonlegado → continua lendo top-level (_deprecated_top_level: truemarker indica caminho novo)
Nenhuma quebra. Apps registradas em v2.19.x seguem rodando.
Backlog identificado pra v2.21.0
- Interpolation
{{::Var}}embuild.args:— precisa exporresolve_single_valuecomopubemsrc/environment/mod.rs - Healthcheck
depends_on:cross-app — multi-app ordering - Sol 2 post_deploy_check schema (smoke pós-deploy configurável)
- Service discovery dinâmico entre apps (env var templating)
- Sub-bloco
traefik.external.{snippet, write_to}— ações concretas em external mode - W6 smoke probe (rodar imagem brevemente pra detectar listen port real)
Migration path
Pra apps existentes opt-in:
# Adicionar build.args
echo " args: { VITE_X: ... }" >> .deploy.yml/build/
# Adicionar hosts
echo -e "hosts:\n host.docker.internal: host-gateway" >> .deploy.yml
# Mudar pra external mode
echo "traefik:\n mode: external" >> /opt/runner/config.ymlSem precisar runner unregister ou --force.
[2.19.0] - 2026-05-06 — Port allocation autônomo (Lote B)
Resumo
Single feature focused: eliminar a única intervenção manual restante no CI/CD do runner — escrever ports: ["VPC_IP:porta:porta"] no .deploy.yml. Runner agora aloca porta livre automaticamente, persiste sticky no state file (re-deploys reusam a mesma porta), e expõe via runner status --json pra CCS consumir.
Bonus: pre-flight de secrets (Sol 1 do feedback de prod) — deploy aborta cedo se secret declarado em .deploy.yml::secrets: tem valor vazio (a menos de --insecure). Encerra o ciclo "deploy aparentemente bem-sucedido mas funcionalmente quebrado" causado por healthchecks que retornam 200 mesmo com secrets faltando (ex: Laravel /up).
Adicionado
Port allocation (config + state)
network: block opcional em /opt/runner/config.yml:
network:
vpc_ip: 10.108.0.5 # opcional; sem isso bind cai pra 127.0.0.1
publish_strategy: random # random | sequential | explicit | none
port_range: [8000, 8999]
port_blocklist: []Estratégias:
- random (recomendado): sorteia porta livre na primeira deploy, persiste sticky pra re-deploys reusarem
- sequential: pega a primeira porta livre no range
- explicit: lê
ports:do.deploy.yml(comportamento v2.18.x) - none: container roda sem
-pno host
Sticky lifecycle:
- Primeira deploy: aloca porta → persiste em
state/<app>.yml::network.published_port - Re-deploys (commit novo, fetch --deploy): reusa a mesma porta — Traefik externo permanece válido
runner unregister X: libera porta de volta ao poolrunner reset --hard X: libera state → próximo deploy realocarunner ports realloc X: força nova alocação (operador atualiza Traefik)
Resolução de bind address:
network.vpc_ipconfigurado → bind nesse IP- Sem vpc_ip → bind em
127.0.0.1(safe default — Traefik no mesmo host alcança via loopback)
Comandos novos
runner ports list [--json] # lista portas alocadas em todas apps
runner ports release <app> # libera state.network sem parar container
runner ports realloc <app> # limpa published_port — próxima deploy realoca
runner deploy <app> --port N # override permanente (escreve no state, força reuso)
runner deploy <app> --no-port # desabilita port mapping (strategy=none) só desta deploy`runner init` interativo
Quando rodado em TTY, runner init agora pergunta:
- "Esse server fica em uma VPC interna? [s/N]"
- Se sim: oferece auto-detect via
ip -4 -o addr show, confirma o IP - "Strategy [random/sequential/explicit] (default: random)"
- Persiste o bloco
network:no config.yml
Em modo não-TTY (CI, piped) skipa silenciosamente — backcompat 100%.
`runner status --json` runtime.network
Bloco novo no payload pra CCS consumir:
"runtime": {
"network": {
"vpc_ip": "10.108.0.5",
"published_port": 8473,
"endpoint": "http://10.108.0.5:8473",
"allocation": {
"strategy": "random",
"allocated_at": "2026-05-06T12:30:00Z",
"sticky": true,
"source": "state"
}
}
}Apps legacy sem state.network → runtime.network: null (key presente, valor null).
Pre-flight de secrets (Sol 1 do feedback prod)
Antes de iniciar o container, valida que cada secret declarado em .deploy.yml::secrets: tem valor não-vazio em .runner/secrets.enc (após decifrar). Se vazio:
Error: deploy_blocked: secrets declared in .deploy.yml have empty values: ["APP_KEY"]
Bypass: rerun with --insecure (NOT recommended).
Or fill the values:
runner env set <app> --key APP_KEY --secret --value <value>Bypass via --insecure (operator opt-out). Resolve o caso onde Laravel /up retornava 200 mesmo com APP_KEY vazio, fazendo o runner declarar deploy bem-sucedido enquanto / retornava 500.
Corrigido
- Sticky port resolution não filtrava containers do próprio app:
sticky::resolveconsultavaports_in_use(ss + docker ps) e rejeitava porta ocupada por OUTRO processo — mas não distinguia containers da MESMA app (que estão sendo substituídos). Resultado: re-deploy do mesmo app sob random/sequential strategy hitavaport_in_use_by_other. Fix: pipeline filtra ports de containers{project_und}_{instance}_*antes de chamar sticky. Resolve "ou é nosso → reusa" prometido na docstring desde a v2.19.0 inicial.
Backwards compatibility
- Apps registradas em v2.18.x continuam rodando sem mudança
- Default de
publish_strategyéExplicitquandonetworkblock ausente — comportamento idêntico ao v2.18.x (lêports:do.deploy.yml) - State files antigos sem bloco
networkmigram silenciosamente — porta re-alocada no próximo deploy quando strategy != Explicit instances.X.domaincontinua aceito (sem mudança)
Validação
- 309 unit tests pass (era 304 + 5 do
secrets_preflight+ extras de network module) - Matriz CI/CD: 90 cells, 73 pass, 0 fail, 17 n/a em 114s
- 2 ops novas no matrix:
network_check: valida porta sticky em range 9000-9100secrets_preflight: valida deploy aborta com secret vazio
- 9 estados validados ponta-a-ponta sob random strategy: A B C D E F G H I
Backlog identificado (não bloqueia)
runner status --jsonschema ainda flat fora do novoruntime.network— refactor completo (manifest/config/runtimeblocks separados) fica pra v2.20.0- W6 smoke probe (rodar imagem brevemente pra detectar listen port real do container)
- Sol 2 do feedback prod (post-deploy smoke test configurável no
.deploy.yml) — v2.20.0
[2.18.2] - 2026-05-06 — Wizard "auxiliar inteligente"
Resumo
5 melhorias no wizard/generator que tornam o runner add realmente zero-touch
para a maioria dos perfis e auxiliam o operador quando a análise estática
não tem como saber a resposta. Construído sobre uma matriz de testes CI/CD
(72 cells, 9 estados × 7 ops) que cobre todos os cenários de uso do runner.
Adicionado (W1) — Warning explícito quando porta é incerta
Antes: se o Dockerfile não tinha EXPOSE, o generator silenciava com port: 8080.
Para compose, pegava o mapping host:container sem avisar que o container
internamente pode escutar em outra porta.
Agora:
- Dockerfile sem EXPOSE: warning
Port: Dockerfile sem EXPOSE; assumindo 8080. Se o container escuta em outra porta, ajuste port: no .deploy.yml - Compose com
ports:: warningPort: usando porta X do mapping no compose. ATENÇÃO: este é mapping host:container — se o container escuta internamente em outra porta (nginx :80, php-fpm :9000), ajuste port:
Adicionado (W2) — Healthcheck type-aware no generator
Antes: TCP cego em todos os deploys gerados.
Agora detect_default_healthcheck() escolhe baseado no nome da imagem:
| Imagem | Healthcheck gerado |
|---|---|
nginx*, caddy*, httpd*, apache*, traefik* |
mode: http path: / |
php-fpm*, qualquer *fpm* |
mode: tcp |
node*, python*, ruby*, openjdk*, rust* |
mode: tcp |
| Outros | mode: tcp (fallback seguro) |
Adicionado (W5) — Aviso quando Dockerfile + compose coexistem
Antes: silenciosamente usava o Dockerfile, ignorava compose service.
Agora: warning Source: repo tem Dockerfile + docker-compose.yml. Build do Dockerfile foi usado como source-of-truth (compose só pra env vars). Para usar a imagem do compose em vez de buildar, edite type:image + image:
Adicionado (W7) — Detecção de domínio placeholder no wizard
Após o prompt de domínio interativo, warning loud se o valor casa com:
*.example.com / .org / .net.test,.local,.localhost,.invalid- Markers:
placeholder,todo,fixme,xxx,changeme,yourdomain
Não bloqueia (operador pode realmente querer um staging em *.test), só nudge.
Corrigido (Bonus) — `--accept-defaults` rejeita perfil bare
Antes: runner wizard --accept-defaults em repo sem Dockerfile/compose/.env
gerava silenciosamente um .deploy.yml inútil.
Agora: bail com no_deploy_profile (mesmo comportamento do runner add).
Corrigido — TCP/HTTP probe IMAGE mode usa `bash -c` no fallback
Bug: probe usava sh -c '... /dev/tcp/... ...' no fallback. Como /dev/tcp
é builtin do bash (não do dash/sh), containers Debian-based sem nc falhavam
silenciosamente o healthcheck. Quebrava nginx:1, php-fpm:debian, etc.
Fix: o fallback agora invoca bash -c "exec 3<>/dev/tcp/127.0.0.1/PORT" —
funciona em qualquer imagem que tenha bash (a maioria das de produção).
Validação
- 284 unit tests pass (era 283; +1 do
placeholder_domain) - Matriz CI/CD: 72 cells, 63 pass, 0 fail, 9 n/a em 110s
- 9 estados validados: A (.deploy.yml), B (Dockerfile), C (compose), D (Laravel), E (bare → falha esperada), F (image-only), G (sem EXPOSE), H (IP-restricted), I (secrets em env)
Backlog identificado pela matriz (não bloqueia)
runner status --jsonainda flat — refactor para separarmanifest/config/runtimeblocks (input pra integração CCS)- W6 (smoke probe pra detectar porta real do container) — alta complexidade
- W3 (version source detection: file vs git-commit) — baixa prioridade
- W4 (
git ls-remote --headspra listar branches no wizard) — baixa prioridade - W8 (presets por stack: Laravel/React/Flask) — média prioridade
[2.18.1] - 2026-05-05 — Hotfix (6 bugs do loop e simulation)
Corrigido (P0)
- Pipeline lê
repodo state file (Bug E real em prod): migration v2.16.0 era parcial — pipeline ainda exigiagithub.repono.deploy.ymle quebrava deploys de apps migrados. Agora resolve do state primeiro, fallback pro manifest legacy. Erro novono_repo_configuredé instrutivo. - Wizard
--answers.instance_branchpropaga até o YAML: era parseado mas hardcoded comobranch: main. AgoraInstanceSection.branchchega ao render. - Wizard recusa sobrescrever
.deploy.ymlexistente (perfil A): bail commanifest_already_exists. Antes gerava defaults silenciosos. - Validator aceita
type:ORapp_type:: antes pedia sóapp_type:mas o parser canonical e o wizard usamtype:.
Corrigido (P1)
- Mensagem de erro
no_deploy_yml: no_deploy_profile:redundante: strip do prefixo interno do generator. runner status --jsontop-levelcurrent_version: campo era NULL no top, populado em 3 lugares diferentes do JSON. Agora atalho do state.current_version.crypto.rs/vault/ckeys.rsaceitamRUNNER_KEYS_DIRenv var: paths hardcoded/opt/runner/.keysexigiam root. Agora override opcional pra rootless test/CI; default inalterado pra prod.
Testes
36 unit tests no wizard module + 283 na suite total. Loop validation script: 6/6 pass do zero, incluindo cenário adversarial sem github: no manifest.
[2.18.0] - 2026-05-05 — Lote A (feedback de producao)
Atendendo feedback do deploy real do middleware-241 (Laravel 11). 6 itens.
Corrigido
--branchpropaga prainstances.X.source.branch(#4): antes,runner add --branch devclonava dedevmas a instance ficava commaindo.deploy.yml—runner fetchposterior puxava da branch erradarunner add --deployrealmente deploya (#5): antes era silenciosamente ignorado com mensagemAuto-deploy requested but disabled. Agora invocapipeline::deploy()na sequencia
Wizard polish
- Curadoria de
.env.example(#6): detectaAPP_ENV=local,APP_DEBUG=true,LOG_LEVEL=debug,NODE_ENV=development→ oferece "perfil de producao" (production,false,info); pruning automatico de placeholders comuns (AWS_*,MAIL_*,MEMCACHED_*,PUSHER_*) - Deteccao Laravel via
composer.json(#7): identificalaravel/framework, analisaroutes/api.php+resources/views/para distinguirsystem: apivssys; com Dockerfile ->type: docker-build runner wizard --answers <file>(#8): substitui prompts interativos por respostas em JSON. Habilita IA/script/CI gerar manifesto valido sem TTY. Suporta override de campos do manifesto,env_overrides,skip_env(glob),secret_keys/non_secret_keys(forca classificacao)- ckey display por fingerprint (#10): default agora mostra apenas
ck_xxxx…xxxx(ckey fica encriptada emstate/<app>.yml); flag--ckey-showopt-in para exibir plaintext
Adiado para Lote B/C/D
Itens P0 do feedback (architectural changes):
VPC port allocation (— entregue na v2.19.0network.{vpc_ip, publish_strategy, port_range})traefik.mode: external+ remocao de URL fantasia no output (planejado, não implementado)- Backup automatico no
unregister(planejado, não implementado)
Detalhes
- 7 unit tests novos (281 total na suite, 0 falhas)
- Sem breaking change
[2.17.5] - 2026-05-05
- gitops fix attempt #5: migra
extra_artifacts->scripts:v3.0 section. Tool web emrunner.ccs.systems/tools/secrets.htmlsegue aguardando atualizacao do gitops worker em producao
[2.17.4] - 2026-05-05
- gitops fix attempt #4: arquivo
tools/secrets/index.htmlna raiz do repo (igualinit.sh)
[2.17.3] - 2026-05-05
Corrigido (gitops)
- Source path em
extra_artifactsagora usaclient/tools/secrets/index.html(relativo a raiz do repo) — tool web finalmente publicada em https://runner.ccs.systems/tools/secrets.html
[2.17.2] - 2026-05-05
Corrigido
- gitops:
type: htmlem extra_artifacts era silenciosamente ignorado. Trocado paratype: script
[2.17.1] - 2026-05-05 — F3 Tool web
- Secrets Web Tool — gera
.runner/secrets.enc100% client-side (Web Crypto API) - URL: https://runner.ccs.systems/tools/secrets.html
- Mesmo formato AES-256-GCM + SHA-256(ckey) que o Runner ja decifra
- Modo encrypt-only (sem decode, decisao de seguranca)
[2.17.0] - 2026-05-05 — F1 Wizard CLI
- `runner wizard` — wizard interativo para gerar
.deploy.yml(e opcionalmente.runner/secrets.enc) - Auto-trigger no
runner addem modo TTY quando perfil E (cru) - Detecta linguagem (next/react/flask/django/celery/rust/php) → infere
system/type - Multi-service compose: pergunta qual e o app principal, marca redis/postgres/etc como assets
- Subcomando
runner templateinspeciona schema embutido (debug)
[2.16.0] - 2026-05-05 — Lote 5 (#6, #4)
- Generator de .deploy.yml — detecta perfil A-E e gera manifesto proporcional
AppState.repomigrado para state file (deprecatesgithub:no.deploy.yml)- Migration silenciosa para apps existentes
[2.15.0] - 2026-05-05 — Lote 4 (#7)
runner addnao clona mais o repo na raiz do app dir (causava duplicacao)- Apenas
.deploy.yml+.runner/state-markerna raiz; codigo vive empull/esrc/{instance}/{ver}/ runner cleanup --reset-root <app>opt-in para apps existentes
[2.14.0] - 2026-05-05 — Lote 3 (#8)
runner add/deployBLOQUEIA secrets emenvironment:sem ckey configurada- Flag global `--insecure` suprime TODAS as travas (banner vermelho + audit log JSONL)
[2.13.0] - 2026-05-05 — Lote 2 (#3)
- `runner tokens` — list/test agrupado por fingerprint (sem revelar plaintext)
runner add --token-from <other-app>reusa token encriptado entre apps
[2.12.0] - 2026-05-05 — Lote 1 (#1, #2, #5)
--ckeysem valor gera ckey random; com valor aceita literal--repoaceita URLs (https/ssh/git@) e normaliza parauser/repo--branchagora obrigatorio sempre (sem defaultdist)
[2.11.6] - 2026-05-04
- Logs vao para stderr (stdout limpo para piping/JSON)
- Handler SIGPIPE evita panic ao piping para
head/jq
[2.11.5] - 2026-05-04
- self-update lida com chattr +i (immutable bit) automaticamente
[2.11.0-2.11.4] - 2026-04-29 a 2026-05-03
- v2.11.4: docker run env injection via
--env-file - v2.11.3: provisionamento de
.env/.secretsno bootstrap - v2.11.2: bug fix
app_has_ckey(secrets nao injetados em redeploys) - v2.11.0: vault completo (mkey + ckeys + config-backups + secrets), Ed25519 minisign
[2.10.0] - 2026-04-25
runner debug test— validacao end-to-end (config, vault, deploys, validators)- Coverage 100% de
--jsonem todos os comandos
[2.9.0] - 2026-04-22
- CCS Data Contract — alinhamento de output
runner snapshot --jsonpara integracao com dashboard CCS
[2.8.0] - 2026-04-20
- Master key backup:
runner mkey export/importpara disaster recovery
[2.7.0] - 2026-04-19
init.sh— instalador unificado (arch-aware,--rcflag para release candidates)
[2.6.0] - 2026-04-18
mode: exitno healthcheck — apps CLI/scanners/jobs one-shot (sem container permanente)
[2.5.0] - 2026-04-18
runner initinterativo — wizard de setup inicial com--sete--mkey
[2.4.2] - 2026-04-17
- Deteccao automatica de secrets — Aho-Corasick multi-pattern matching com 3 niveis de confianca (HIGH 95%, MEDIUM 75%, LOW 50%)
runner env setinteligente — auto-detecta secrets pelo nome, encripta com ckey ou avisa se plaintext--forceno env set — forca como secret sem questionamento (requer ckey).secretsso com encriptacao — sem ckey o pipeline ignora.secrets,--secretsem ckey retorna erroregeneratefallback — procura key no.envse.secretsnao existe- Filtro de falsos positivos —
keyboard,primary_key,max_tokens,cache_key,public_key, etc.
[2.4.1] - 2026-04-17
runner cleanup --images— remove imagens Docker nao utilizadas de projetos gerenciados pelo runnerrunner cleanup --build-cache [FILTER]— prune do Docker buildkit cache com filtro de idade (default: 72h, aceita24h,168h,all)- Limpeza automatica de imagens no pipeline —
cleanup_old_versionsagora remove a imagem Docker de versoes que excedemkeep_versions docker image prune -f— executado automaticamente apos cleanup de imagens (dangling layers)
[2.3.0] - 2026-04-16
- mTLS com tokio-rustls — API HTTP requer client certificates quando habilitado
- Hot-reload de CA cert — mtime-based, CCS rotaciona cert via SCP sem restart
- TLS proxy architecture — tokio-rustls acceptor + warp backend ephemeral
[2.2.0] - 2026-04-14
runner service— lifecycle CLI para systemd (init, start, stop, restart, status, config, uninstall)runner signal— sinais externos para CI/CD: send (ok/warn/fail + action), clear, listrunner serve --service— service mode com API HTTP + scheduler generico- API HTTP autenticada — 5 endpoints REST com X-API-Key (/health, /api/v1/status, signal, deploy, apps)
- API Key com fingerprint — formato
rk_{SHA256(hostname+MAC+machine-id)[..8]}_{random} - Scheduler generico — trait-based: CanaryJob, TtlCleanupJob, SignalCheckJob
- service.yml — configuracao separada do service mode (API, scheduler, webhook, mTLS)
[2.1.0] - 2026-04-07
- Healthcheck honra HEALTHCHECK do Dockerfile — nao sobrescreve com --health-cmd
- Healthcheck modos
tcp:ecmd:— prioridade cmd > tcp > path depends_on:entre apps — topo sort emrunner deploy --all, cycle detection- Validacao semantica em
runner add— build context, Dockerfile, port, healthcheck image_mode: dockerfileaceito como alias de self-containedstart_periodconfiguravel no .deploy.yml (default 30s)- wait_for_healthy tolerante — nao aborta em Unhealthy transitorio
- Self-update multi-arch — detecta x86_64/aarch64 e baixa binario correto do CDN
- Lock file PID-alive — remove locks orfaos automaticamente
- Mensagens de erro melhoradas — YAML com linha/coluna, token expirado, rollback em 1a deploy
[1.5.0] - 2026-03-17
Adicionado
--instancenorunner add: Define qual instancia do.deploy.ymlesta maquina deve deployar--instancenorunner edit: Altera a instancia a qualquer momento, com validacao contra o.deploy.yml- Campo
instanceno state: Persistido no state file, usado pelo auto-deploy - Guia Multi-Ambiente: Documentacao completa sobre deploy em multiplas maquinas
Alterado
- Auto-deploy resolve instancia por prioridade:
state.instance>default_instance_name()>"production" runner edit --show: Agora mostra a instancia configurada
[1.4.1] - 2026-03-13
Adicionado
- max_deploy_retries: Limita re-tentativas de deploy para o mesmo commit que falhou (padrao: 3). Evita loops infinitos em builds quebrados
- Rastreamento de falhas no estado: Campos
last_failed_commitedeploy_retry_countno arquivo de estado da app - Rollback automatico em falha de start: Quando o container antigo e parado por conflito de porta e o novo falha ao iniciar, o anterior e reiniciado automaticamente
Melhorado
- Mensagens de erro nas notificacoes: Extrai a linha de erro real de outputs verbosos (Docker BuildKit, npm, Rust) em vez de truncar no inicio. Limite de 800 caracteres
- Notificacao de falha mostra rollback: Indica qual versao ficou ativa apos o rollback
[1.4.0] - 2026-03-12
Adicionado
runner notify setup: Setup interativo de notificacoes Telegram e Discordrunner notify test: Envia notificacao de teste para todos os canais configuradosrunner notify status: Mostra status dos canais- StructuredLogChannel: Audit trail em
deploy-events.jsonl - NotifyManager v2: Substitui funcoes legacy por multi-channel com suporte a Telegram, Discord e structured logging
Alterado
- Pipeline usa NotifyManager v2: Substitui chamadas legacy por
NotifyManager.send() - Config consolidado:
GlobalConfigremovido, tudo viaRunnerConfig
Removido
- Codigo legacy de notificacoes (
send_telegram,send_discord,notify_deploy_*)
[1.2.3] - 2026-03-01
Modificado
Modos de Deploy Simplificados
O Runner agora opera em dois modos exclusivos, determinados pela presenca de image: ou build: no .deploy.yml:
- Modo IMAGE (
image: xxx): usa imagem pronta do registry, monta source via volume - Modo BUILD (
build: {dockerfile: Dockerfile}):docker buildcom Dockerfile do repositorio
Isso substitui a logica anterior de image_mode (bind-mount/self-contained) e app_type: docker-build.
A geracao automatica de Dockerfile por stack (generate_dockerfile) foi deprecada. Projetos devem fornecer seu proprio Dockerfile.
Retrocompatibilidade
Configs antigas continuam funcionando:
image_mode: self-contained-> tratado como modo BUILD com Dockerfile defaultapp_type: docker-build-> tratado como modo BUILDimage_mode: bind-mount+image:-> modo IMAGE (sem mudanca)
Adicionado
- Deploy lock: Previne deploys concorrentes no mesmo app via
.deploy.lock - Audit log no pipeline: Deploy registra sucesso e falha no
deploy-audit.jsoncom versao, duracao e erro - Output estruturado: Modulo
output/com envelope JSON para integracao programatica - Campo
source.directory: Permite especificar subdiretorio do repo para deploy (ex:directory: .usa raiz inteira) - Deteccao de VOLUME em imagens: Override automatico com bind mount explicito para evitar overlay de anonymous volumes
- Metodo
is_build_mode()para deteccao do modo de deploy - Metodo
is_image_mode()para deteccao do modo de deploy - Metodo
validate_deploy_mode()para validar configuracao - Metodo
effective_build_config()para obter config de build efetiva - Auto-mount de
data/,logs/,keys/em ambos os modos
Corrigido
- Audit log backward compat: Campo
instanceemDeployAuditEntrycomserde(default)para logs antigos - source.directory ignorado: Campo nao existia em
InstanceSourceConfig, serde ignorava silenciosamente - Read-only filesystem: Mount de source como
:roimpedia Docker de criar mountpoints para VOLUMEs - Health check timeout: Usava
healthcheck.timeout(per-check) como tempo total de espera. Agora calcula corretamente:start_period + (interval + timeout) * retries + buffer - prepare() em build mode: Usa diretorio completo do pull/ ao inves de auto-detectar subdiretorios
Deprecado
generate_dockerfile()— projetos devem fornecer Dockerfilewrite_dockerfile()— nao utilizado pelo pipeline- Campo
image_mode— usarimage:oubuild:diretamente app_type: docker-build— usar secaobuild:
[1.2.2] - 2026-02-19
Adicionado
Comando `runner edit`
Novo comando para editar configuracao de aplicacoes registradas:
# Ver configuracao atual
runner edit runner-docs/front --show
# Mudar branch trackeada
runner edit runner-docs/front --branch devO comando inclui validacoes automaticas:
- Verifica se repositorio git existe
- Verifica se branch existe no remote
- Verifica se branch contem
.deploy.yml
Melhorias no Cleanup
- Output colorido com destaque para nomes de containers
- Flag
--verbosepara logs detalhados - Deteccao de conflitos quando multiplas configs matcham mesmo container
Self-Update com URL
Suporte para atualizar para versao especifica via URL:
runner self-update --url https://runner.ccs.systems/rc/v1.2.2-rc.8/runnerCLI Padronizado
Todos os comandos agora aceitam app como argumento posicional:
# Antes (verbose)
runner fetch --app runner-docs/front
# Agora (direto)
runner fetch runner-docs/front
runner health runner-docs/front
runner logs tail runner-docs/front
runner env status runner-docs/frontPreparacao de Conteudo Melhorada
A funcao prepare() agora suporta repositorios com conteudo na raiz:
- Detecta automaticamente diretorios:
dist/,guides/,content/,docs/,public/,src/ - Fallback para raiz do projeto quando nenhum diretorio especifico existe
- Identifica corretamente
src/como estrutura de versoes vs codigo fonte
Corrigido
- Duplicatas na deteccao de containers orfaos
- Comparacao de versao usando SHA256 quando versoes sao iguais
- Conteudo na raiz (ex:
guides/) nao era copiado para diretorio de versao - Comandos
fetch,health,logs,envexigiam flag--appdesnecessariamente
[1.2.0] - 2026-02-16
Adicionado
Novos Source Types
Tres novos tipos de fonte para instancias:
| Tipo | Descricao | Uso |
|---|---|---|
tag |
Deploy de tags Git | Releases (v*) |
local |
Deploy de diretorio local | Hotfixes, builds externos |
image |
Deploy de imagem Docker pronta | Imagens pre-built |
instances:
release:
source:
type: tag
tag_pattern: "v*"
hotfix:
source:
type: local
path: /builds/hotfix/
canary:
source:
type: image
image: ghcr.io/org/app:canaryCanary Scheduler (Auto-Promocao)
Sistema de promocao automatica de canary deployments:
instances:
production:
canary:
enabled: true
auto_promote:
enabled: true
initial_weight: 10
increment: 10
interval: 5m
final_weight: 100
health_check_before: true
abort_on:
- health_check_failed
- error_rate_above:5%
- latency_p99_above:500msNovos comandos CLI:
runner canary status <project> -i <instance>- Ver status do schedulerrunner canary pause <project> -i <instance>- Pausar promocaorunner canary resume <project> -i <instance>- Retomar promocaorunner canary abort <project> -i <instance>- Abortar e rollback
Notificacoes v2
Arquitetura extensivel com trait-based channels:
HTTP Webhook - Notificacoes para qualquer endpoint HTTP:
# config.yml
integrations:
flare:
type: http_webhook
url: https://flareapp.io/api/deploys
method: POST
auth:
type: api_key
key: "${FLARE_API_TOKEN}"
location: query
param_name: api_tokenTipos de autenticacao suportados:
none- Sem autenticacaoapi_key- Token em query param ou headerbearer- Header Authorization: Beareroauth2- Client credentials flow (auto-renew)basic- Username:passwordhmac- Assinatura HMAC-SHA256
Structured Log - Audit log em JSON Lines:
logs:
deploy_events:
type: structured_log
path: /opt/runner/logs/deploy-events.jsonl
format: json_linesComando `runner serve`
Servidor de triggers para webhooks e cron:
runner serve # Auto-detecta modo
runner serve --mode webhook # Apenas webhook server
runner serve --mode cron # Apenas cron scheduler
runner serve --mode both # Ambos
runner serve --port 9000 # Porta customizadaO webhook server:
- Recebe eventos GitHub/GitLab/Bitbucket
- Verifica assinatura HMAC-SHA256
- Aciona deploys automaticos
- Endpoint de health em
/health
CLI Filters por Labels CCS
Filtragem de containers por labels:
runner list --project=meu-app
runner list --instance=production
runner list --project=frontend-* --jsonLabels CCS usadas:
ccs.systems/projectccs.systems/instanceccs.systems/version
Alterado
Deploy Output Melhorado
Novo formato de output com badges de progresso:
[1/6] [PREPARE] Preparando deploy...
[2/6] [ARTIFACT] Copiando artefatos...
[3/6] [ENV] Gerando ambiente...
[4/6] [SECURITY] Verificando segurança...
[5/6] [CONTAINER] Subindo container...
[6/6] [PROMOTE] Promovendo versao...
═══════════════════════════════════════
✓ DEPLOY CONCLUIDO
App: projeto/production
Versao: v1.2.0
URL: https://app.projeto.com.br
═══════════════════════════════════════- deploy --verbose (-V): Modo verbose com logs tecnicos detalhados
- deploy --force (-f): Forca redeploy mesmo se versao ja implantada
- Box de resumo: Exibe app, versao e URL ao final do deploy
Fetch Output em Portugues
Status traduzidos para melhor legibilidade:
| Icone | Status | Significado |
|---|---|---|
| ✓ | atualizado | App atualizada |
| ↑ | desatualizado | Update disponivel |
| ↓ | baixado | Artefatos baixados |
| ✗ | erro | Erro no processo |
- fetch output ordenado: Resultados ordenados alfabeticamente por projeto/sistema
- fetch --force (-F): Nova flag para forcar git pull antes de carregar config
Corrigido
- Recursao infinita na copia: Skip de diretorios de instancia (production, staging, pr-*) e commit hashes
- app_type no Dockerfile: Usa
config.app_typeao inves de hardcoded "sys" - Duracao de healthcheck: Normaliza valores numericos para formato Docker (30 → 30s)
- Tipo "docs": Novo tipo para imagens de documentacao (MkDocs, PimDocs)
[1.1.13] - 2026-02-16
Alterado
- fetch output colorido: Cores ANSI para facilitar leitura
- Verde (✓) para sucesso e "up to date"
- Amarelo (↑) para updates/pulled
- Vermelho (✗) para erros
- Ciano para sugestoes de solucao
- Dim para informacoes secundarias (versoes)
[1.1.12] - 2026-02-16
Alterado
- fetch output limpo por padrao: Output mais amigavel para usuarios
- Logs INFO escondidos por padrao (usa nivel WARN)
- Formato limpo com alinhamento e indicadores visuais
- Sugestoes de fix para erros comuns
- Nova flag
--verbosepara output detalhado - Uso:
runner fetch(limpo) ourunner fetch --verbose(detalhado)
[1.1.11] - 2026-02-16
Adicionado
- fetch --force (-F): Nova flag para forcar git pull antes de carregar config
- Resolve problema de
.deploy.ymldesatualizado no servidor - Util para migracao de formato legado para v1.1.10
- Uso:
runner fetch --forceourunner fetch -F
- Resolve problema de
[1.1.10] - 2026-02-16
Corrigido
- Bug 11 - Auto-mount volumes: Respeita volumes explicitos do
.deploy.yml- Se usuario configura
keys:/app/keys:ro, usa essa config - Default para
keys/mudou de:rwpara:ro(seguranca) logs/edata/continuam como:rw- Evita erro de mount duplicado
- Se usuario configura
[1.1.9] - 2026-02-16
Corrigido
- app_type parsing: Alias
type/app_typepara compatibilidade YAML - env_files: Container carrega
.enve.secretsautomaticamente - volumes bind mount: Volumes relativos convertidos para paths absolutos
[1.1.8] - 2026-02-16
Corrigido
extract_version(): Suporte a campos nested em TOML (ex:
package.versionem Cargo.toml)- Parser agora detecta secoes
[section]e busca chaves dentro delas - Compativel com JSON e YAML via dot notation
- Parser agora detecta secoes
copy_dir_recursive(): Previne recursao infinita durante copia de diretorios
- Detecta quando o destino esta dentro da origem
- Pula diretorios de versao (
v*) e symlinks
docker-build: Suporte completo no pipeline de deploy
- Nova struct
DeployBuildConfigpara configuracao de build - Metodo
is_docker_build()para deteccao de tipo - Healthcheck sem curl (usa bash /dev/tcp)
- Build de imagem a partir do Dockerfile do projeto
- Nova struct
[1.1.7] - 2026-02-15
Alterado
Storage: Default
apps_pathalterado de/appspara/data/apps- Segue estrutura de storage do ecossistema CCS
- Compativel com migracao para Kubernetes
- Configs existentes com
apps_path: /appscontinuam funcionando
Self-Update: Novas flags
--rc: Atalho para--channel rc-C, --check: Apenas verifica se ha atualizacao (nao baixa)
[1.1.6] - 2026-02-14
Adicionado
- Canais GitOps - Suporte a canais
stableercno.gitops.yml - Releases estaveis na raiz:
/runner - Release candidates em:
/rc/runner - Versionamento automatico por tag pattern (
vX.Y.ZvsvX.Y.Z-rc.N)
Alterado
- Self-update agora suporta flag
--rcpara baixar release candidates - Estrutura de storage separada por canal
[1.1.5] - 2026-02-14
Corrigido
- Versao interna do binario corrigida para corresponder a tag
[1.1.2] - 2026-02-13
Corrigido
folder_nameno.gitops.ymlcorrigido para processar corretamente
[1.1.1] - 2026-02-13
Alterado
- URL de download alterada para
runner.ccs.systems .gitops.ymlatualizado para nova estrutura de storage
[1.1.0] - 2026-02-12
Adicionado
MCP Server (Model Context Protocol)
Implementacao completa do servidor MCP para integracao com Claude Code.
Comandos CLI:
runner mcp serve # Inicia servidor MCP
runner mcp install # Instala config MCP
runner mcp uninstall # Remove config MCP
runner mcp status # Mostra info do servidorTools disponiveis (25+):
| Categoria | Tools |
|---|---|
| Deploy | runner_deploy, runner_rollback |
| Apps | runner_list_apps, runner_app_status, runner_add_app |
| Instances | runner_instances, runner_versions, runner_destroy_instance |
| Canary | runner_weights, runner_set_weight, runner_promote |
| Staging | runner_stage_deploy, runner_stage_list, runner_stage_destroy, runner_stage_cleanup |
| Environment | runner_env_status, runner_env_set, runner_env_validate |
| Config | runner_validate_config, runner_generate_config |
| Maintenance | runner_fetch, runner_cleanup |
| Logs | runner_logs_history, runner_logs_tail |
Resources (9):
runner://instructions/*- Documentacao estaticarunner://status/*- Status dinamico (apps, staging, config)
Prompts (5):
setup_new_project- Setup de novo projetodiagnose_deploy- Diagnostico de falhascanary_workflow- Workflow de canarymigrate_config- Migracao para v1.0.0staging_pr- Deploy de PR
Self-Update
Modulo de auto-atualizacao do binario.
runner self-update # Atualizar para versao estavel
runner self-update --force # Forcar reinstalacao
runner self-update --channel rc # Usar release candidate
runner self-update --path /opt/bin # Caminho customizadoFeatures:
- Download automatico da versao mais recente
- Suporte a canais (stable/rc)
- Backup automatico do binario atual
- Verificacao de integridade
[1.0.0] - 2026-02-05
Alterado (Breaking Changes)
Esta versao remove completamente o codigo legado e padroniza a arquitetura baseada em instancias.
Novo Formato `.deploy.yml`
O novo formato usa instances{} ao inves de containers[]:
# Novo formato (v1.0.0)
project: meu-projeto
port: 8000
networks: [public, mysql]
environment:
KEY: value
instances:
production:
domain: app.site.com
source:
type: dist
keep_versions: 3Comparacao de mudancas:
| Legado | v1.0.0 |
|---|---|
containers[] |
instances{} |
type: front-static (enum) |
app_type: "front-static" (string) |
versioning.keep_versions |
instances.X.keep_versions |
environment.templates |
environment: {} (HashMap) |
deploy.strategy |
Traefik dynamic config |
Mantido
InstanceDeployConfig- Estrutura principalGlobalConfig- Config globalRunnerConfig- Config em/opt/runner/config.yml- Traefik module - Geracao de YAML dinamico
- Containers module - Docker run/build
[0.6.0] - 2026-02-04
Adicionado
Novos Modulos
runner/mod.rs- Configuracao global do Runnertraefik/mod.rs- Geracao de YAML dinamico para Traefikcontainers/mod.rs- Operacoes Docker viadocker run/build
Novos Comandos CLI
runner init- Inicializa ambiente Runnerrunner instances <project>- Lista instanciasrunner versions <project> -i <name>- Lista versoesrunner weights <project> -i <name>- Mostra pesosrunner weight <project> <inst> <ver> <weight>- Ajusta pesorunner promote <project> -i <name> <version>- Promove versaorunner destroy <project> -i <name>- Remove instanciarunner cleanup- Limpa versoes antigasrunner migrate --app <path>- Migra estrutura legada
[0.5.0] - 2026-01-28
Adicionado
- Comando
reset- Reset de deploy - Flag
--hard- Reset incluindo.enve.secrets - Flag
--skip-tests- Pula testes apos redeploy - Hook
post_healthy- Comandos apos health check - Variaveis em hooks -
${VERSION},${APP_PATH},${CONTAINER} - Comando
stage reset- Reset de staging
[0.4.0] - 2026-01-26
Adicionado
- Production Compose Generation - Geracao automatica de
docker-compose.yml - Staging de PRs - Ambientes efemeros para Pull Requests
- Comando
stage deploy- Deploy de PR - Comando
stage list- Lista stagings - Comando
stage destroy- Remove staging - Comando
stage cleanup- Cleanup automatico - Template variables -
${PR_NUMBER},${REPO},${PROJECT}, etc.
[0.3.0] - 2026-01-26
Adicionado
- Comando
add- Registra novas apps - Comando
fetch- Busca atualizacoes - Tipo
docker-build- Deploy com Dockerfile customizado - Auto Deploy - Configuracao per-app
- State Management - Persistencia de estado
- Audit Log - Log global de fetch
Alterado
- Binario renomeado de
cicd-runnerpararunner - Path padrao alterado para
/opt/runner/
Removido
- Webhook server - Substituido por add/fetch
[0.2.0] - 2025-01-25
Adicionado
- MANUAL.md - Documentacao para clients
- Webhook server - Endpoint para GitHub push events
- HMAC-SHA256 - Validacao de assinaturas
[0.1.0] - 2025-01-25
Adicionado
- CI/CD Runner em Rust - Implementacao inicial
- Blue-Green Deployment - Teste antes de promover
- Versioned Deploys - Diretorios versionados
- Environment Templates - Sistema de templates
- Custom Deploy Types - wordpress-plugin, mautic, custom
- Multi-Source Downloads - Download de multiplos repos
- Telegram/Discord Notifications - Notificacoes
- Playwright Integration - Testes E2E
- Automatic Rollback - Rollback em falha
- Health Checks - Verificacao de saude