Sources de arquivo (HTTPS e rsync)

A partir da v2.163.0, uma instância pode buscar o código sem git, de três jeitos:

type De onde vem Como o cron detecta mudança
local pasta no próprio servidor árvore ou arquivo de controle — ver Deploy local
https artefato baixado (.tar.gz, .tgz, .zip ou arquivo único) control_url, ou ETag/Last-Modified do servidor
rsync árvore em outro servidor, por ssh ou rsync:// arquivo de controle (control_file)

Nos três, o código chega em <app>/pull/ numa troca atômica (pasta nova + rename): um deploy que falha no meio não deixa a app sem código. O resto do deploy é o de sempre.

O que o cron faz

A cada ciclo, o runner fetch --deploy pergunta ao driver qual é a revisão atual da origem, sem baixar o código:

  • revisão diferente da implantada: pede deploy (disponível);
  • igual: atualizado;
  • origem fora do ar, URL errada, chave recusada: erro de fetch (conta para o auto-pause, como um repositório que não responde; falha de rede é transitória e não conta);
  • detect: manual: a app fica manual e só o runner deploy implanta.

A revisão é http:<sha256> ou ctl:<sha256>. O runner status e o snapshot mostram a revisão implantada inteira (revision).

A versão da pasta src/<instância>/<versão> segue o version: do manifesto, lido só do código já baixado (pull/). Sem bloco version:, ou com version.source: git-commit/git-tag (não há git), a versão é <tipo>-<12 hex> da revisão (ex.: http-3f9a1c2b7d4e).

runner secrets push recusa instância com source de arquivo (secrets_push_source_de_arquivo): não há repositório para o secrets.enc.

HTTPS

instances:
  staging:
    source:
      type: https
      url: https://cdn.exemplo.com/minha-app/minha-app.tar.gz
      control_url: https://cdn.exemplo.com/minha-app/version.txt          # opcional
      checksum_url: https://cdn.exemplo.com/minha-app/minha-app.tar.gz.sha256  # opcional
      signature:                                                        # opcional
        url: https://cdn.exemplo.com/minha-app/minha-app.tar.gz.minisig
        public_key: "RWQ..."                                            # chave pública minisign
      headers:
        Authorization: "{{::CDN_TOKEN}}"                                # do cofre da app
      max_bytes: 2147483648                                             # padrão: 2 GiB

Formato pela extensão do último segmento da url: .tar.gz/.tgz e .zip são extraídos; qualquer outra extensão é um arquivo só, gravado em pull/ com esse nome.

Detecção de mudança:

  1. com control_url: o sha256 do corpo dele. É o mais barato e o mais confiável — publique o version.txt por último;
  2. sem: HEAD na url, pelo ETag (ou Last-Modified + Content-Length);
  3. sem nenhum dos dois: o arquivo inteiro é baixado a cada ciclo (o log avisa source_https_sem_marca).

detect: manual desliga a sonda (só o runner deploy implanta); detect: tree não se aplica ao https (o validador avisa detect_ignorado).

Integridade. checksum_url (formato do sha256sum: <hex> <nome>, ou só <hex>) e signature (minisign ≥ 0.8) são conferidos antes de extrair. Sem nenhum dos dois, o deploy segue, mas o log e o runner preflight avisam download sem verificação de integridade.

Extração segura. Caminho absoluto, .. e link simbólico que sai do destino são recusados (zip-slip). Acima de max_bytes descompactados, o deploy para.

Credenciais. Só https:// (o validador recusa http://, inclusive em redirect). Os {{::VAR}} dos headers vêm do cofre da app (.env, .env.<instância>, .secrets):

runner env set minha-app CDN_TOKEN 'Bearer ...' --secret

O valor nunca entra em log nem em mensagem de erro. Com headers declarados, redirect só é seguido para a mesma origem (esquema, host e porta), até 5.

rsync

instances:
  homolog:
    source:
      type: rsync
      remote: [email protected]:/srv/build/minha-app/   # ou rsync://host/modulo/
      control_file: VERSION                                     # obrigatório
      ssh_key: /opt/runner/keys/rsync_ed25519                   # opcional, caminho absoluto
      exclude: [".git"]                                         # padrão: [".git"]

Detecção de mudança: sempre pelo control_file (relativo ao remote): a sonda baixa só ele. Quem entrega grava o VERSION por último. detect: não se aplica (o validador avisa detect_ignorado se houver detect: tree), exceto detect: manual.

Cópia. rsync -a --delete-excluded para uma pasta nova ao lado de pull/ (o --delete age só no destino, nunca na origem), semeada com o pull/ atual para transferir só a diferença. O control_file é conferido antes, na cópia e de novo na origem: se mudou no meio, o deploy para com source_em_mudanca e o próximo ciclo tenta de novo.

ssh. Sempre BatchMode=yes (nenhum prompt) e StrictHostKeyChecking=yes: o host já tem de estar no known_hosts do usuário do runner.

ssh-keyscan origem.exemplo.com >> ~/.ssh/known_hosts

ssh_key é um caminho absoluto (relativo é recusado com source_rsync_chave_relativa), arquivo com modo 0600 ou 0400. Sem ssh_key, valem as chaves do usuário do runner.

Link simbólico que aponta para fora da árvore não vem (--safe-links); fifo, socket e device também não. Os arquivos ficam do usuário do runner.

Conferir antes do primeiro deploy

runner preflight minha-app     # componente `source`: tipo, revisão, integridade
runner fetch minha-app         # a sonda responde? (disponível / atualizado / erro)
runner deploy minha-app
runner status minha-app        # revision: http:… / ctl:…

Erros comuns

Código Causa
source_https_url_nao_https url, control_url, checksum_url ou signature.url sem https://
source_https_header_sem_segredo header usa {{::VAR}} que não está no cofre da app
source_https_sem_integridade aviso: sem checksum_url nem signature
source_rsync_chave_relativa ssh_key não é caminho absoluto
source_rsync_chave_ssh ssh_key ilegível ou com modo diferente de 0600/0400
source_em_mudanca a origem mudou durante a cópia; o próximo ciclo tenta de novo
By Borlot.com.br on 22/10/2026