Changelog

Todas as mudancas notaveis neste projeto sao documentadas neste arquivo.

O formato e baseado em Keep a Changelog, e este projeto adere ao Semantic Versioning.

[2.34.7] - 2026-06-09

Modificado

  • O bloco Next steps: do output adota o mesmo guia de trilho dos blocos de detalhe.

[2.34.6] - 2026-06-09 — output mais legivel

Modificado

  • Erros saem com a tag ERROR (vermelho), alinhada as linhas INFO/ WARN. Antes o erro era impresso como Error: ... 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 -V exibe 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

  • --json por 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 --deploy

Migracao

Atualizar crons:

# /etc/cron.d/runner
*/5 * * * * /opt/runner/runner -o json fetch --deploy >> /var/log/runner-fetch.json 2>&1

Scripts 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 --version reportava 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 para environment:, mesmo com KEY no nome. Nomes de secret vao para secrets:.
  • 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 wizard em repo com Dockerfile agora gera type: docker-build + build:. Antes gerava image: com a imagem base e nao buildava a app.
  • O .deploy.yml gerado pelo wizard agora inclui o bloco version:. Antes o deploy falhava com version.file is empty.
  • runner add nao 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 repetido

Manifest `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_PASSWORD

runner 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 --repo clone, 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.
  • --resume flag pra retomar wizard interrompido (lê .runner/wizard-answers.yml v2).

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 add força force_overwrite=true no wizard interno (clone fresh sempre substitui).

Compatibilidade

  • --accept-defaults continua funcionando (non-interactive).
  • --from-compose continua funcionando.
  • runner add interno usa o novo wizard sem regressão.
  • Web wizard schema preservado.
  • wizard-answers.yml v1 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 em src/config/mod.rs) — resolve {{::Var?default}} em cada domain contra .env + .secrets da app. Routes que resolvem pra string vazia são DROPADAS em vez de gerar Host(\{{::AppDomain?}}`)` literal que nunca matcha.
  • Traefik routing (pipeline/container.rs TraefikMode::Local e External) — agora chama effective_routes_resolved em vez de effective_routes. Antes, app com domain: "{{::AppDomain?}}" sem AppDomain no .env deployava 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_domain resolve 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-N ou pr_number no state → PR-N).
  • Linha 🌿 branch mostrando 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 GitHub
    • PR #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 Xh só para PR deploys com TTL ativo. Calculado em runtime a partir de state.created_at + state.ttl_hours.
  • Novo evento PrExpired disparado pelo runner cleanup --expired quando 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ído em vez de Deploy 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_version ganha o guard contra version.file vazio / . / ./ que fetch/git.rs::extract_version_with_diagnostic já tinha desde v2.23.19. Antes, app_path.join("pull").join("") resolvia pra pull/ (diretório), exists() retornava true, e read_to_string quebrava 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 gerar file: "" em manifests novos. Agora escreve file: "package.json" (o default do serde) mesmo quando source: git-commit. Previne o footgun se o operator editar source depois.

Como atualizar

runner self-update
runner -V   # confirma 2.30.11
runner deploy <app> --force   # tenta de novo

Bugs fechados

  • Report do customer Agency-Arms: deploy de sweepstake-backend (Laravel) falhava em version detection independente do version.source configurado.

[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_vars no wizard mapper — bare ${VAR} agora preserva o literal quando shell env nao tem o valor, em vez de substituir por string vazia (o que docker compose faz no parse-time). Razao: runner tem ciclo de vida por-app (runner env set / runner secrets set setam 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 (virava args: 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) — aplica expand_compose_vars() em cada valor de build.args, paridade com a v2.26.3 (Bug 11) que ja cobria environment:. Novos manifests gerados pelo runner wizard --from-compose saem com ${VAR} substituido pelos valores do shell env.
  • Runtime expansion em build.args (container.rs::start_container) — apos a resolucao de {{::Var}} existente, aplica tambem expand_compose_vars_from_app() (helper novo em environment::) 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

  1. {{::Var}} → estado do runner (.env/.secrets/CLI overrides) — pre-existente.
  2. ${VAR}.secrets > .env > shell env — novo em v2.26.9.
  3. 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.enc auto-renametry_restore_from_repo (chamado pelo runner add) agora renomeia o bundle indecifravel pra secrets.enc.orphan-<timestamp> quando a ckey nao bate. Pre-fix, o bundle ficava no lugar e as proximas chamadas runner secrets set/generate/paste falhavam com aead::Error (Bug 2 v2.26.1 so deixou a mensagem descritiva, sem auto-recovery). Plaintext .env/.secrets ficam intocados. Operator pode recuperar renomeando de volta se achar a ckey original.
  • git fetch propaga token via GitAuthHelperfetch_to_pull (Phase 0 do deploy) e fetch_single_app (CLI runner fetch, caminho legado com app_path/.git) agora usam git_fetch_secure(auth, ...) que injeta GIT_ASKPASS. Pre-fix, o pull/.git/config tinha o placeholder https://x-access-token@github.com/... sem password real → GIT_TERMINAL_PROMPT=0 fazia fetch de repo privado falhar silenciosamente. Resolucao de token: state.github_token → decrypt → fallback GITHUB_TOKEN env.

Adicionado

  • git_fetch_secure(auth, repo_path, branch) em pipeline/prepare.rs — variante autenticada do git_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.yml muda (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-roda map_service com o wizard-answers.yml atual, compara cada campo mapper-derivado do InstanceDeployConfig com 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 --apply

Campos 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 menciona networks + comando runner 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 --apply agora sobrevive ao git reset --hard que o proximo runner deploy faz em pull/. Pos-reset, se app_path/.deploy.yml tem structure_hash_v2 que bate com o fingerprint do compose atual, ele e copiado de volta sobre pull/<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 14runner deploy nao limpa mais pull/ no fim. Pre-fix, "Cleaned up pull/ directory" rodava sempre, quebrando o proximo runner reconcile (que precisa de pull/ pra ler o compose). Loop: deploy → pull gone → reconcile fails → fetch bootstraps slowly → repeat. Pull/ agora persiste entre deploys; fetch reusa via git 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 deploy retornar erro. Pre-fix, ficava UP segurando portas e bloqueava rollback pro docker 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.lock manualmente 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-compose traduz compose.services.X.command automaticamente. Container pipeline passa pro docker run apos 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 como docker compose faria 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, igual docker 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 sobrescrevem environment: (config-as-code, manifest) (Bug 13). Pre-fix, runner env set era no-op para qualquer chave declarada no manifest porque -e KEY=VAL sempre venceu --env-file. Manifest agora vale como default; operador override em prod ganha.
  • runner fetch resolve repo de state OR manifest (Bug 9, re-open) — apps type: image gerados pelo wizard frequentemente omitem github: no manifest (a imagem nao precisa de clone no build). --repo passado ao runner add escreve em state.repo, mas fetch ignorava isso e retornava no_git em silencio. Agora fetch resolve repo de state.repo OR .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: null agora usa pull/ inteiro (Bug 7) — pre-fix, image-mode apps com source_dir: null disparavam auto-detect que adivinhava errado (escolheu docs/ num repo com markdown na pasta, deixando src/<v>/ sem o codigo). Default agora = repo root, matching docker-compose. Heuristica antiga continua disponivel via source_dir: auto.

Corrigido

  • Volume bind path resolve a source_path (Bug 8) — ./livekit.prod.yaml:/etc/livekit.yaml agora aponta pra src/<instance>/<version>/livekit.prod.yaml (onde o source copy mora) quando o arquivo existe ali, com fallback pra app_path para runtime state dirs (./data, ./logs). Pre-fix, todos os caminhos relativos resolviam a app_path que esta vazio pos-copy — container recebia mount vazio.
  • Fetch fallback pra GITHUB_TOKEN env var (Bug 9, parcial) — quando state token e ausente ou indecifravel (apps registrados com --insecure, ou apos rotacao de mkey), fetch agora usa GITHUB_TOKEN do 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.yml agora valida build.dockerfile relativo ao subdir do manifest, nao ao clone root.
  • Bug 2: runner secrets set em bundle orfao (ckey rotacionada) agora imprime erro descritivo com 2 caminhos de recovery em vez de aead::Error cru.
  • Bug 3: runner fetch faz clone inicial de pull/ quando ausente (apps em estado no_git apos --insecure ou falha de initial_pull_clone).
  • Bug 4 (critical): runner reconcile --apply agora escreve em ambos app_path/.deploy.yml e pull/<manifest>, e refresh_manifest_from_pull faz skip quando os bytes batem. Pre-fix, o reconcile era silenciosamente desfeito pelo proximo deploy.
  • Bug 5 (critical): runner reconcile agora detecta a lista de compose files usada na geracao via wizard-answers.yml::compose_files. Apps gerados com wizard --from-compose -f a -f b re-mergem automaticamente sem operador passar -f de 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-compose agora aceita -f file.yml -f override.yml, com semantica de merge identica ao docker compose (override last wins, suporta !override, !reset).
  • Bug 2 (HALT total) — Halt eh agora per-service por default (fail-soft). Service risky com privileged: true nao bloqueia mais o manifest do service clean. Flag --strict restaura 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 ou all.

Adicionado

  • -f / --compose-file <PATH> repeatable na CLI. runner wizard --from-compose -f base.yml -f prod.yml mescla per compose-spec antes da discovery.
  • Auto-detect de docker-compose.override.{yml,yaml} ao lado da base: se existir e nenhum -f for 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 como privileged,sysctls,group_add ou all.
  • --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_halts e 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). Use compose_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):

  • agent com deploy.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 (formas single e {soft, hard}), read_only, security_opt, logging.driver/logging.options, hostname, dns, expose, stop_signal, stop_grace_period, pull_policy, platform. Bloco swarm deploy.resources.{limits,reservations} agora estruturado em vez de opaco.
  • Mapper: tradução completa pros blocos novos do .deploy.yml. Precedência: swarm deploy.resources.limits.{memory,cpus} vence mem_limit/cpus curtos quando ambos presentes.
  • no-new-privileges:true em security_opt é roteado pro campo bool security.no_new_privileges, não duplicado em security_opts.
  • GPU: deploy.resources.reservations.devices.driver: nvidia mapeia pra gpus: all (ou gpus: device=N quando count numérico).

Fingerprint v2

  • Novo structure_hash_v2 no header x-runner-source, substituindo structure_hash em 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: 256m vs memory: 512m) NÃO entram no hash, seguindo o princípio v1 de "estrutura entra, tuning sai".
  • check_compose_drift_or_halt faz dispatch automatico: lê structure_hash_v2 se presente, senão cai pro structure_hash legado.

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.yml autoradas à mão (sem x-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 Commands em cli/mod.rs ganhou um docstring de uma linha como primeira linha, seguido de blank line, depois detalhes/exemplos. Clap usa a primeira linha em -h e o bloco inteiro em --help.
  • --insecure global 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 validar resources/security/logging/... da v2.24.0 antes de pushar pro dist branch.
  • Flag --instance <name> pra forçar instance específica.
  • Flag --json pra output como JSON array.

Uso

runner inspect-args meu-app
runner inspect-args meu-app --instance staging
runner inspect-args meu-app --json

Internamente 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.
  • Lifecycleinit (default false), entrypoint, stop_signal, stop_grace_period, pull_policy (ausente = daemon default).
  • Network avançadohostname, dns, expose, network_aliases (map network→aliases).
  • Storage / Hardwaretmpfs, devices, gpus, platform.

Compatibilidade

  • Schema parsing: todos os campos novos com #[serde(default)] ou Option<T>. .deploy.yml antigo 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 run de 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.rs intacto — apps geradas via runner wizard --from-compose mantém structure_hash está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: bool no config.yml (default true). Apos a remocao das versoes antigas em apps build:, roda docker image prune -f. So remove untagged <none> sem container associado — seguro.
  • docker.auto_prune_build_cache: string no config.yml (default "72h"). Apos build-mode deploys, roda docker builder prune -a -f --filter until=72h. Aceita "72h", "168h", "7d", "all", ou vazio (desliga).
  • Logs Pruned dangling images — Total reclaimed space: XGB e Pruned 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=72h

Quem 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: null em apps deployedbuild_no_git_result em fetch/resolve.rs hardcodava current_version e container_version como None. Apps healthy reportavam null no log do cron pra ambos os campos, mesmo com runner list mostrando a versao correta. Agora popula do state.current_version e via docker inspect (get_running_container_version).
  • version config: cannot read '/data/apps/<app>/': Is a directoryextract_version_with_diagnostic em fetch/git.rs usava path.exists(), que aceita diretorios. Quando version.file no .deploy.yml era vazio ou ., o app_path.join("") resolvia pro proprio diretorio e o fs::read_to_string quebrava. Agora rejeita early com erro claro e usa path.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 tentava git ls-remote a cada 5min e falhava sem nenhuma acao corretiva. Adicionado contador consecutive_fetch_errors no state que incrementa em cada erro de fetch e zera em sucesso. Quando cruza o threshold runner_config.max_consecutive_fetch_errors (default 10), o app vira status: paused e e skipado pelo cron.

Adicionado

  • runner unpause <app> — clear do estado pausado. Reseta consecutive_fetch_errors e last_error, volta status pra active. Use depois de corrigir o problema upstream (repo movido, network voltou).
  • max_consecutive_fetch_errors no config.yml — threshold global pra auto-pause. Default 10. Set 0 desabilita.
  • error agora populado em no_git results — quando o ls-remote falha, a mensagem de erro aparece no error: do FetchResult e em state.last_error (antes ficava so no log via warn!).

[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_sync no .deploy.yml (per-app): controla se .env/.secrets sao regenerados a partir do secrets.enc remoto.
    • remote (default): remoto e soberano — hash diferente = regenera .env/.secrets
    • skip: local e soberano — nunca sobrescreve .env/.secrets por mudanca remota
  • env_sync_default no config.yml (global): define o default para todas as apps no servidor. Util para servidores de staging onde .env e ajustado manualmente.
  • Phase 1.4 (sync_secrets_from_repo): novo passo no pipeline entre template rendering e env provisioning. Compara SHA256 do secrets.enc remoto 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

  1. Per-app env_sync: skip no .deploy.yml sempre vence
  2. Se per-app e remote (default/omitido), usa env_sync_default do config.yml
  3. Se ambos omitidos, comportamento e remote (retrocompativel com v2.23.17 para apps sem secrets.enc no 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_dir como parâmetro (phase_8_push_secrets passa via RunnerConfig).
  • Carrega state file do app, decrypta github_token via mkey.
  • Constrói GitAuthHelper com esse token e chama auth.git_command(&["push"]) em vez de Command::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óximo runner deploy --force consegue 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.domain pró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 domain de labels Traefik Host(X) — precisa parser de rule mais cuidadoso (casos compostos Host(a) || Host(b)).
  • Heurísticas de guess_system pra "app" → front e imagens oven/bunfront.

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.yml

Implica --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.yml

Cobertura 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 estruturado

Fluxo: 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.yml fora 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-route

Backward 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: public

Resolve 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: false

E /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ções cleanup_*_phase + helpers
  • cmd_edit (58) → edit_apply_*, validate_branch_change, git_checkout_and_reset
  • cmd_ckeys (45) → 1 função por SecretsCommands variant
  • cmd_tokens (38) → cmd_tokens_list/test/reveal
  • cmd_add (37) → resolve_add_token/ckey, register_pr_app, find_parent_app_for_repo
  • cmd_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: fmt Display impls quebrados em 7 funções write_*
  • 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 = 16 no Cargo.toml — cold build multiarch ~46% mais rápido. Binário cresce ~2MB (trade-off aceitável).
  • sccache integrado no build_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). Agora rustls 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 arrastava crossbeam-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 por tokio::process built-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 perm 0640 em vez de 0644. 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_state corrige perm de files legacy (0644) pra 0640 na primeira leitura. Não loosen perms já mais restritivas — operador que manualmente setou 0600 manté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. Cada show/set/generate/delete/paste-encoded escreve uma linha em /opt/runner/logs/secrets-access.jsonl (perm 0640 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 keys registra apenas nomes, nunca valores. Auditável end-to-end com cat/jq sem vazar secrets. Separado de insecure-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 delete da última key falhava com No .env or .secrets content to encrypt. Causa: write_app_secrets tentava encriptar bundle vazio, e runner_core (corretamente) refuse. Agora detecta caso vazio e remove .runner/secrets.enc em vez de encriptar zero bytes. Próximo secrets set repopula 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-grupo secrets. Mesmo padrão do ckeys delete (v2.23.0). Procura a key em .env e .secrets, remove de onde encontrar, reescreve os 3 stores atomicamente. --force/-y/--yes skipa confirmation. Key ausente é no-op (exit 0).

    Resolve o resíduo TEST_CONVERGENCE_KEY=hello-2026 reportado 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ÍDO mas o domínio ficava 404 com cert default quando paths.traefik_dynamic apontava pra dir que o container Traefik não montava. Agora o runner inspeciona os mounts do traefik container 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/dynamic
  • Stores convergentes em secrets paste-encoded — antes, encodar secrets via paste-encoded atualizava só .runner/secrets.enc, mas o deploy lia .env/.secrets plaintext (stale) e sobrescrevia o bundle. Resultado: container subia com tudo vazio. Agora as 3 fontes são escritas atomicamente.

  • build.context honrado — antes era silenciosamente ignorado, runner sempre procurava Dockerfile no root e enviava o repo inteiro como contexto. Agora effective_context = source/<build.context> e o Dockerfile é resolvido relativo a ele (docker semantics standard).

  • Connection strings auto-detectadas como secretDATABASE_URL, DB_URI, POSTGRES_URL, MONGO_URI, REDIS_URL, AMQP_URL, SMTP_URL e variantes agora vão automaticamente pro .secrets cifrado em vez de vazarem pro .env plaintext. (BASE_URL/API_URL continuam tratados como públicos.)

Corrigido — altos

  • version.file aceita subpath confirmado via testes de regressão. Mensagem de erro agora mostra os 4 paths absolutos tentados pra diagnóstico imediato.

  • runner add faz clone inicial pra pull/ — antes app ficava em status: no_git permanente até primeiro deploy. Agora shallow clone (--depth=1) ao final de add_app, best-effort.

Adicionado

  • runner tokens reveal <app> --insecure — mirror do runner 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 push falhava em repos com .runner/ no .gitignore (template comum). Causa: o helper adicionava !.runner/secrets.enc como 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/.secrets no .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.enc se padrões exóticos de .gitignore ainda travarem o staging.


[2.23.1] - 2026-05-15 — `runner secrets push`

Adicionado

  • runner secrets push <app> — push manual de .deploy.yml + .runner/secrets.enc pro 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.enc apenas), .gitignore check, refuse se .runner/secrets.enc não puder ser produzido.

  • Auto-bootstrap no runner add — quando --ckey e token estão disponíveis e --deploy não foi passado, runner tenta o bootstrap_push ao final de add. Failure não falha o add — operador pode retry via runner 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 add não cria mais ckey órfã. Antes, runner ckeys add <project> seguido de runner add criava DUAS ckeys, a primeira ficava órfã sem como remover. Agora add_app consulta vault::ckeys::find_for_project() e:

    • Sem --ckey + vault tem uma → herda silenciosamente
    • Com --ckey + vault tem uma → warna + instrui delete
  • runner ckeys delete <id> novo subcommand. Completa o CRUD do vault.

  • runner secrets show header agora indica source: --- secrets (from .runner/secrets.enc) --- em vez de --- .secrets ---.

  • runner manifest --resolved renderiza {{::Var}} no .deploy.yml usando .env/.secrets/secrets.enc. Templates sem valor ficam como <UNRESOLVED:KEY>.

  • runner secrets set/generate/share substitui o paste-encoded --mode {manual,generate,shared} confuso. Antigo continua funcionando com deprecation warning.

  • runner generate-config agora emite secrets: block separado em vez de placeholder {{::KEY}} em environment:.

  • --yes / -y aliases pra --force em unregister, destroy, ckeys delete.

  • Mensagem misleading "pull/ on first fetch""on first deploy" (clone só acontece em runner 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.enc ser commitado. Bug de v2.22.6: pipeline encriptava .env/.secrets em Phase 1.6 e já fazia git push do .runner/secrets.enc — antes do pre-flight, build e health check. Quando o deploy falhava depois, o commit ficava no remote. O cron fetch --deploy detectava "novo commit", tentava deploy de novo, falhava, commitava… loop infinito.

    Fix: persist_to_repo quebrado em dois:

    • write_local_secrets_enc — escreve apenas app_path/.runner/secrets.enc localmente (Phase 1.6). Não toca git, então falha posterior não polui o repo.
    • commit_and_push_secrets — copia o blob pro pull/, 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ós runner 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 all lê 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 pra runner deploy --all. CCS e outros orquestradores chamam runner deploy-all --json esperando comando explícito; antes o runner só tinha deploy --all e o CCS quebrava com unrecognized subcommand. Output JSON inclui success, 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 export agora 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-sealed retornava null. 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/.secrets em /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-file ao docker), não candidatos a commit. Check agora skipa quando app_path/.git/ não existe.
  • persist_to_repo: o git add .runner/secrets.enc rodava em /data/apps/{app}/ (fatal: not a git repository). Agora redireciona pra pull/ (git working tree real). Auto-propagação de secrets.enc pro repo remoto via git push funciona — sem mais step manual de copiar via scp.

[2.22.5] - 2026-05-12 — `--build-cache` default

Corrigido

  • runner cleanup --images --build-cache sem valor agora usa default 72h (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.enc restore não provisionava templates do .deploy.yml. Quando runner add decifrava secrets.enc do repo, restaurava o .env antigo (que podia ter só PROJECT+INSTANCE) e skipava o env wizard. Agora roda em merge mode: adiciona keys do .deploy.yml environment: que estejam faltando no .env restaurado, resolvendo templates com defaults.
  • B2: docker run injetava template literal via --env. O pipeline injetava todas as entries de config.environment como --env KEY=VALUE no docker run, incluindo valores com {{::...}} não resolvidos. Como --env tem precedência sobre --env-file, o template literal sobrescrevia o valor correto do .env. Agora entries com {{:: são skippadas (mesmo padrão de GENERATE).
  • C: multi-template começando com {{:: (v2.22.3): "{{::A}},{{::B}}" era parseado como single Prompt full-match. Agora parse_env_value_spec verifica que o inner não contém outro {{:: antes de tratar como Prompt — strings multi-template viram Composite corretamente.

[2.22.2] - 2026-05-12 — Template resolution no deploy pipeline

Corrigido

  • runner fetch --deploy (cron) deixava {{::Var?default}} literal no .env porque provision_env_files só resolvia GENERATE, 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 em ports: era descartado. Era consumido apenas para resolver environment:/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 .env para que ports: e build.args: possam resolvê-los via resolve_value_from_app_files.

[2.22.1] - 2026-05-12 — Template resolution no `runner add`

Corrigido

  • runner add --env "TenantSlug=X" com .deploy.yml tendo TENANT_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/.secrets antes 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: 3

Caso 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.yml sem path_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: started

Comportamento: 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: true

Output em modo external:

  • print_snippet: true (default) → snippet em stderr depois do deploy, copy-pasteable
  • write_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 anterior

Bypass: 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-end
    • depends_on (state B): valida wait timeout em dep não-existente
    • duplicate_repo (state A): valida runner add --repo X re-aceito falha com duplicate_repo
  • traefik_external (já em v2.20) ganhou snippet asserts: confirma stderr emit em mode=external

Backwards compatibility

  • .deploy.yml sem depends_on: → Vec vazio, comportamento idêntico v2.20.x
  • .deploy.yml com {{::Var}} em build.args: agora interpola (era literal em v2.20 — comportamento mais correto, sem quebra esperada)
  • config.yml sem traefik.external: → sub-block opcional, só usado em mode=external
  • runner add em repo já registrado muda comportamento: era silently OK, agora duplicate_repo exit 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 --json cleanup — 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: production

Cada 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.5

Cada 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_network ganhou 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 nova
    • traefik_external: valida que mode=external não escreve local file
    • hosts_check: valida --add-host no docker inspect
  • status_schema.py agora hard check (era permissivo) — falha se manifest/config/runtime faltarem

Backwards compatibility

  • .deploy.yml sem build.args: → BTreeMap vazio, zero --build-arg flags
  • .deploy.yml sem hosts: → BTreeMap vazio, zero --add-host flags
  • config.yml sem traefik.mode: → default local → comportamento idêntico v2.19.x
  • .deploy.yml com networks: [X] e X não existe → v2.19.x quebrava no docker run; v2.20.x cria
  • Consumer do status --json legado → continua lendo top-level (_deprecated_top_level: true marker indica caminho novo)

Nenhuma quebra. Apps registradas em v2.19.x seguem rodando.

Backlog identificado pra v2.21.0

  • Interpolation {{::Var}} em build.args: — precisa expor resolve_single_value como pub em src/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.yml

Sem 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 -p no host

Sticky lifecycle:

  1. Primeira deploy: aloca porta → persiste em state/<app>.yml::network.published_port
  2. Re-deploys (commit novo, fetch --deploy): reusa a mesma porta — Traefik externo permanece válido
  3. runner unregister X: libera porta de volta ao pool
  4. runner reset --hard X: libera state → próximo deploy realoca
  5. runner ports realloc X: força nova alocação (operador atualiza Traefik)

Resolução de bind address:

  • network.vpc_ip configurado → 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:

  1. "Esse server fica em uma VPC interna? [s/N]"
  2. Se sim: oferece auto-detect via ip -4 -o addr show, confirma o IP
  3. "Strategy [random/sequential/explicit] (default: random)"
  4. 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::resolve consultava ports_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 hitava port_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 é Explicit quando network block ausente — comportamento idêntico ao v2.18.x (lê ports: do .deploy.yml)
  • State files antigos sem bloco network migram silenciosamente — porta re-alocada no próximo deploy quando strategy != Explicit
  • instances.X.domain continua 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-9100
    • secrets_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 --json schema ainda flat fora do novo runtime.network — refactor completo (manifest/config/runtime blocks 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:: warning Port: 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)

  1. runner status --json ainda flat — refactor para separar manifest / config / runtime blocks (input pra integração CCS)
  2. W6 (smoke probe pra detectar porta real do container) — alta complexidade
  3. W3 (version source detection: file vs git-commit) — baixa prioridade
  4. W4 (git ls-remote --heads pra listar branches no wizard) — baixa prioridade
  5. 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ê repo do state file (Bug E real em prod): migration v2.16.0 era parcial — pipeline ainda exigia github.repo no .deploy.yml e quebrava deploys de apps migrados. Agora resolve do state primeiro, fallback pro manifest legacy. Erro novo no_repo_configured é instrutivo.
  • Wizard --answers.instance_branch propaga até o YAML: era parseado mas hardcoded como branch: main. Agora InstanceSection.branch chega ao render.
  • Wizard recusa sobrescrever .deploy.yml existente (perfil A): bail com manifest_already_exists. Antes gerava defaults silenciosos.
  • Validator aceita type: OR app_type:: antes pedia só app_type: mas o parser canonical e o wizard usam type:.

Corrigido (P1)

  • Mensagem de erro no_deploy_yml: no_deploy_profile: redundante: strip do prefixo interno do generator.
  • runner status --json top-level current_version: campo era NULL no top, populado em 3 lugares diferentes do JSON. Agora atalho do state.current_version.
  • crypto.rs/vault/ckeys.rs aceitam RUNNER_KEYS_DIR env var: paths hardcoded /opt/runner/.keys exigiam 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

  • --branch propaga pra instances.X.source.branch (#4): antes, runner add --branch dev clonava de dev mas a instance ficava com main do .deploy.ymlrunner fetch posterior puxava da branch errada
  • runner add --deploy realmente deploya (#5): antes era silenciosamente ignorado com mensagem Auto-deploy requested but disabled. Agora invoca pipeline::deploy() na sequencia

Wizard polish

  • Curadoria de .env.example (#6): detecta APP_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): identifica laravel/framework, analisa routes/api.php + resources/views/ para distinguir system: api vs sys; 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 em state/<app>.yml); flag --ckey-show opt-in para exibir plaintext

Adiado para Lote B/C/D

Itens P0 do feedback (architectural changes):

  • VPC port allocation (network.{vpc_ip, publish_strategy, port_range})entregue na v2.19.0
  • 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 em runner.ccs.systems/tools/secrets.html segue aguardando atualizacao do gitops worker em producao

[2.17.4] - 2026-05-05

  • gitops fix attempt #4: arquivo tools/secrets/index.html na raiz do repo (igual init.sh)

[2.17.3] - 2026-05-05

Corrigido (gitops)

[2.17.2] - 2026-05-05

Corrigido

  • gitops: type: html em extra_artifacts era silenciosamente ignorado. Trocado para type: script

[2.17.1] - 2026-05-05 — F3 Tool web

[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 add em 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 template inspeciona 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.repo migrado para state file (deprecates github: no .deploy.yml)
  • Migration silenciosa para apps existentes

[2.15.0] - 2026-05-05 — Lote 4 (#7)

  • runner add nao clona mais o repo na raiz do app dir (causava duplicacao)
  • Apenas .deploy.yml + .runner/state-marker na raiz; codigo vive em pull/ e src/{instance}/{ver}/
  • runner cleanup --reset-root <app> opt-in para apps existentes

[2.14.0] - 2026-05-05 — Lote 3 (#8)

  • runner add/deploy BLOQUEIA secrets em environment: 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)

  • --ckey sem valor gera ckey random; com valor aceita literal
  • --repo aceita URLs (https/ssh/git@) e normaliza para user/repo
  • --branch agora obrigatorio sempre (sem default dist)

[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/.secrets no 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 --json em todos os comandos

[2.9.0] - 2026-04-22

  • CCS Data Contract — alinhamento de output runner snapshot --json para integracao com dashboard CCS

[2.8.0] - 2026-04-20

  • Master key backup: runner mkey export/import para disaster recovery

[2.7.0] - 2026-04-19

  • init.sh — instalador unificado (arch-aware, --rc flag para release candidates)

[2.6.0] - 2026-04-18

  • mode: exit no healthcheck — apps CLI/scanners/jobs one-shot (sem container permanente)

[2.5.0] - 2026-04-18

  • runner init interativo — wizard de setup inicial com --set e --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 set inteligente — auto-detecta secrets pelo nome, encripta com ckey ou avisa se plaintext
  • --force no env set — forca como secret sem questionamento (requer ckey)
  • .secrets so com encriptacao — sem ckey o pipeline ignora .secrets, --secret sem ckey retorna erro
  • regenerate fallback — procura key no .env se .secrets nao existe
  • Filtro de falsos positivoskeyboard, 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 runner
  • runner cleanup --build-cache [FILTER] — prune do Docker buildkit cache com filtro de idade (default: 72h, aceita 24h, 168h, all)
  • Limpeza automatica de imagens no pipelinecleanup_old_versions agora remove a imagem Docker de versoes que excedem keep_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, list
  • runner 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: e cmd: — prioridade cmd > tcp > path
  • depends_on: entre apps — topo sort em runner deploy --all, cycle detection
  • Validacao semantica em runner add — build context, Dockerfile, port, healthcheck
  • image_mode: dockerfile aceito como alias de self-contained
  • start_period configuravel 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

  • --instance no runner add: Define qual instancia do .deploy.yml esta maquina deve deployar
  • --instance no runner edit: Altera a instancia a qualquer momento, com validacao contra o .deploy.yml
  • Campo instance no 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_commit e deploy_retry_count no 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 Discord
  • runner notify test: Envia notificacao de teste para todos os canais configurados
  • runner 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: GlobalConfig removido, tudo via RunnerConfig

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 build com 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 default
  • app_type: docker-build -> tratado como modo BUILD
  • image_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.json com 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 instance em DeployAuditEntry com serde(default) para logs antigos
  • source.directory ignorado: Campo nao existia em InstanceSourceConfig, serde ignorava silenciosamente
  • Read-only filesystem: Mount de source como :ro impedia 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 Dockerfile
  • write_dockerfile() — nao utilizado pelo pipeline
  • Campo image_mode — usar image: ou build: diretamente
  • app_type: docker-build — usar secao build:

[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 dev

O 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 --verbose para 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/runner

CLI 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/front

Preparacao 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, env exigiam flag --app desnecessariamente

[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:canary

Canary 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:500ms

Novos comandos CLI:

  • runner canary status <project> -i <instance> - Ver status do scheduler
  • runner canary pause <project> -i <instance> - Pausar promocao
  • runner canary resume <project> -i <instance> - Retomar promocao
  • runner 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_token

Tipos de autenticacao suportados:

  • none - Sem autenticacao
  • api_key - Token em query param ou header
  • bearer - Header Authorization: Bearer
  • oauth2 - Client credentials flow (auto-renew)
  • basic - Username:password
  • hmac - 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_lines

Comando `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 customizada

O 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-* --json

Labels CCS usadas:

  • ccs.systems/project
  • ccs.systems/instance
  • ccs.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_type ao 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 --verbose para output detalhado
    • Uso: runner fetch (limpo) ou runner 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.yml desatualizado no servidor
    • Util para migracao de formato legado para v1.1.10
    • Uso: runner fetch --force ou runner fetch -F

[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 :rw para :ro (seguranca)
    • logs/ e data/ continuam como :rw
    • Evita erro de mount duplicado

[1.1.9] - 2026-02-16

Corrigido

  • app_type parsing: Alias type/app_type para compatibilidade YAML
  • env_files: Container carrega .env e .secrets automaticamente
  • 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.version em Cargo.toml)

    • Parser agora detecta secoes [section] e busca chaves dentro delas
    • Compativel com JSON e YAML via dot notation
  • 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 DeployBuildConfig para 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

[1.1.7] - 2026-02-15

Alterado

  • Storage: Default apps_path alterado de /apps para /data/apps

    • Segue estrutura de storage do ecossistema CCS
    • Compativel com migracao para Kubernetes
    • Configs existentes com apps_path: /apps continuam 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 stable e rc no .gitops.yml
  • Releases estaveis na raiz: /runner
  • Release candidates em: /rc/runner
  • Versionamento automatico por tag pattern (vX.Y.Z vs vX.Y.Z-rc.N)

Alterado

  • Self-update agora suporta flag --rc para 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_name no .gitops.yml corrigido para processar corretamente

[1.1.1] - 2026-02-13

Alterado

  • URL de download alterada para runner.ccs.systems
  • .gitops.yml atualizado 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 servidor

Tools 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 estatica
  • runner://status/* - Status dinamico (apps, staging, config)

Prompts (5):

  • setup_new_project - Setup de novo projeto
  • diagnose_deploy - Diagnostico de falhas
  • canary_workflow - Workflow de canary
  • migrate_config - Migracao para v1.0.0
  • staging_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 customizado

Features:

  • 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: 3

Comparacao 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 principal
  • GlobalConfig - Config global
  • RunnerConfig - 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 Runner
  • traefik/mod.rs - Geracao de YAML dinamico para Traefik
  • containers/mod.rs - Operacoes Docker via docker run/build

Novos Comandos CLI

  • runner init - Inicializa ambiente Runner
  • runner instances <project> - Lista instancias
  • runner versions <project> -i <name> - Lista versoes
  • runner weights <project> -i <name> - Mostra pesos
  • runner weight <project> <inst> <ver> <weight> - Ajusta peso
  • runner promote <project> -i <name> <version> - Promove versao
  • runner destroy <project> -i <name> - Remove instancia
  • runner cleanup - Limpa versoes antigas
  • runner migrate --app <path> - Migra estrutura legada

[0.5.0] - 2026-01-28

Adicionado

  • Comando reset - Reset de deploy
  • Flag --hard - Reset incluindo .env e .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-runner para runner
  • 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
By Borlot.com.br on 16/02/2026