`runner service`
Instala e gerencia o serviço systemd do runner (scheduler, webhook e API remota). Todos os subcomandos de mutação exigem root e systemd.
| Subcomando | O que faz |
|---|---|
init |
Gera service.yml, a unit e habilita (nasce com a API desligada) |
start / stop / restart |
Controla o serviço |
status |
Estado do serviço (-o json: installed, running, scheduler, api) |
config |
Altera porta, bind, chave, autenticação e restrição por IP |
expose / unexpose |
Publica a API num domínio do Traefik ou remove a rota |
uninstall |
Remove unit, service.yml, a chave e o segredo do header |
Com -o json, um erro vira UM objeto {"error":"<codigo>","message":"..."} no
stdout, com exit diferente de zero. O texto humano de expose, unexpose e
uninstall vai para o stderr. Veja a tabela de códigos.
Autenticação da API
Por padrão a API autentica com o header X-API-Key e uma chave rk_...
(entregue em /opt/runner/service-api-key, modo 600). Para usar um header
próprio:
# o segredo vem por stdin: nunca na linha de comando
runner service config --auth-header X-Meu-Header --secret-stdin < /caminho/segredo| Flag | Efeito |
|---|---|
--auth-header <NOME> |
Liga o header próprio. O modo é exclusivo: o X-API-Key passa a dar 401. Authorization é aceito, e o valor enviado é o segredo puro, sem Bearer. Sem --secret-stdin, troca só o nome (exige segredo já gravado) |
--secret-stdin |
Lê o segredo do stdin: 32 a 4096 bytes de ASCII visível, o \n final sai. Sozinho, rotaciona o segredo e mantém o header. Com o stdin num terminal, é recusado |
--auth-padrao |
Volta para X-API-Key + rk_, gera uma rk_ NOVA e apaga o segredo do header |
O segredo fica em /opt/runner/service-auth.secret (modo 600, dono root, arquivo
comum). O service.yml guarda só o nome do header e a impressão digital (12 hex).
A API não sobe se o arquivo sumir, tiver outra permissão ou divergir da impressão.
A resposta 401 é a mesma nos dois modos e não revela o header esperado.
Ao entrar no modo header, a rk_ é zerada: api.api_key fica vazio no
service.yml e /opt/runner/service-api-key é apagado. O status passa a
trazer key_configured: false.
Restrição por IP
runner service config --ip-permitidos 192.0.2.10/32,198.51.100.0/24
runner service config --limpar-ip-permitidos
runner service config --ip-permitidos 192.0.2.10 --atras-de-proxy cloudflare| Flag | Efeito |
|---|---|
--ip-permitidos <CIDR,...> |
Substitui a lista (v4/v6). IP sem / vale /32 ou /128. Mínimo /8 (v4) e /16 (v6), até 64 entradas. Vale para a API e os hooks; o /health fica livre |
--limpar-ip-permitidos |
Remove a restrição |
--atras-de-proxy cloudflare|nenhum |
Informa o proxy à frente do Traefik. Exige a lista |
service expose aceita --ip-permitidos e --atras-de-proxy. Com a API
exposta, config --ip-permitidos reescreve a rota runner-api.yml: o
vpn-only@file sai da rota e a faixa da VPN entra na lista (a combinação seria E,
não OU). Ao limpar, ele volta. API exposta por versão anterior à 2.175.0 pede
reexpor_necessario: rode expose de novo.
Fora da lista, a resposta é 403 com ip_nao_permitido. A verificação roda
antes da autenticação. O mTLS não pode ser combinado com a lista
(ip_permitidos_com_mtls).
O IP do client só vem do
X-Forwarded-Forquando a conexão chega de um proxy confiável (trusted_proxies). Um par de fora é sempre julgado pelo próprio IP.
Downgrade: com
--ip-permitidosativo, roderunner service config --limpar-ip-permitidosANTES de voltar para versão < 2.175.0; senão a API fica sem filtro de IP e sem VPN. No modo header, a versão antiga recusa subir (api_key_ausente); rode--auth-padraoantes se for usar a API nela.
Status em JSON
runner -o json service status traz o bloco api:
{
"enabled": true, "bind": "127.0.0.1", "port": 9090, "mtls": false,
"exposed": true, "domain": "runner.exemplo.com", "key_configured": false,
"auth": {"modo": "header", "header": "X-Meu-Header", "fingerprint": "3f9a1c2b7d4e"},
"expose": {"domain": "runner.exemplo.com", "vpn_only": true, "hooks_publicos": false, "middlewares": []},
"ip_permitidos": {
"cidrs": ["192.0.2.10/32"], "incluir_vpn": true, "faixa_vpn": ["10.0.1.0/24"],
"proxy": "cloudflare", "cloudflare_faixas_data": "2026-10-29"
}
}auth.modoéapi_keyouheader. No modoapi_key,headeréX-API-Key. Ofingerprinténullse não há chave.ip_permitidossó aparece quando configurado. Trazaviso_modo_host: truecom o Traefik em modo host e semtrusted_proxies, eavisoquando as faixas embutidas da Cloudflare têm mais de 180 dias.exposesó aparece com a API exposta. Se ela foi exposta por versão anterior à 2.175.0, sem o registro gravado, traz sódomaine"vpn_only": null.- Sem
service.yml:{"installed": false, ...}com exit 0.
Códigos de erro
| Código | Quando |
|---|---|
servico_nao_instalado |
Sem service.yml ou unit |
servico_ja_instalado |
init com serviço existente |
systemctl_falhou |
systemctl saiu com erro |
systemd_ausente |
Host sem systemd |
permissao_negada |
Não é root |
porta_ocupada / bind_invalido / bind_falhou |
Porta em uso, bind inexistente, falha ao escutar |
api_desligada, mtls_atras_do_traefik |
Pré-condições do expose |
api_key_ausente, api_key_formato |
Chave da API |
dominio_em_uso |
Domínio já tem router |
traefik_nao_encontrado |
Sem Traefik ou Docker inacessível |
expose_sem_resposta |
A rota não respondeu em 60 s |
mtls_incompleto, tls_cert_ausente, tls_chave_ausente |
TLS |
auth_header_invalido |
Nome de header fora da regra (token RFC 9110, até 64, sem hop-by-hop, sem X-Forwarded-*) |
auth_header_ausente |
--secret-stdin sem header configurado |
auth_modo_header |
--api-key com o modo header ligado |
segredo_ausente |
Header sem segredo gravado ou informado |
segredo_stdin_vazio, segredo_stdin_terminal |
Stdin vazio ou num terminal |
segredo_fraco, segredo_grande, segredo_invalido |
Fora de 32 a 4096 bytes ou com caractere não visível (inclui espaço) |
segredo_permissao |
Arquivo do segredo não é comum, 600 e do root |
segredo_divergente |
Arquivo não bate com a impressão do service.yml |
ip_permitidos_invalido |
CIDR inválido, largo demais ou lista acima de 64. CIDR duplicado não é erro: a duplicata é removida |
ip_permitidos_com_mtls |
Lista combinada com mTLS |
ip_permitidos_nao_aplicado |
Restart ou rota falhou; o estado anterior foi restaurado |
vpn_faixa_desconhecida |
Não foi possível ler a faixa da VPN do Traefik |
vpn_only_ausente |
expose --vpn-only num host sem o middleware vpn-only no file provider do Traefik (falha na hora, nada é gravado) |
atras_de_proxy_invalido |
Valor desconhecido ou sem lista |
reexpor_necessario |
API exposta por versão antiga: rode expose de novo |
runner_erro |
Erro sem código próprio |
Na API HTTP, a recusa por IP responde 403 com ip_nao_permitido ou
ip_nao_determinado.