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.80.2] - 2026-08-20 — `switch-instance`, e tres travas de superficie
Adicionado — `runner switch-instance`
Move um app ja deployado para outra instancia. A instancia entra no NOME do
container ({project}_{instance}_{versao}) e no arquivo de rota do Traefik, entao
trocar so o state.instance nao renomeia nada: o proximo deploy sobe o segundo
conjunto ao lado do primeiro, que continua servindo.
Ordem EDIT → DEPLOY → DESTROY, deliberada: a alternativa trocaria uma falha
recuperavel por indisponibilidade. Deploy quebrado devolve o state ao original e a
instancia antiga segue no ar. Sem --force, imprime o plano e nao altera nada.
Ver switch-instance.
Guarda no runner edit --instance: recusa a troca com container no ar, nomeando os
containers e as duas saidas.
Seguranca — token do bot vazava no log (#96)
http_webhook logava a URL completa, e canais cuja credencial vive no PATH escreviam o
segredo em texto claro — o Telegram usa .../bot<TOKEN>/sendMessage, uma linha por
notificacao. Medido em producao: 47 linhas em 15 arquivos.
O caminho de falha vazava tambem: o erro do reqwest embute a URL na mensagem dele.
Mascarar so no log de sucesso deixaria o segredo escapar justamente pelo caminho que
mais aparece em log de incidente.
O token em uso deve ser considerado comprometido e rotacionado.
Adicionado — `test/` e `tests/` saem da copia de deploy (#95)
O driver nao e credencial, e superficie: codigo-fonte da suite dentro de um artefato
de runtime, servido por static server ou assado na imagem pelo COPY . ..
docs/ fica deliberadamente de fora — existe type: docs, e app de documentacao serve
docs/ legitimamente.
Corrigido — parcial: healthcheck do Traefik ignora o manifesto (#94)
healthcheck.interval/timeout valem para o Docker, mas o router usa 10s/5s fixos
da crate. Meia correcao: o conserto real exige campo novo em traefik-rs. Ate la, o
runner passa a avisar em vez de aceitar calado.
[2.80.0] - 2026-08-19 — `runner destroy` apagava a frota inteira (#97)
Entradas 2.70–2.79 ainda nao migradas para este changelog; ver
CHANGELOG.mdno repo.
Corrigido — CRITICO
runner destroy <app>removia containers de TODOS os projetos do servidor.ContainerFilter::to_docker_argsusavaprojectcomo flag booleana: testavais_some()e filtrava so pormanaged-by=runner, nunca pelo nome do projeto. Medido em producao, o filtro devolvia 79 containers de 73 projetos.A causa nao era codigo ausente — era codigo nao propagado: o refino de identidade existia e estava correto em
list_containers_detailed, mas a funcao irma usada pelo destroy nao o tinha. Duas listagens respondendo diferente a mesma pergunta. A correcao e delegacao, para que a divergencia nao possa voltar.Verificado em producao:
destroy nfse -i productionlistava 79 containers, passou a listar 1.
Adicionado — travas de raio
- Trava no
destroy: se um container da lista nao casar com{project}_{instance}_, aborta sem remover nada e nomeia o intruso. - Dry-run no
destroy: sem--force, lista os containers e conta as rotas que seriam removidas. Antes so dizia "Use --force to confirm" — o operador confirmava as cegas. - Gate de amplitude no
deploy --alledeploy-all: imprime as familias afetadas, confirma em terminal e exige--yesem cron. Ver deploy-all. - Gate de fronteira de projeto: uma query que casa alvos de projetos distintos
agora aborta —
--yesnao autoriza o cruzamento, de proposito. Vale paradeploy,fetch,restart,cleanup,unregisterereconcile.
BREAKING
runner deploy-allpassa a exigir-y, --yesem contexto nao-interativo (cron/CI). Scripts existentes precisam da flag. Ver deploy-all.
[2.69.0] - 2026-08-06 — Falha de deploy diz onde procurar o bundle; esquema de secrets divergente para o deploy
Adicionado
Reconciliação de esquema de secrets antes do deploy. O state do servidor e o arquivo de bundle respondiam separadamente "qual esquema é este", e nada checava se concordavam. Com o state declarando ckey assimétrica e o bundle no disco em formato simétrico, o deploy subia verde, com o container sem nenhum secret — sem erro, sem aviso, sem log.
Agora as duas fontes são cruzadas. Divergência interrompe o deploy, citando o que cada fonte declara, o caminho do arquivo inspecionado e como reconciliar. Ver ckey simétrica e assimétrica.
environment:aceito na instância como sinônimo deenvironment_overrides:. Era a grafia que operadores escreviam e o runner ignorava em silêncio.
Corrigido
Falha por secret obrigatório passa a dizer ONDE o bundle foi procurado. A mensagem informava que faltava valor, mas não se o runner deixara de encontrar um bundle que o operador acredita existir — falhas diferentes com o mesmo sintoma. Agora lista os caminhos candidatos na ordem de resolução, com a origem de cada um, e marca quais existem no disco.
O cabeçalho reflete o que foi verificado: havendo bundle, aponta o conteúdo; não havendo, diz que nenhum foi encontrado. Ver Secrets não resolvidos.
Seis classes de erro deixaram de cair em texto cru (várias em inglês): falha ao decifrar bundle, repositório inacessível,
version.fieldinexistente, repositório sem tags, branch inexistente e instância inexistente — esta passando a listar as instâncias declaradas.
Deprecado
source: {type: dist}— não usar em deploys novos; prefiratype: branch+branch: dist. Nada muda de comportamento e não há data de remoção. Ver Recursos deprecados.
[2.68.0] - 2026-08-05 — Labels CCS obrigatorias no deploy; campo `labels:` funciona
Adicionado
Labels de identificacao CCS em todo container. O runner passa a emitir
ccs.systems/type(derivada desystem:),ccs.systems/env(derivada da instancia) eccs.systems/uidno caminho de deploy principal — antes so o fluxo de staging as aplicava.Campo
labels:no.deploy.yml, repassado ao container. Ate a v2.66.0 era ignorado em silencio; na v2.67.0 passou a avisar; agora funciona.Quatro labels sao reservadas e nao podem ser sobrescritas:
managed-by,project,instanceeversion. O runner le as quatro de volta para casar container com app, varrer orfaos e adotar container perdido. Umversionsobrescrito faria o runner remover o container recem-implantado. Tentativa de sobrescrever e ignorada com aviso.
Corrigido
runner preflightvalidava o manifesto errado em app com multiplos manifestos. Ele assumiapull/.deploy.ymlenquanto o deploy resolve pelo caminho registrado no state, entao emitia parecer confiante sobre um arquivo diferente do que seria deployado — aprovando sem olhar o que importa, ou reprovando app saudavel. Como o preflight sai com codigo nao-zero emrede serve de gate em CI, o impacto era direto.
Notas
ccs.systems/uidrecebe o nome da app. Quem declararuidemlabels:sobrescreve.ccs.systems/enve omitida quando a instancia nao mapeia paradev|prod|staging|shared(ex:v1,homolog). Label ausente e melhor que label mentindo para quem filtra.
[2.67.0] - 2026-08-05 — Campo desconhecido no manifesto deixa de passar em silencio
Adicionado
Campo desconhecido no
.deploy.ymlgera aviso em vez de ser ignorado. Todos sao coletados de uma vez, com sugestao de correcao quando possivel:2 campo(s) desconhecido(s) no .deploy.yml — IGNORADOS pelo runner: - repo -> voce quis dizer `github.repo`? - labels -> nao suportado nesta versaoO dano era silencioso e real: apps declaravam blocos inteiros que nunca chegaram a container nenhum, e ninguem percebia por falta de aviso.
Esta versao apenas avisa. Bloquear e uma fase futura — quem tem manifesto antigo tem tempo de migrar.
runner preflightganhou dois componentes:env_drift— divergencia entre o.env/.secretsefetivo e oenvironment:do manifesto. Divergencia de valor ewarn(o.envvencer o manifesto e o contrato documentado);PORTdivergente ered, porque o Traefik roteia para a porta do manifesto enquanto o app escuta a do.env— resulta em 503 com container saudavel.manifest_sync— compara o.deploy.ymlcanonico com o dopull/. A comparacao e semantica (ordem de chave e comentario nao disparam alarme) e a mensagem diz qual vence no proximo deploy.
[2.66.0] - 2026-08-05 — Wizard deixa de descartar respostas; `.env` das apps em 0600
Adicionado
Navegacao "voltar" no wizard.
ESCvolta uma pergunta (repetido, volta mais) e as perguntas seguintes vem pre-preenchidas com o que ja havia sido respondido. Respondernno "Confirma e salva?" abre um menu de edicao por campo, em vez de descartar as respostas e obrigar a recomecar.ESCe a unica tecla usada:Backspaceja apaga caractere e a seta-para-cima e o historico do campo — sequestrar qualquer uma quebraria a edicao normal.
Alterado
ESCno "Confirma e salva?" cancela o wizard, em vez de voltar uma pergunta. E o unico ponto ondeESCdescarta respostas — aquele confirm e a fronteira em que o wizard comeca a escrever em disco. Para corrigir um campo a partir do preview, usen.
Corrigido
.envdas apps agora nasce0600(era0644, legivel por qualquer usuario do sistema), em todos os pontos de escrita, incluindo o deploy de staging. O arquivo acumula chaves herdadas de template, incluindo placeholders deSECRET_KEY,JWT_SECRET_KEYeSMTP_PASSWORD. Nao ha custo operacional: quem le e o runner e o Docker, ambos como root.Mensagem do backoff de deploy dizia
waiting for new push, sugerindo que so um commit novo destravaria. O backoff afeta apenas o cron: umrunner deploy <app>manual nunca foi bloqueado e, ao ter sucesso, zera o contador. A mensagem agora nomeia as duas saidas.
[2.65.0] - 2026-08-05 — `secrets:` deixa de implicar obrigatorio; `required:` vira o gate unico
Alterado
secrets:significa "e sensivel", nao "e obrigatorio". Declarar uma chave sobsecrets:bloqueava o deploy com valor vazio — falso positivo em segredo legitimamente vazio (por exemploREDIS_PASSWORDnum Redis sem auth). O unico contorno era mover a chave paraenvironment:, o que a tira do.secrets(0600, cifrado) e a joga no.env: a saida de emergencia levava a menos seguranca. Agora secret vazio gera aviso, nao bloqueio.required:e o gate unico de obrigatoriedade, com flag dedicada--allow-missing-required(em vez do--insecure, que desliga todas as protecoes).
Adicionado
required:por instancia, com uma regra: declarou = essa e a lista final; nao declarou = herda a base.YAML na instancia efeito (ausente) ou required:vazio (null)herda a base required: []nenhuma obrigacao required: [A, B]exatamente A e B (substitui) Quando a lista da instancia omite chaves da base, o deploy avisa: a substituicao e intencional, o enfraquecimento nao pode ser silencioso.
runner addrecusa manifesto seminstances:, que registrava um app impossivel de deployar — e o erro so aparecia comandos depois, citando um nome de instancia que nao existe no manifesto do operador.
Corrigido
- Troca silenciosa de instancia. Com o state pedindo uma instancia inexistente e o manifesto tendo default, o runner trocava de instancia sem alarde. State pedindo
homolognum manifesto so comproductionpublicava producao em silencio. Agora eWARNnomeando as duas e sugerindorunner edit --instance. - O gate de
required:nao cobria o cron. Era checado so no comandorunner deploy;fetch --deploy(caminho desassistido) nao checava nada. O gate foi para o pipeline, cobrindo manual, cron e PR. - Falha de pre-flight reembalada como falha de container — o erro subia como "container could not start" e arquivava log de um container que nunca existiu. Agora roda antes de qualquer acao de container.
[2.64.0] - 2026-08-03 — Fingerprint de token utilizavel; custodia visivel
Adicionado
runner tokens listmostra o fingerprint completo. A lista identificava tokens por um valor que nao dava para digitar (a forma abreviada com...), e nenhum comando aceitava aquele identificador. Agora ha coluna com a forma completa, no humano e no--json(fingerprint_full).--token-fromaceita fingerprint alem de nome de app, com match exato — forma abreviada e prefixos sao recusados com erro que ensina o formato. O token sai selado de um state e entra selado no outro, sem virar plaintext.unregisteravisa ao remover a ultima copia de um token. O token vive copiado no state de cada app (nao ha vault central), entao remover a ultima portadora apagava o valor em silencio. O aviso sugere--token-fromoustate backup.
[2.63.0] - 2026-08-02 — `fetch` para de mostrar manifesto velho; `--local` zero-download
Corrigido
runner fetchagora reparseia o manifesto cacheado. A deteccao de update nao tocava em disco: o.deploy.ymlda raiz do app (e portantorunner manifest <app>) continuava mostrando a config antiga depois de um fetch que ja reportava o commit novo. O refresh so acontecia quando um deploy rodava — dando a impressao de "atualizou sozinho minutos depois" e divergindo entre apps.- Manifesto que nao parseia no head remoto nao substitui um cache valido.
- Apps geradas de compose nao sao sobrescritas (o manifesto delas e gerenciado por
runner reconcile).
Adicionado
runner deploy --localagora e zero-download. Antes,--localmantinha o.deploy.ymlda raiz soberano mas ainda rodava o git pull. Agora o pull e pulado por completo e o deploy usa os artefatos ja em disco. Sempull/(ou com ele vazio), erro clarono_local_artifactsem vez de deployar o nada.
[2.62.0] - 2026-08-02 — Secrets assimetricos; middlewares do Traefik sobrevivem ao redeploy
Adicionado
Esquema assimetrico de secrets, valor a valor. Arquivo textual com a chave legivel e so o valor cifrado, uma linha por variavel. Alterar um secret nao abre nem recifra os demais (diff de uma linha). A chave privada fica no servidor e a publica vai no header, entao
runner env setcifra sem acesso a privada. Cada valor carrega um envelope validado no decrypt: ciphertext copiado de outra app, instancia ou variavel e rejeitado. Suporta valores multilinha (PEM), que o.envnao comportava.Registro com
runner add --ckey v2; migracao comrunner secrets migrate. Apps legados seguem inalterados. Ver ckey simetrica e assimetrica.Passthrough de middlewares e
tls.options. Blocorouting:cujos refs o runner apenas repassa aos routers gerados:routing: middlewares: [vpn-only@file, cloudflare-only@file] tls_options: mtls-aop@fileOs objetos continuam definidos pelo operador no Traefik; o runner so referencia. Com isso, protecoes de infra (mTLS, IP allowlist) sobrevivem ao redeploy em vez de sumirem quando os routers sao regenerados.
runner preflightavisa quando um ref@filenao existe no Traefik.
Corrigido
--slugnao sobrevivia ao deploy. O override valia so no registro; o deploy relia o manifesto do repo e voltava ao valor original, fazendo dois apps do mesmo repo gerarem o mesmo nome de router (colisao no Traefik).- Arquivo de secrets assimetrico nascia
644e so virava600no primeiroenv set. - Erro de app ambiguo listava um identificador identico entre dois registros do mesmo repo. Agora lista o nome do app, unico e digitavel.
[2.61.0] - 2026-07-31 — scrub de arquivos sensíveis + skip-last + Traefik imperativo
Adicionado
- Scrub de artefatos sensíveis no deploy:
.env*,.secrets*,.deploy.yml,docker-compose*, configs de CI (.github/,.gitlab-ci.yml,Jenkinsfile…), material de chave (*.pem/*.key/*.enc…) e configs de credencial (.npmrc,.netrc…) são excluídos automaticamente do artefato deployado — bots varrem esses paths. Fecha bind-mount (IMAGE) e contexto de build (BUILD) num mecanismo só;Dockerfile*preservado. Escape hatch por app:security.allow_sensitive: [nomes]no.deploy.yml. runner fetch <app> --skip-last: marca a alteração pendente como considerada sem deployar. Novo push volta a "disponível";deploy --forcedeploya o pulado. Ver runner fetch.- Roteamento Traefik imperativo (modo
local): a fase ROUTING descobre o Traefik em execução e usa o dynamic_dir que ele lê (warn em divergência), validaentrypoints/cert_resolvercontra o static config real e usa o nome real do container. Mododocker_labelssegue suportado, com alerta recomendando o modo dynamic.
[2.60.1] - 2026-07-29 — `edit --branch` volta a funcionar no layout `pull/`
Corrigido
runner edit <app> --branch <b>abortava com "No git repository found" em todo app registrado no layout v1.2.3+: a validação procurava.gitna raiz do app (/data/apps/<app>), mas o clone vive em<app>/pull/. Na prática não havia como apontar um app já registrado para outra branch (ex.: migrar demainparadist) semunregister+add. Ver runner edit.- Troca de branch falhava em clone single-branch. O
pull/nasce declone --branch X --depth 1, cujoremote.origin.fetchcobre só a branch rastreada;git fetch origin <outra>atualizava apenas oFETCH_HEADe oreset --hard origin/<outra>seguinte morria em "unknown revision". O fetch doedite o do deploy passam a usar refspec explícito+refs/heads/<b>:refs/remotes/origin/<b>. --pulldeixava o clone na branch antiga quando o worktree estava sujo — o state diziadiste o clone continuava emmain(apontando pro commit certo, o que tornava o sintoma difícil de ler). Agora écheckout --force -B <b> origin/<b>.
[2.60.0] - 2026-07-28 — `set-ckey --generate` headless
Adicionado
runner set-ckey <app> --generate: gera e anexa a ckey sem prompt (chamaensure_ckey), idempotente, com envelope JSONckey.ensure. Permite provisionar app nova em automação, sem wizard interativo.
[2.59.0] - 2026-07-28 — `ensure_ckey` no deploy (failover de ckey)
Adicionado
- Todo deploy garante a ckey antes de montar o bundle (
ensure_ckey, com failover local viaattach_ckey_to_app, idempotente). Mata a classe de falha "deploy sem ckey" em app registrada sem--ckey.
[2.58.0] - 2026-07-24 — `fetch --deploy` detecta tags (canal de release por tag)
Adicionado
fetch --deployagora detecta tags novas para instânciassource: type: tag— completa a feature da v2.57.0. Glob (v*) e vazio funcionam como canal de release automático (tag nova em commit novo → deploy sozinho); pin (v1.2.2) nunca auto-deploya. Ver Deploy a partir de git tag.
[2.57.0] - 2026-07-24 — deploy a partir de git tag (`source: type: tag`)
Adicionado
source: type: tag: deploya o código de uma git tag resolvida relativa a uma branch (só tags alcançáveis por ela). Um campotagcom 3 modos — glob (v*→ última que casa), pin (v1.2.2→ exata), vazio (última por data) — maissort: semver|date. Versão do deploy = nome da tag. Ver Anatomia do .deploy.yml.Escopo (v2.57.0): deploy manual. O auto-deploy no cron chegou na v2.58.0 (ver acima).
[2.55.2] - 2026-07-24 — default de instância determinístico
Corrigido
instancesno.deploy.ymlvirouIndexMap(eraHashMap), preservando a ordem do YAML. Quando não háproductionnemstate.instance, o default de instância passa a ser a primeira instância declarada — determinístico, resolvendo a nota conhecida da v2.55.1.
[2.55.1] - 2026-07-24 — `runner deploy` honra a instância registrada
Corrigido
runner deployerunner deploy --allagora honramstate.instance(a instância com que o app foi registrado viarunner add --instance <N>), igual aorunner fetch --deploy. Antes os deploys manuais miravam sempre a instância default do manifesto. Ordem de resolução documentada em cada comando (deploy, deploy-all, fetch). Ver também Dois deploys da mesma branch.
Nota conhecida (resolvida na v2.55.2)
- Default de instância sem
productionera não-determinístico nesta versão (instanceseraHashMap). A v2.55.2 troca porIndexMape o default passa a ser a primeira instância declarada no YAML. Recomendação continua: declararproduction, registrar com--instance, ou usar um manifesto por deploy.
[2.55.0] - 2026-07-22 — hierarquia de secrets (`secret.enc > env > deploy.yml`) + diagnosis envelope
Adicionado
- Envelope terminal com
diagnosisestruturado (data.after.diagnosis) em falhas de deploy no modo-o json, incluindo aborts precoces (secrets/preflight) — consumers exibem causa raiz estruturada em vez de só stderr.
Corrigido
- Secrets shadowing: placeholders
{{::KEY}}sem valor não são mais materializados como linhaKEY=vazia (Fix A), e o merge do bundle não deixa uma linha local vazia sombrear o valor real dosecrets.enc(Fix B). Garante a hierarquiasecret.enc > env > deploy.yml. Ver Hierarquia env/secrets. - Envelope
successreflete o resultado real do deploy, não o mero fato do comando ter rodado.
[2.54.0] - 2026-07-22 — `--skip-health` no deploy/add + secrets mascarados no wizard
Adicionado
--skip-healthemrunner deployerunner add --deploy: pula a validacao de saude so naquela invocacao, equivalente ahealthcheck: mode: skip_checkporem sem tocar no manifesto do repo. Para o caso em que o container sobe mas ainda nao ha Traefik/healthcheck valido no server — a app e declarada OK sem gating, sobe, e um redeploy futuro (ja com o ambiente pronto) volta a validar a saude normalmente, sem reinstalar. Emite o evento de notificacaoHealthSkipped. Veja `runner deploy` e Modos de healthcheck.
Corrigido
- Inputs de
secrets:no wizard do primeiro deploy agora entram mascarados: os prompts de secret resolvidos como"{{::...}}"eram lidos em texto puro, ecoando o valor no terminal/scrollback. Agora entram como***(nunca ecoam). Campos deenvironment:(plain) seguem em texto claro. Veja Classificacao de env/secrets.
[2.53.4] - 2026-07-18 — cleanup --build-cache respeita o config
Corrigido
cleanup --build-cachepassa a respeitar o config: o CLI tinha um72hhardcoded que ignoravadocker.auto_prune_build_cache. Quem configurasse24h(ouall) ainda via72hno comando manual — e como o filtro de idade nao pega cache recente, o operador rodava o cleanup e "nao recuperava nada". Precedencia agora: flag explicita → config →72h.
[2.53.3] - 2026-07-18 — build cache limitado por tamanho + pressao de disco
Adicionado
- Teto de tamanho do build cache (
docker.auto_prune_build_cache_max, default5GB): o auto-prune pos-build aplicadocker builder prune --max-used-space, mantendo o cache recente (builds seguem rapidos) e evictando o excedente. Corrige o buraco do filtro de idade —72hso evicta cache mais VELHO que a janela, entao em box que deploya varias vezes por dia o cache crescia sem limite (~1 GB por deploy) ate encher o disco. - Gatilho de pressao de disco (
docker.build_cache_min_free_mb, default2048): se o livre cair abaixo do minimo ANTES de um build, o runner poda o cache antes de buildar. O prune normal roda so depois do build.0desabilita. cleanup --imagestambem aplica o teto do cache: na pratica o build cache costuma ser maior que as imagens.
[2.53.2] - 2026-07-18 — secrets show/pull per-instância/per-target
Corrigido
secrets show/pullenxergam apps per-instância/per-target (#58): liam.runner/secrets.encroot hardcoded, invisível a apps cujo bundle ésecrets.<instance>.enc(ousecrets.<instance>.<target>.enc) — retornavam vazio. Agora resolvem o bundle real (per-target → per-instance → legado).
[2.53.1] - 2026-07-18 — namespace do bundle por target
Adicionado
- Namespace do bundle de secrets por target (#50): novo campo
target:noconfig.yml(opt-in). Quando setado, o bundle virasecrets.<instance>.<target>.enc— cada server tem seu slot, cifrado com a sua ckey. Resolve o clobbering quando 2 servers deployam a mesma instância do mesmo app. Read prefere per-target → per-instance → legado (self-healing). Semtarget:= flat inalterado.
[2.53.0] - 2026-07-17 — rotate-secret manual + rede de recuperação
Adicionado
ckeys rotate-secret --to-plain(#43): modo manual — rotaciona a ckey de um app a partir de uma ckey nova em texto puro via STDIN (CCS ou usuário), sem passar pelo cofre. Re-encode atômico (failover/rollback), push estrito, prova JSON; a ckey nunca é ecoada/logada.- Rede de recuperação + auto-heal: o rotate preserva a ckey anterior (
state.ckey_previous). Se o bundle novo não chegou ao repo (--no-push, push falho, drift por pull), o deploy auto-heala — reconhece 'repo pré-rotação', não aborta, e re-pusha o bundle local. Sem perda de conteúdo. runner validate --app <name>: valida app registrado por nome.
Corrigido
- rotate-secret mira o bundle que o deploy lê (
secrets.<instance>.encquando existe, root senão).
[2.52.0] - 2026-07-17 — deploy --local
Adicionado
deploy --local(#57): deploya o estado que esta no disco sem re-sincronizar do repo — pula ogit resetdopull/e o refresh do.deploy.ymla partir dopull/. Fecha a perda silenciosa em que uma edicao local era revertida pra versao do repo nodeploy --force. Naquele deploy o disco e soberano (nao persiste); combina com--force, nao combina com--all. Complementa oenv_sync: skip(que cobre so.env/.secrets).
[2.51.0] - 2026-07-17 — Rede Docker IPv6/dual-stack no manifesto
Adicionado
- Rede Docker IPv6/dual-stack a partir do manifesto (#56):
networks:aceita a forma-objeto comipv6:/subnet_v6:/subnet_v4:/bridge_name:. Quando a rede nao existe, o runner fazdocker network create --ipv6 --subnet ...(ULA RFC 4193 deterministica do nome sesubnet_v6omitido → portavel entre hosts). Resolve o caso "alcancar destino IPv6-only" (Supabase) de forma declarativa, semdocker network createmanual. Drift deEnableIPv6→ WARN (nunca recria). O runner nao escreve sysctl — so lenet.ipv6.conf.all.forwardingpra um aviso; o Docker habilita o forwarding ao subir a bridge--ipv6. Retrocompativel:networks:como lista de strings segue identico.
[2.50.2] - 2026-07-17 — cleanup: --project não gateia fases globais
Corrigido
cleanup: over-gating do--project(#49): fases globais explícitas (--expired/--orphans/--prs/--images/--build-cache) não gateiam mais a confirmação multi-alvo —cleanup --project X --imagesem cron (sem--yes) abortava por uma confirmação que não protegia nada. Com fases globais,--projecté ignorado com WARN explicativo; o gate e a fase de versões do projeto só entram com--projectsozinho ou--reset-root.
[2.50.1] - 2026-07-17 — Panics de runtime aninhado no CLI do traefik
Corrigido
traefik doctor/validate/weightnão panicam mais (#55): os handlers sync carregam runtimes tokio internos (resolver DNS do TM-DNS, cliente HTTP blocking do TraefikApi) e eram chamados do contexto async do main — oblock_oninterno panicava (Cannot start a runtime from within a runtime) e o teardown panicava com exit 101. Agora rodam sobblock_in_place. Efeito prático: o doctor completo executa (o check TM-DNS nunca tinha conseguido rodar) edoctor --check/validate --applysaem com exit 0.
[2.50.0] - 2026-07-17 — Incidentes de produção: prune de órfãs no Traefik, logs de container falho, mask de secrets
Adicionado
- Prune automático de services órfãs no Traefik (#52): no modo file-provider, cada deploy blue-green deixava a service da versão anterior órfã no arquivo dinâmico — com healthCheck ativo batendo num container que não existe mais, acumulando indefinidamente. Após promover a versão nova, o runner agora poda automaticamente as órfãs do próprio app (escopo
{project}-{instance}-; nunca toca services de outros apps ou arquivos do operador). Novo checkTM-ORPHANSnorunner traefik doctorlista órfãs em qualquer arquivo. - Logs do container falho arquivados antes do rollback (#54): quando o deploy falha no start ou no health check, o rollback destruía o container levando junto os logs que diagnosticariam a falha. Agora as últimas 200 linhas de
docker logssão salvas em<logs_dir>/failures/<app>/<version>_<timestamp>.logantes do remove; a mensagem de erro e orunner debug last-errorapontam pro arquivo. traefik validate --verify none|loaded|http: aplica ops sem consultar a API do Traefik (--verify none) — útil pra poda manual de órfãs acumuladas (- op: service_delete/name: <service>).- Snapshot com source por instância (#53): a lista flat
instances[]dorunner snapshotcarregaslug/repo/branch/vcs_providerdenormalizados do project (a coluna SOURCE do CCS dependia disso).
Corrigido
- Vazamento de segredo no aviso de divergência (#52):
runner env set --secretimprimia o valor do secret em claro no avisomanifesto declara X mas o runtime usa Y. Valores de keys sensíveis (do.secrets, declaradas emsecrets:ou com nome secret-like) agora saem mascarados (prefixo de 4 chars + tamanho). deploy --forcerespeitapull_policy: never(#52): em modo IMAGE com imagem carregada localmente (docker load), o force pulava direto pro pull que sempre falhava com WARN ruidoso. Agora skipa o pull com log informativo.- Ops de delete serializáveis:
service_delete/router_deletenão eram expressáveis notraefik validate --ops-file(falha de serialização); corrigido.
[2.49.0] - 2026-07-12 — Origem (trigger/actor) nos deploy-events + canary history
Adicionado
- Origem do deploy (
trigger/actor): o histórico de deploy passa a registrar quem/de onde o deploy veio, dimensão distinta dos logs de execução (result/error).DeployTrigger=cli(deploy manual),cron(fetch --deployagendado),api(dispatch do CCS),webhookouunknown. Novas flags--trigger/--actoremdeploy(defaultcli) efetch(defaultcron); a rota de API de deploy aceitatrigger/actorno body. Ologs historycarrega os campos no envelope e ganha a coluna ORIGIN. Backward-compat via serde default (audit antigo →unknown). - Origem também nos deploy-events: além do audit (
deploy-audit.json), oDeployEvent(started/success/failure) emdeploy-events.jsonlcarregatrigger/actor. Como é desse arquivo que o CCS renderiza o histórico (viarunner_audit_read), a origem chega ao front sem mudança no client. runner canary history: novo subcomando que expõe a timeline de mudanças de peso do canary (envelopecanary.history) — contraparte dologs historydo deploy.
[2.48.0] - 2026-07-09 — Preflight, manifest-stdin e secrets local-only
Adicionado
runner preflight <app>(P4): readiness de deploy por componente (manifest / secrets / ports / dns / image-args) num relatório estruturado verde/amarelo/vermelho — sem subir container. É o gate do passo 4 do Deploy Wizard do CCS. Agrega checks já existentes (validate_project,validate_secrets_have_values,ports_in_use,dns_check::check_one,preview_run_args) sem reimplementar nada.--jsonemite{app, overall, components[]}pro consumo programático. Exit code != 0 apenas quando ooveralléred(problema bloqueante);warnegreensão sucesso.runner add --manifest-stdin(R1): ingere um.deploy.ymlgerado externamente (deploy wizard do CCS) via stdin. O runner clona o repo, grava o manifesto ingerido no clone antes da validação e reusa o fluxo existente (load → validação →bootstrap_push→ deploy). Incompatível com--from-compose; stdin vazio falha commanifest_stdin_empty. Sem a flag, o comportamento é idêntico ao repo-based atual.instances.<nome>.local_secrets(#44): marca uma instância como local-only — o deploy não pusha osecrets.encpro repo nem sobrescreve.env/.secretslocais a partir do remoto. Resolve o caso "secret local numa instância, mesmo repo sobe normal noutra" sem.gitignore.runner secrets push <app> --force(#44): o comandosecrets pushganhou a flag--force, que passa a forçar commit+push dosecrets.encsob demanda (ignora o short-circuit de "não mudou"); aceita-i <instância>e-x(match exato); recusado em instânciaslocal_secrets. Sem--force,secrets pushmantém o comportamento antigo de bootstrap.
Corrigido
self-updateestourava memória (OOM) em box memory-tight: o download usavaresponse.bytes(), que carregava o binário inteiro (~15MB) no heap e abortava em máquinas apertadas sem swap. Agora o download é streamado direto pro disco viaresponse.chunk()(memória constante) e a assinatura minisign é verificada por mmap (páginas file-backed, reclamáveis, sem heap). Mesma classe do fix aplicado no ccs-client.
[2.47.0] - 2026-07-08 — Resolução inteligente de referência de app (#46)
Adicionado
- Resolução multi-identidade de
<app>(#46): todo comando que recebe<app>passa a aceitar slug do projeto,project/system, nome exibido norunner list, repo (owner/nameou basename) e domínio da instância como sinônimos — case-insensitive,-/_equivalentes. Índice construído em memória por invocação a partir do que já é lido hoje (apps/*/.deploy.yml), sem cache (custo desprezível). - Exato vence, senão radical por prefixo-de-segmento: se a referência é o nome exato
de um app, resolve só ele; senão casa por prefixo de segmento (
ccs-→ todos osccs-*). Comandos de leitura (status,versions) agem em todos os alvos casados; comandos que mutam (deploy,fetch,restart,reconcile,cleanup,unregister) listam os alvos e pedem confirmação (ou--yesem automação/CI); comandos de alvo único (edit,set-ckey,mkey,rotate-secret,adopt) exigem match único e erram listando candidatos se houver ambiguidade. -x/--exact: flag global que força match exato, desabilitando o radical.- Batch tolerante a falha: num multi-alvo mutante, a falha de um alvo é registrada e o comando segue pros demais, agregando o resultado (exit≠0 se algum falhou); alvo único propaga o erro cru (comportamento inalterado).
- Fora de escopo (v1): nome de container não é identidade de resolução — ficaria
preso a uma consulta Docker por invocação (assíncrono, custo desproporcional pro
hot-path síncrono de hoje). Registrado como follow-up (#48) — a
resolver com um cache de containers fora do hot-path (ex: alimentado por um watcher de
docker events), nunca uma query Docker síncrona por chamada.
[2.46.1] - 2026-07-08 — Correção de flake nos testes (test-only)
Corrigido
- Flake de isolamento nos testes de custódia de ckey (#45):
ckey_custody/set_ckey/fetchcada um tinha umstatic MKEY_ENV_LOCKpor-função — mutexes distintos que não serializavam entre módulos, então o override concorrente deRUNNER_KEYS_DIR(env var global ao processo) apontava o master key pro dir errado e causava falha espúria não-determinística nocargo testparalelo (e no gate domake publish). Consolidado num lock único compartilhado (crypto::keys_dir_env_lock/with_isolated_keys_dir). Validado com 21 rodadas paralelas sem falha.
Nota: release test-only — o binário é idêntico ao v2.46.0 (as mudanças vivem só em
#[cfg(test)]). Sobe apenas pra registrar o fix na CDN/releases.
[2.46.0] - 2026-07-07 — Integração CCS: force/fetch via task + source no snapshot
Adicionado
- Snapshot reporta origem por project (#40): cada
SnapshotProjectagora incluirepo,branchevcs_provider(derivados degithub.repo/dist_branchdo.deploy.yml;vcs_providerassumegithub). Acende a badge SOURCE no CCS (/cicd/deploys) linkando pro repositório, sem mudança no front. Aditivo — campos omitidos quando não há blocogithub:. ckeys rotate-secret --to-sealed(Fase 1, #43 — fail-closed): primitivo de custódia de ckey selada do cofre — lê o blob selado do stdin, valida context (anti-replay porgeneration+ project/target), re-encode atômico journaled two-phase dosecrets.ence push estrito com prova JSON. A verificação de assinatura + unwrap da ckey ficam atrás de um trait com stub fail-closed (aborta "contrato do cofre pendente") até o contrato cofre↔runner fechar (Fase 2). O comando existe no binário mas nunca instala uma ckey não-verificada — seguro por construção. State ganhouckey_generation(default 0).
Corrigido
- Handlers MCP repassam
--forceefetchaceitaapp(#41):handle_deploy/handle_fetchagora montam os comandos completos que a CLI já suportava (deploy <app> --force,fetch [<app>] --deploy --force), destravando os botões force/fetch-per-app do CCS. Removido o-i <instance>inválido dohandle_deploy(oDeployda CLI não tem esse flag — o deploy via task estava sendo montado com um argumento que o clap rejeita). Schemas dos MCP toolsrunner_deploy/runner_fetchatualizados.
[2.45.0] - 2026-07-05 — Notificações per-app por template
Adicionado
- Bloco
notify:no.deploy.yml— notificações per-app, declarativas, por template. Cada app assina eventos de deploy (deploy_success/deploy_failure/deploy_rollback/deploy_started) e dispara uma mensagem/requisição montada por template. - Sintaxe
{{::var}}(a mesma dos secrets) interpola contexto do deploy (repository_name,deploy_result_status,deploy_version,deploy_error,instance_name,instance_domain,event,deploy_id,project) + env/secrets da app. - Canais nomeados no
config.yml(notify.channels:):webhook(requisição HTTP estruturada method/url/headers/body),telegram,smtp(email via lettre/rustls). Extensível via o traitNotifyChannel. - Gate
when:opcional por subscrição (==/!=); semwhen, sempre dispara. - Fire-and-forget: o dispatch é best-effort, com timeout (10s por envio, 30s total), silent failover (canal ausente/falho → só loga) e NUNCA bloqueia/falha o deploy. Aditivo ao notify global do servidor.
[2.44.2] - 2026-07-03 — Corretude de secrets no funil (R1/R2/A1/A2)
Corrigido
- Secret
generateinválido no render do core — o wizard web/MCP/CCS emitiam{GENERATE:hex:32}(sem$), formato que o resolvedor NÃO reconhece → o secret de produção virava a string literal. Agora${GENERATE:hex:64}(paridade CLI). - Secret
literalvazava em plaintext no.deploy.ymlcommitável (render do core) → agora vira o placeholder{{::KEY}}(valor pedido no registro). ${GENERATE:alnum:N}do picker CLI não tinha braço no resolvedor → mesma classe do bug acima, viva no CLI. Corrigido +generate_alnum.- Contrato único
${GENERATE:...}no core (runner_core::generate): o wizard-mcp deixa de reimplementar umcontains("GENERATE")que abençoava formatos quebrados; o dry-run agora reprova um secret que não resolve.
[2.44.1] - 2026-07-03 — Polimento do multi-ambiente
Corrigido
- Bootstrap por instância —
runner add(bootstrap_push) semeava osecrets.encglobal no upstream mesmo para apps per-instância; agora semeiasecrets.<instance>.enc(mesmo failover da leitura/commit). Fecha o último canto assimétrico do modelo por instância. add --instancesurfa falha de provisionamento — se oset-ckeyautomático no registro falhar, o operador vê um aviso claro (⚠ + comando sugerido), não só uma linha no log.set-ckeytrima a ckey — espaços em volta não vazam mais para encrypt/fingerprint (evita "parece que bate mas não bate")..gitignoreignora.worktrees/(higiene).
[2.44.0] - 2026-07-02 — Secrets isolados por instância (multi-ambiente)
Adicionado
- Secrets por instância com failover retrocompatível. Cada instância pode
ter seu próprio
secrets.<instância>.enc, cifrado com ckey própria — staging e produção deixam de compartilhar credenciais. A resolução no deploy segue um failover de dois eixos: Eixo A (arquivo)config.bundleexplícito →secrets.<instância>.enc(por convenção de nome) →secrets.encglobal; Eixo B (config ausente) degrada pro providerBundlepadrão da instância declarada no deploy. Ausência nunca é erro. App antigo com sósecrets.encse comporta byte-idêntico ao anterior. - Bloco
config:por instância no.deploy.yml(source: bundle | vault,bundle,vault: {addr, path, mode}). O wizard emite o bloco só quando não-default; orunner addgera manifest sem bloco (secrets por convenção). Lido no deploy pelo reader do client (InstanceDeployConfig). - Escrita/commit de secret por instância — o deploy (phase 1.6 + commit)
agora grava/commita
secrets.<instance>.enc(o mesmo arquivo que a leitura resolve), não maissecrets.encglobal. Fecha a assimetria read/write:runner secrets setnum servidor persiste no arquivo por-instância no próximo deploy. Regra do nome centralizada emtarget_enc_filename. Retrocompat: app legado mantémsecrets.enc. runner set-ckey --instance— provisiona a ckey de uma instância cifrando.env/.secretsemsecrets.<instance>.enc(ckey via--ckeyou stdin sem eco) e registrando-a no vault/state local.runner add --instance --ckeyprovisiona o.encno registro. Separação por servidor;source: vaultno deploy segue com erro loud (resolução via vault fora de escopo).- Abstração
ConfigProviderno core:BundleProvider(default, zero-dependência) decifra o.encda instância;VaultProvider(opcional) faz poll HTTP (KV v2) ou lê arquivo JSON local. Token do vault sempre deRUNNER_VAULT_TOKEN, nunca do manifest.
Segurança / build
- Vault é feature opt-in de compilação (
--features vaultnorunner-core, ativada pelo client). Sem a feature,ureqnão é linkado — o core segue wasm-safe para as SPAs e o default permanece zero-dependência. source: vaultdá erro loud no deploy (resolução via vault ainda não habilitada; provisionamento pendente de spec própria) — evita deploy silencioso sem secrets.
Notas
- Fronteira dev↔prod recai só nos secrets (ckey independente por instância);
config não-secreta (
environment_overrides) fica em texto plano no manifest. - Pendente: provisionamento de ckey independente por instância (
set-ckey) .
[2.43.9] - 2026-06-25 — assets chown por uid:gid numérico (não pelo nome)
Corrigido
runner assets uploade chown de state/cache no deploy agora resolvem o user do container para uid:gid numérico antes dochownno host. Antes usavam{{.Config.User}}direto (ex.:ccs) — um nome que não existe no host — entãochown ccsfalhava e o asset (ex.: secret PEM) ficavaroot:600, ilegível pelo uid do processo do container. Agora: nomes são resolvidos viaid(docker execno container rodando;docker runna imagem no deploy); specs numéricos (999,999:986) usados direto; root/0 → skip. Helper puroclassify_chown_user+ testes unitários.
Contexto
Quebrava o provisionamento de assets.secrets em qualquer imagem com USER
nominal — inclusive a rehidratação host-fresh do Regra 9 (export/import de
projeto CICD), onde o secret precisa ser legível pelo container no primeiro boot.
[2.43.3] - 2026-06-24 — anexar ckey a app já registrado (sem re-add)
Adicionado
runner deploy <app> --ckey [<chave>]— anexa um ckey a um app que ainda não tem, sem precisar re-registrar.--ckeysozinho gera um AES-256-GCM novo;--ckey <chave>usa a informada. No-op (com aviso) se o app já tem ckey; nunca sobrescreve. Grava nostate.ckey, então destravaenv set --secret/secrets set. Não combina com--all.runner env set <app> KEY VALUE --secret— em modo interativo, quando o app não tem ckey, agora pergunta se quer gerar+registrar um na hora (em vez de só errar).y→ gera, anexa e salva o secret encriptado. JSON/não-interativo mantêm o erro estruturado (E7002) — CI/scripts inalterados.
Contexto
Antes, secrets num app registrado sem ckey exigiam re-registrar (runner add --ckey, destrutivo) ou copiar de um peer. O runner ckeys add só criava no
vault, sem gravar no state do app — por isso não destravava o env set --secret.
[2.43.2] - 2026-06-24 — redesenho do template de notificação de deploy
Modificado
- Template das mensagens de deploy reformulado. Header lidera com a ação
(
🚀 Deploy Iniciado/✅ Deploy Concluído/❌ Deploy Falhou), seguido deDeploy: <app>,Projeto: <grupo>(opcional),🌐 Instance: <inst>. Removido o badge de cor de ambiente (🟢/⚪ PROD) — a instância textual substitui. - Commits agora aparecem na mensagem de INÍCIO (não mais na de conclusão),
pra diagnosticar o que está sendo deployado antes de um possível erro. A captura
de commit passou a rodar antes do
deploy_started. - Linha de branch simplificada:
🌿 Branch: <branch>(+· PR #Nquando detectado). Removido o⚠️ direct pushe o ícone de alerta, que confundiam. - Conclusão/Falha enxutas e auto-contidas: repetem nome/projeto/instância/versão,
com
⏱️ duração; falha traz⚠️ Erro:. Não repetem domínio, branch nem commits.
Adicionado
project_group: campo opcional no.deploy.yml(cliente/produto guarda-chuva), exibido comoProjeto:quando presente. Não confundir comproject(nome do app).- Campo
project_groupno payload JSON do evento (webhook/audit) quando setado.
[2.43.1] - 2026-06-24 — fix: notify silenciava deploy_success/failure com `_`/`*` no subject
Corrigido
- Notificação de
deploy_success/deploy_failuresumia quando o subject do commit tinha_,*,`ou[(snake_case é comuníssimo). Oto_markdown()interpolava subject/autor crus, fora de code span, e o canal Telegram envia comparse_mode=Markdown→ a API respondia HTTP 400 "can't parse entities" e o erro era engolido pelolet _ = …send(). Como odeploy_startedé emitido antes da captura de commit (sem linha de commit), ele sempre chegava — daí o sintoma "iniciado chega, concluído some". O evento continuava gravado no audit log; só o chat sumia.- Fix: subjects, autores e o resumo de erro agora vão em code span
(
`...`), onde os metacaracteres são literais; crases internas são sanitizadas pra não fechar o span. Não era race/timeout nem backend-specific (o report suspeitava) — qualquer app com_/*no subject era afetado.
- Fix: subjects, autores e o resumo de erro agora vão em code span
(
[2.43.0] - 2026-06-11 — notificações por classe (3 gates) + health/init-log opcionais
Modificado
- Notificações agora são por classe.
notify.enabled(defaulttrue) controla a classe deploy (iniciado/concluído/falha);notify.extras.*(todos defaultfalse, opt-in) controlam os extras. - ⚠️ Mudança de comportamento:
verbose_errors(era defaulttrue, top-level) virounotify.extras.verbose_errors(defaultfalse, opt-in). O resumo de batch ("Runner deploy batch") virounotify.extras.batch_summary(opt-in — antes saía sempre que havia canal).notify_successfoi removido: o "Deploy concluído" agora seguenotify.enabled. Configs antigos parseiam sem erro (as chaves antigas viram inertes).
Adicionado
notify.extras.health_status(opt-in): status do health check pós-deploy.notify.extras.init_log(opt-in): log do boot do container até o health. ⚠️ Pode conter segredos impressos no boot — habilite deliberadamente.
[2.42.4] - 2026-06-23 — Docs: flag notify_success no MANUAL
Documentation
- MANUAL: documenta
notify.notify_success(sucesso opt-in, defaultfalse) — bloco de config de referência, tabela de eventos notificados (Deploy Concluídomarcado como opt-in) e subseção dedicada explicando quando ativar e que o evento de sucesso continua sempre no audit log (deploy-events.jsonl). Sem mudança de código — o flag foi entregue no v2.42.3; esta release apenas publica a doc na CDN (GitOps lê docs da tag).
[2.42.3] - 2026-06-23 — Notify: commits reais no deploy + sucesso opt-in
Fixed
- Notify reportava o auto-commit de secrets em vez do commit real do dev:
apps com encrypted secrets (
.runner/secrets.enc+ ckey) sempre mostravam📝 Último: <hash> — chore: update encrypted secretsno Deploy Concluído. O runner faz um auto-commitchore: update encrypted secretsnopull/depois do deploy, ecollect_commit_infoliagit log -1(HEAD) → pegava o auto-commit. Agora os commits-bookkeeping do runner (is_runner_secret_commit) são filtrados do range<deploy anterior>..HEAD, e a notificação lista os commits reais reconhecidos no deploy (📝 Commits:— até 5, com trailer… +N mais) em vez de uma única linha. Apps sem encrypted secrets não eram afetadas e seguem iguais.
Added
notify.notify_success(defaultfalse): a notificação de chat por app no deploy bem-sucedido (✅ Deploy concluído) agora é opt-in. Ela duplica o resumo🚀 Runner deploy batche vira ruído em deploys de um dev só; só compensa em deploys multi-app/compose ou com muitas execuções acumuladas. Falhas continuam sempre notificando, e o evento de sucesso continua gravado no audit log (deploy-events.jsonl) independentemente do setting.- Campo novo no
deploy-events.jsonl/webhook JSON:commits(lista dos commits reais do range, newest-first, capada).
- Campo novo no
[2.42.0] - 2026-06-12 — Network DNS aliases pra integração CCS↔DA + `runner env unset`
Added
- Network DNS aliases gerenciados via
.deploy.yml(Plan A da integração CCS↔DeployAlly): a chavenetworks:agora aceita três formatos intercambiáveis — string list legada (- public), lista de objetos (- name: X, aliases: [...]) e mapping (X: { aliases: [...] }). Todos convergem internamente pro mesmonetwork_aliases. O top-levelnetwork_aliases:(v2.24.0+) continua funcionando e mescla com dedup quando coexiste com aliases inline. - Switch determinístico de alias no blue-green: após health check passar,
o runner faz
docker network disconnectexplícito no container antigo pra cada network com aliases declarados e confirma viadocker network inspectque o alias resolve pro novo container. Elimina a janela em que ambos containers (verde + azul) compartilhavam o alias e a resolução DNS interna era não-determinística. Falha no disconnect/verificação emite warn mas não aborta — o stop/remove do container antigo logo em seguida desconecta implicitamente pelo Docker.- Log por switch:
network alias 'deployally_sys' switched on 'internal_ccs_da': <old_container> -> <new_container>. - Motivador: integração CCS→DA precisa de nome estável (
deployally_sys) independente da versão deployada (deployally_sys_production_v0-12-N).
- Log por switch:
- Backward-compat estrito: configs com
networks:em string-list seguem o caminho de parsing rápido (sem normalização), preservando linha/coluna em erros YAML — só configs com formato inline disparam a pré-passagem. - Testes: 7 novos cobrindo legacy string list, inline objects, mapping form,
merge com top-level legado, default omitido, mistura string+object e
contrato end-to-end yaml→
--network-alias.
Added
runner env unset <app> <KEY>: remove uma chave do.env(default) ou do.secrets(--secret). Casos comuns: dropar um var plaintext ou um duplicado que também está cifrado no.secrets(onde.secretsvence em runtime, então a app segue funcionando). Com ckey, re-encripta o.runner/secrets.enca partir dos arquivos pós-remoção e propaga pro remoto viacommit_and_push_secrets.- Durabilidade: o push pro remoto é necessário — deploy Phase 1.4 sincroniza remote→local e re-mescla chaves só-remotas; uma remoção apenas local reapareceria. Se o bundle commitar local mas não propagar (push falho), emite AVISO explícito (detecta commit à frente do upstream) pra evitar falsa sensação de remoção durável.
- Mesma rede de segurança do
set: hidrata local do bundle ANTES de mutar, então nunca regrava um arquivo "magro". - Fecha o gap em que o runner só tinha
set/loade não dava pra remover uma chave sem editar/data/appsà mão.
[2.40.0] - 2026-06-11 — traefik-manager refactor (Plans 1-8) + integração pós-v2.39 (PR #17)
Fixes de integração (review do merge com a dev v2.32→v2.39)
- Sanitização de versão unificada (
naming.rs): nomes de leaf services sempre sanitizam versões pontuadas (1.2.3→1-2-3) — deploy, weight, promote, abort e scheduler usam o mesmo helper (antes: deploy cru × weight sanitizado = weighted children dangling e queda de tráfego) - healthCheck restaurado nos services gerados (path, interval 10s, timeout 5s, hostname=domínio da rota — preserva o fix v2.23.15 de hostnames com underscore); multi-route ganha hostname por rota
- Render escopado por arquivo: o plano renderizava o índice fundido inteiro em cada arquivo (routers de apps vizinhos vazavam; primeiro deploy de instância nova falhava com "concurrent edit detected")
destroycompleto: remove TODOS os routers/services da instância (multi-route incluso) e o arquivo dinâmico, ANTES de remover containers (antes: preflight falhava depois dos containers já removidos = destroy parcial)- docker_labels multi-route: índice de rota consistente entre router/service/port labels (antes: rota ≥2 referenciava service de índice errado = rota morta)
- Scheduler de canary usa
traefik.manager.state_dirconfigurado (lock compartilhado com o deploy); labelentrypointsvazia omitida; parity tests comparam healthCheck
Refactor completo do gerenciamento de Traefik no runner. Substitui as ~2020
LOC de geradores monolíticos em src/traefik/ por um workspace crate
dedicado (crates/traefik-manager/) com:
Arquitetura
- Operações tipadas: enum
Opcobre 16 variantes (DomainAdd/DomainRemove/DomainReplace, RouterCreate/RouterDelete/RouterEditRule, ServiceSetTarget/ServiceCreate/ServiceWeights/ServiceBlueGreenInit/ServicePromote, MiddlewareAttach/MiddlewareDetach, StaticSet, Adopt, FailoverSwitch). - Plan + Transaction: cada operação produz um
Planrevisável;Transaction.applyfaz 10 steps (lock, snapshot, parse, op-apply, validate, render, write, restart-if-needed, verify, commit). Rollback automático em qualquer falha. - Traits para boundaries: FileSource, DockerClient, TraefikApi, DnsResolver, Lock, Clock, RestartStrategy — testabilidade alta sem touchar fs/docker real.
- Validators: ConfigIndex com lookups (router/service/middleware/by_host/domain_index)
- dangling_refs + duplicates.
- Doctor: 12 checks padronizados (TM-API, TM-DYNDIR, TM-YAML, TM-DUPL, TM-DANGLING, TM-ENTRYPOINT, TM-PROVIDER, TM-NETWORK, TM-LOADED, TM-TLS, TM-LOGS, TM-DNS — este último via hickory-resolver).
Pipeline migration
update_traefik(Local + External + DockerLabels modes) — migrado paraOps.deploy_canary,promote_canary,destroy_instance— todos via traefik-manager.canary_scheduler.update_traefik_weights+abort— idem.- Status/snapshot/misc read paths usam o novo crate.
CLI
Read commands (Plan 5)
runner traefik ganha 13 subcomandos read-only:
info,scan,schema <resource>routers list,services list,middlewares list,domains listinspect domain|router|service|filedoctor,history,diffapi(ping/routers/services/certs/overview)
Write commands (Plan 7)
16 subcomandos de escrita, todos dry-run por default, mutação só com --apply:
domain-add,domain-remove,domain-replace --scope single|allrouter-create,router-delete,router-edit-ruleservice-target,service-weight,service-bluegreen-init,service-promotemiddleware-attach,middleware-detachstatic get,static set-log-level,static set-access-log,static set-entry-point,static set-apiadopt,rollback,failover-switchvalidate --applyagora aplica ops (era plan-only)
Saídas estruturadas
- Envelope JSON padronizado:
{ success, timestamp, schema_version, client, command, data, error }. - 21 JSON Schemas v1 reais para cada resource de escrita (placeholders read continuam para vNext).
- WriteOutcome:
DryRun | Applied | Failedcommode/plan/report/response.
Cleanup
src/traefik/deletado (1115 + 331 + 574 ≈ 2020 LOC removidos).- 3 validators de docker-fs (TraefikPathCheck + validate_dynamic_dir_*)
migrados para
traefik_manager::io::docker_fs.
Testes
- 215 unit/integration tests em
traefik-manager. - Suites de paridade:
parity_local,parity_canary,parity_multigarantem semântica idêntica aos geradores legacy (validação byte-equivalent pós-normalização YAML). - Doctests verdes.
- Runner: 764 tests (drop natural com a saída do
src/traefik/tests.rs).
Compatibilidade
- Sem breaking changes nos artefatos produzidos (parity tests garantem).
- Comandos antigos não afetados — apenas o codepath interno trocou.
[2.39.0] - 2026-06-10 — `runner adopt`: recovery de registro a partir de container vivo (#36)
Item G do #35 (P7 do incidente #32). Substitui ~10 comandos manuais por app (playbook Fase 2) por um comando só, no caso residual sem nenhum backup (disco zerado, server novo, migração) com containers ainda vivos.
Adicionado
runner adopt <app> --from-container <c>(--jsonsuportado):- Captura as envs do container vivo via Docker API e filtra os defaults da imagem (diff container × imagem: só fica o que foi injetado no run)
- Classificação secret/plain via
secret_detect: confiança ≥75% →.secrets, ≤35% →.env, faixa intermediária pergunta (com--yes, vai pra.secrets, conservador) - Modo cold (sem registro): exige
--repo/--branch/--ckey, registra pelo mesmo caminho dorunner add(sem deploy; containers não são tocados) - Modo warm (registro existe): reusa repo/token/ckey do state, só recaptura envs e re-encripta o bundle (flags de registro ignoradas com warning)
- Grava
.env(0644)/.secrets(0600) atômico, re-encripta.runner/secrets.ence pusha o bundle (mesma regra dosecrets reset-bundle) - State:
status: running+current_versiondo labelccs.systems/version - Guards: container parado/inexistente → erro; warm com captura vazia → erro
(não sobrescreve arquivos existentes com vazio); label
ccs.systems/projectdivergente → confirmação;--jsoné não-interativo por contrato (exige--yesse houver prompt pendente) - Valores de env nunca aparecem em logs ou na tela sem máscara;
--jsonemite um único objeto (sem valores)
Modificado
fetch::add_appesecrets reset-bundleganharam modoquietinterno (usado pelo adopt pra manter o contrato de JSON único); comportamento dos comandos existentes inalterado. Push de bundle falho em modo quiet emite warning explícito.
[2.38.0] - 2026-06-10 — modelo `assets:` Fase 2: CLI + lifecycle + export (#28)
Completa o modelo de ativos persistentes (F1 no v2.37.0).
Adicionado
- CLI
runner assets(todos com--json):list <app>— categorias, source, mount, tamanho, status derequired(✓/✗ vazio)upload <app> --to <source> <files...>— cópia server-local pro asset dir + chown automático (User do container corrente, non-root) + audit;--toprecisa ser um source declarado no manifestosnapshot <app> [-f file]/restore <app> [-i file]— tar dedata/(primitiva pra BackupAlly e migração entre servidores); restore confirma overwrite;--jsonno snapshot exige-fclean <app> --category cache— limpa caches respeitandottl:como filtro de idade (Nd/Nh; ttl inválido tipo30merra em vez de virar wipe total)
runner export --include-assets— embute o tar dedata/(assets_tar_b64) no bundle; falha de tar é erro explícito (não null silencioso).
Modificado
- ⚠️
unregisteragora protege assets: comdata/não-vazio e sem--force, bloqueia mostrando o tamanho + hint de snapshot (antes--jsonprosseguia silenciosamente e apagava tudo). Fluxos automatizados devem usar--force(como já era a prática).
[2.37.0] - 2026-06-10 — modelo `assets:` Fase 1: ativos persistentes com categorias (#28)
Originado no report da migração do Capital28 (#28): cada app usava um padrão diferente de persistência, os mounts automáticos eram não-documentados, e não havia validação de asset crítico (app subia healthy sem os arquivos OAuth — quebrava na 1ª chamada).
Adicionado
- Campo
assets:no manifesto — 4 categorias semânticas (açúcar tipado sobrevolumes:, que segue válido):secrets(operador sobe 1x; RO;required: trueaborta o deploy se vazio, com odocs:na mensagem)state(app escreve; RW;backup:é metadado pro BackupAlly — o runner não agenda)cache(ephemeral;ttl:é metadado pra limpeza na Fase 2)config(arquivo do repo na versão deployada; RO; atualiza no deploy)- Itens vivem em
<app>/data/<source>(subtree já preservado pelo ciclo de vida).
- Validação: schema (paths relativos sem
.., mounts absolutos, colisões) noadd/validate;requiredno pre-flight do deploy (pós-refresh do manifesto — valida o que vai rodar de fato). - chown automático: dirs novos de
state/cacherecebem o owner doUserda imagem (non-root) — apps non-root não falham mais silenciosamente em escrita. - Sem overlap: mounts de assets entram nas destinations explícitas (auto-mounts
data/logs/keysnão duplicam). - Drift:
assets:é operator-owned — não dispara manifest drift. - Docs: MANUAL/
generate-config/content ganham a seção de assets, a documentação dos mounts automáticosdata/logs/keys(antes implícitos) e a tabela de ciclo de vida verificada no código — incluindo:unregisterapagadata/(snapshot antes!);reset --hardpreservadata/(o help antigo era enganoso).
Fase 2 (anunciada, v2.38.0)
CLI runner assets list/upload/snapshot/restore/clean, proteção no unregister, export --include-assets.
[2.36.1] - 2026-06-10 — Spec 3: fecha o débito de observabilidade + data-safety (#35)
Corrigido
runner statusnão mascara mais state corrompido: erro deload_state(corrompido sem backup íntegro) vira aviso vermelho explícito (⚠ state: corrompido/ilegível — runner state restore <app>) + campo aditivostate_errorno JSON, em vez de contadores zerados como se fossem dados.- Service-API
GET /appsnão dropa app com state ilegível: emite a entrada comstatus: "state_corrupt"(campos null) — o dashboard vê o app com problema em vez de contagem menor silenciosa. - Canary state com write atômico (
atomic_write): fecha o últimofs::writetruncável de state (mesma classe do incidente #32). - Reconcile sem churn de metadata: regeneração que só muda o header (
generated_at/runner_version) agora é "Already in sync" — sem write, sem commit, sem push (antes poluía o repo com commits de metadata a cada reconcile no-op). - PR deploy grava o state
@pr-N(qualifier threaded pelodeploy()→mark_as_deployed_with_commit/mark_as_deploy_failed): sucesso, falha (retry counters agora funcionam pra PR) e short-circuit (re-deploy de PR healthy) — o state default de produção não é mais poluído com versão/contadores de PR.
Decisão registrada
- Alertas de disco-baixo permanecem em severidade Error (wontfix deliberado — disco cheio foi o gatilho do incidente #32).
[2.36.0] - 2026-06-10 — detecção do "PORT fantasma" (env vs manifesto) (#34)
Incidente sdr.emploia.com.br: PORT=3001 no .env sobrescrevia o port: 3000 do manifesto — container healthy (healthcheck usa $PORT), Traefik roteando pra 3000 → 503, nenhuma superfície revelava. Agora é detectado em 3 camadas.
Adicionado
runner env setavisa na introdução: ao gravar uma key que diverge do manifesto, imprime⚠ AVISOimediato no stderr (ex.: PORT ≠port:→ explica que o Traefik continuará roteando pro manifesto). Nunca bloqueia.runner doctor: checkenv_mismatch— varre os apps registrados comparando o env efetivo de runtime (.env+.secrets, secrets vencem) com o manifesto: mismatch de PORT (alertável) e divergência de valor em keys declaradas noenvironment:(informativo). Placeholders${VAR}/vazios ignorados.doctor --watchalerta PORT mismatch no Telegram: o check entra no fingerprint do watcher (mesma classe do disco baixo) — exit 2 + alerta com dedup; recovery quando corrigido.- Helper
environment::env_manifest_mismatches(fonte única dos 3 pontos).
[2.35.3] - 2026-06-10 — Caso 2B (build.context) + state_health no list (#33)
Bugs encontrados pelo E2E do ciclo de drift no tester (que validou o loop do #33 #2 ponta a ponta: drift → reconcile --apply --yes → push upstream → fetch → deploy sem re-drift).
Corrigido
- #33 Caso 2B —
runner addresolviabuild.contextrelativo ao manifesto (desde v2.26.1), enquanto o deploy copia o repo inteiro prasrc/<ver>/e odocker buildresolve contra essa raiz. A divergência rejeitava o output do próprio wizard (context: ./webemweb/.deploy.yml→ procuravaweb/web,[semantic_build_context_missing]) e forçava hand-fixes (context: .) que então quebravam no build. O validator doadd(e o advisory de monorepo) agora resolvem contra a raiz do clone, a mesma semântica do deploy. Manifestos hand-fixados pra.devem voltar ao valor repo-root-relative (runner reconcile --applyregenera certo). runner list:state_health/repoliam o diretório de state errado (heurística "irmão do apps_dir" →/data/state) em instalação padrão (apps em/data/apps, state em/opt/runner/state) — todo app aparecia com⚠ state:missingfalso erepo: —. Agora usa ostate_dir()do config.
[2.35.2] - 2026-06-09 — drift/reconcile: persiste, confirma, sem false-positive (#33)
Adicionado / Modificado
- #2 —
reconcile --applyagora faz commit + push do manifesto reconciliado pro repo, reusando o caminho de auth dosecrets reset-bundle. Fecha o loop de drift (antes o cronfetchre-puxava o manifesto velho e o drift voltava). Flags--push/--no-push+ configreconcile_auto_push.- ⚠️ Mudança de comportamento:
reconcile_auto_pushé defaulttrue— o auto-reconcile do cron passa a pushar manifestos pro repo automaticamente (escrita git não-assistida). Desligue comreconcile_auto_push: falseno config ou--no-push.
- ⚠️ Mudança de comportamento:
- #4 —
reconcile --applyconfirma antes de sobrescrever (mostra o diff +[y/N]);--yes/--forcepula. Não-interativo (cron/pipe) sem--yesnão escreve (evita reverter customização manual silenciosamente). Modo JSON exige--yes(prompt corromperia o stdout). - #1 — elimina drift false-positive em apps re-registrados de versões antigas:
collect_manifest_driftagora canonicalizabuild.args(${VAR}/vazio → equivalente), igual aoenvironment(#10C). Antes, manifesto antigo (KEY: "") vs mapper novo (KEY: ${VAR}) driftava sendo runtime-equivalente.
Nota
- #3 (mostrar QUAL field difere) já estava implementado para manifest drift desde antes (bloco "Fields divergentes"); nada a fazer.
[2.35.1] - 2026-06-09 — publica pubkey de assinatura no CDN (#31)
Adicionado
- #31 —
pubkey.pub(Ed25519/minisign) publicada no CDN em{arch}/pubkey.pub+ root (via GitOpsscripts:). FingerprintF4C67EA1D42B3D9A. Consumers (CCSsubsystem_manage, e os clients implant/deployally/backupally) podem restaurar a verificação de assinatura de binários do runner. A pubkey é a mesma já embarcada (RUNNER_RELEASE_PUBKEY); um teste garante quepubkey.pubnunca diverge dela. README documenta verificação manual (minisign -Vp) + política de rotação. Sem mudança de comportamento do binário.
[2.35.0] - 2026-06-09 — robustez + observabilidade de state (#32 Spec 1+2)
Resposta ao incidente crítico de 2026-06-08 (#32): o disco encheu (100%, ENOSPC) durante o fetch --deploy do cron e o write não-atômico truncou os 10 state files a 0 bytes — ckeys/tokens/metadados perdidos (recovery só foi possível por fontes redundantes; não havia backup de state). Este release entrega a camada de data-safety (Spec 1) e a de observabilidade honesta (Spec 2): doctor/list que revelam a perda em vez de mascarar, watcher proativo de alerta, e playbook de recovery.
Adicionado
- Write atômico de state (
atomic_write: tempfile no mesmo FS →fsync→rename→fsyncdo dir). Em ENOSPC/EIO o arquivo original fica intacto. Substitui ofs::writetruncante emwrite_state_file— todos ossave_stateherdam. Sozinho já teria evitado 100% do incidente. - Backup rolante com integridade + cadeia de rollback: antes de cada swap, a versão anterior íntegra é preservada em
state/.backups/{app}/{UTC}-{sha12}.yml(sha no nome = auto-verificação), dedupe por sha, retençãostate_backups_keep(default 5).load_state: primário corrompido → caminha a cadeia, restaura o 1º íntegro + warn; cadeia toda ruim → erro alto (nunca mais retorna default mascarando perda). - Comandos
runner state backup/restore[--all] /list-backups(todos com--json).restoreé não-destrutivo (salva o primário atual na cadeia antes de sobrescrever) e idempotente;--allsó mexe em primários corrompidos e falha com exit-code não-zero se nada foi restaurado. - Pre-flight de disco no
fetch(state_min_free_mb, default 500): aborta o ciclo comdisk_space_lowantes de tentar escrever num disco crítico. - Config:
state_backups_keepestate_min_free_mb(defaults seguros; configs existentes seguem funcionando).
Modificado
- Lock combinado por app (
AppLock) substitui oDeployLock: um único lock em{state_dir}/{app}.lockcobre deploy e mutações de state (cronfetch,edit,pause/unpause,ckeys copy,deploy), via o helperupdate_state(closure). Reentrante process-global por path (seguro sob o runtime tokio multi-thread — o deploy segura o lock através de.awaits e re-entra nomark_as_deployedsem stall). Serializa a corrida cron-vs-manual; cross-process viaflock. - #29 —
docker-buildhonra oWORKDIRdo Dockerfile (não injeta mais-w /app); modo image (bind-mount) inalterado.
Observabilidade (Spec 2)
runner doctorhonesto: novo checkstate_filesdetecta state vazio/corrompido (o sintoma que o doctor antes não via) e informa se há backup íntegro; o checkmkeyvalida a master key (32 bytes) no path real (/opt/runner/.keys/master.key, viacrypto::master_key_path()) — antes reportava "master key missing" falso checando o lugar errado; + check de disco.runner listnão-mascarante: campo aditivostate_health(ok|empty|corrupt|missing) + marcador⚠ state:quando o state não bate com o container rodando. Antes,listmostrava tudo "OK" pelo container e escondia a perda.runner doctor --watch(cron one-shot): roda os checks e alerta no Telegram (HealthAlert) na transição ok→problema, com dedup por fingerprint (.doctor-watch-state.json, re-alerta a cadawatch_realert_hours, default 6h), recovery no problema→ok, exit≠0 em problema. Reusa oNotifyManagerdo deploy.- Timestamps humanos com rótulo
UTCexplícito (a confusão de TZ dificultou o diagnóstico do incidente). - Playbook de recovery (Restaurar em novo servidor): detecção via doctor/list/watch + recovery via
runner state restore(ou Fase 1/2/3 quando não há backup). ui::tablemede largura visível (ANSI-aware) — alinha células coloridas (marcador novo + coluna STATUS).
[2.34.8] - 2026-06-09 — quick wins do triage de issues (#30, #19)
Corrigido
- #30 —
runner -V/--versionagora é só o semver (runner 2.34.8), sem o sufixo(hash)que fazia parsers last-token (ex: CCSsubsystem_manage) lerem o commit em vez da versão e quebrarem o update. Alinha com os outros binários CCS (ccs/implant/deployally). A proveniência de build (tag:/commit:) continua emrunner versione emrunner version --json. - #19 — pre-flight do deploy valida
pull/(onde ficam Dockerfile/package.json/src) em vez da raiz gerenciada do app. Antes, todo deploytype: docker-build(bind-mount) reportava erros estruturais espúrios (build_dockerfile_missing, version-file) que entravam só na contagem do resumo e confundiam o operador. O deploy sempre funcionou; era ruído de validação.
[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