Referência

CLI

Todos os comandos do ./nimbly: setup, builds, testes, manutenção, agendador e estatísticas.

O Nimbly traz uma CLI em core/cli/nimbly.php. A forma preferida de chamá-la é o lançador ./nimbly na raiz:

./nimbly init
./nimbly up
./nimbly deps
./nimbly build
./nimbly watch
./nimbly test:run
./nimbly user:create
./nimbly module:install <name>

init, deps, build e watch usam npm internamente. Os comandos que usam PHP rodam no PHP do host quando ele existe e recorrem ao Docker automaticamente. Também há variantes explícitas:

php core/cli/nimbly.php system:setup   # require host PHP
./nimbly --docker system:setup         # force Docker Compose

Chamadas diretas equivalentes:

php core/cli/nimbly.php system:setup
php core/cli/nimbly.php user:create
php core/cli/nimbly.php module:install <name>
php core/cli/nimbly.php routes:sync
php core/cli/nimbly.php index:rebuild [resource]
php core/cli/nimbly.php data:cache:clear
php core/cli/nimbly.php user:email-index:rebuild
php core/cli/nimbly.php system:repair
php core/cli/nimbly.php help

Consulta à documentação

Os comandos de documentação só leem, e só leem o NIMBLY.md local. Use-os para carregar a menor seção útil da referência enquanto trabalha:

./nimbly docs:list
./nimbly docs:section "Template Syntax"
./nimbly docs:section "Template Syntax > Route templates"
./nimbly docs:search "router_accept"

A busca não diferencia maiúsculas de minúsculas. Um caminho de seção pode indicar um título aninhado; um nome de título sem caminho precisa ser único. Seções ausentes ou ambíguas devolvem um erro.

Comandos

./nimbly help lista os comandos. ./nimbly <command> --help (ou -h) mostra o que um comando faz e o que recebe, sem executá-lo.

system:setup

Prepara o sistema local. Pode ser executado de novo com segurança: arquivos e registros existentes nunca são sobrescritos.

O que ele faz:

  • Cria o .env com APP_ENV, um PEPPER gerado e BASE_PATH apenas em instalações numa subpasta
  • Gera o .htaccess a partir de um modelo
  • Gera o .user.ini a partir de um modelo, para as configurações de runtime do PHP-FPM
  • Cria a estrutura de pastas de ext/ (data/, static/, lib/, modules/, tpl/, uri/, pastas temporárias)
  • Dá a um site sem nenhuma página uma primeira página inicial (ext/uri/index.tpl e main.tpl): uma página curta e editável que mostra onde ficam páginas, conteúdo e dados, com um recurso de exemplo (ext/data/messages/.meta) e um formulário que grava nele (ext/uri/message.json), fechado para visitantes. Você pode mudá-la ou removê-la; um site que já tem uma rota não é tocado
  • Cria .config/site, o recurso .content, os registros de .routes do core e as funções padrão (admin, editor)
  • Cria o recurso users e um primeiro usuário admin

Perguntas: Site name, Admin email, Admin password. Etapas já concluídas são puladas em silêncio.

Sem perguntas (CI/CD): defina variáveis de ambiente para pular todas as perguntas:

Variável Descrição
APP_ENV Nome do ambiente (prod, stage, dev; padrão dev)
BASE_PATH Caminho base opcional na URL para instalações numa subpasta, por exemplo /mysite/; omita em instalações na raiz
PEPPER Pepper de criptografia; defina para reutilizar um valor existente, omita para gerar automaticamente
EXT_REPO URL do remoto Git do repositório ext (gravada em ext/readme.md)
SITE_NAME Nome do site, gravado em .config/site
SITE_LANGUAGE Idioma principal validado, com duas letras minúsculas, para um site novo (padrão en)
ADMIN_EMAIL E-mail do primeiro usuário admin
ADMIN_PASSWORD Senha do primeiro usuário admin (mínimo de 8 caracteres)
SITE_NAME="My Site" ADMIN_EMAIL=admin@example.com ADMIN_PASSWORD=secret123 ./nimbly system:setup

Sem terminal (ao iniciar um contêiner, a partir de um agendador), a configuração nunca espera por uma resposta. Um SITE_NAME ausente vira My Nimbly Site. Um site sem usuários precisa de ADMIN_EMAIL e ADMIN_PASSWORD: quando falta um deles, a configuração para com uma linha que o nomeia e código de saída 1, para que um contêiner de produção num volume vazio só inicie quando ambos estiverem definidos.

user:create

Cria mais uma conta de usuário. Pergunta de forma interativa o e-mail, a função e a senha. As funções disponíveis vêm de ext/data/roles/. O usuário sempre recebe também a função user. Exige que a configuração já tenha sido executada.

module:install

Executa o script .install.inc de um módulo. Procura primeiro em ext/modules/ e depois em core/modules/. Exige que a configuração já tenha sido executada.

./nimbly module:install event

routes:sync

Percorre ext/uri/**/route.inc e cria em .routes os registros que faltam para rotas dinâmicas. Use depois de acrescentar uma nova pasta de rota com segmentos (param).

./nimbly routes:sync

index:rebuild

Reconstrói as entradas de índice de um recurso indexado. Use depois de acrescentar index ao .meta de um recurso existente, ou depois de importar registros fora do fluxo normal da API.

php core/cli/nimbly.php index:rebuild             # interactive: lists indexed resources, prompts for choice
php core/cli/nimbly.php index:rebuild articles    # direct: reindex the 'articles' resource

O comando percorre todos os registros do recurso e cria os arquivos de índice que faltam. Pode ser executado várias vezes; entradas existentes não são tocadas.

data:cache:clear

Descarta os resultados de consulta em cache de todos os recursos; a próxima leitura os reconstrói. Use depois de editar à mão, no próprio lugar, um arquivo de registro (veja Cache de dados). Salvar pelo site, pela API, pelo Git ou por ext:sync não exige limpar.

php core/cli/nimbly.php data:cache:clear

user:email-index:rebuild

Acrescenta ao recurso users os metadados de busca por e-mail e reconstrói o índice de e-mails.

php core/cli/nimbly.php user:email-index:rebuild
php core/cli/nimbly.php user:email-index:rebuild --yes

Essa migração faz parte do modelo em que o UUID do registro de usuário é fixo e o e-mail é um campo de busca indexado. Ela acrescenta email a users/.meta.index, acrescenta email a users/.meta.unique quando não há e-mails duplicados e cria entradas de índice para todos os usuários existentes, inclusive usuários antigos cujo UUID era originalmente md5(email).

Se encontrar e-mails duplicados, o comando os informa e só acrescenta unique=email depois que forem resolvidos. O índice de e-mails é reconstruído mesmo assim, para que o login continue rápido sempre que possível.

test:run

Executa a suíte E2E. O comando cria dados de teste temporários com test:setup, instala o navegador Chromium do Playwright se necessário, executa o Playwright com core/tests/playwright.config.js e depois remove os dados temporários com test:teardown.

./nimbly test:run
./nimbly --docker test:run

Use --docker na CI ou sempre que os arquivos de dados do PHP precisarem ser gravados pelo mesmo modelo de usuário do Docker que o runtime web. Os testes do core não chamam provedores externos pagos. Substitua o provedor por um mock e teste de forma previsível a requisição, o tratamento da resposta, a validação e o comportamento da interface do Nimbly. A disponibilidade de um provedor é uma verificação operacional de integração, não parte da suíte de testes do core.

test:setup / test:teardown

Comandos de baixo nível usados por ./nimbly test:run. test:setup cria a função test, o usuário test@nimbly.dev, o recurso test-records e registros iniciais. test:teardown remove esses registros e as entradas temporárias de rota .test.

system:repair

Iguala os arquivos gerados e os registros do sistema ao core atual. Uma atualização do core nunca os reescreve, então um site mais antigo pode não ter o que uma instalação nova recebe de system:setup. Cada verificação informa primeiro; nada muda sem --yes.

Verificação Reparado quando
.htaccess da raiz Ele difere de core/cli/setup/htaccess.tpl. O pepper e a base de reescrita vêm do arquivo existente; o arquivo anterior é mantido como .htaccess.before-repair.
Proteções de pasta Um .htaccess em ext/ ou core/ falta ou ainda está na forma antiga <files *.*>. Uma proteção com conteúdo próprio é informada e mantida.
ext/.gitignore Ele não ignora o cache de miniaturas, a fila de jobs, o estado do agendador ou as conversas e execuções dos agentes.
.config/.meta Ele não tem upsert, de modo que as configurações de página não podem ser salvas para uma página que ainda não as tem.
Rotas do core Uma rota do core acrescentada depois da configuração do site não está registrada.
Índice de e-mails dos usuários users/.meta não indexa email. unique só é acrescentado quando não há dois usuários com o mesmo endereço.
./nimbly system:repair         # report only
./nimbly system:repair --yes   # apply

As pastas ignoradas mudam sozinhas em cada servidor (o estado do agendador a cada minuto), então acompanhar uma delas no Git faz o ext:sync entrar em conflito e pode deixar o ext/ do servidor preso num rebase. A regra de ignorar não tira do Git uma pasta que já é acompanhada:

git -C ext rm -r --cached data/.state

schedule:run

Executa os comandos agendados que estão na hora para um projeto. Em servidores VPS manuais com vários projetos Nimbly, não crie uma entrada de cron por projeto. Use o orquestrador de agendamento no nível do servidor e deixe-o chamar o schedule:run de cada projeto registrado, um depois do outro.

Os projetos podem definir agendamentos em ext/cli/. O agendador escolhe os arquivos nesta ordem:

  1. SCHEDULE_FILE do .env ou do ambiente do processo, como caminho de arquivo explícito.
  2. ext/cli/schedule.<env>.inc, em que <env> vem de SCHEDULE_ENV, APP_ENV ou NIMBLY_ENV.
  3. ext/cli/schedule.inc.
  4. core/cli/schedule.inc.

Os nomes de ambiente são normalizados: production → prod, staging → stage, development e local → dev.

Use arquivos de agendamento por ambiente quando o staging ou o desenvolvimento precisam rodar jobs em segundo plano, mas não tarefas exclusivas de produção, como lembretes para membros.

As tarefas agendadas podem usar every_minutes para intervalos maiores que um minuto:

[
    'id' => 'uptime-poll',
    'command' => 'uptime:poll',
    'every_minutes' => 2,
]

O valor precisa ser um inteiro positivo. Uma tarefa nova está na hora imediatamente; depois de rodar, volta a estar na hora quando o relógio entra no próximo intervalo. Os agendamentos existentes com every, daily_at e o timezone opcional continuam suportados.

O estado das últimas execuções do agendador fica em ext/data/.state/schedule. Instalações existentes com o estado antigo em ext/data/.config/schedule são lidas como alternativa de migração até o agendador gravar o novo arquivo de estado.

Manutenção obrigatória e expiração de sessões

Toda chamada de schedule:run inclui as tarefas do core antes de carregar o agendamento da aplicação. Os agendamentos da aplicação acrescentam tarefas; não podem remover nem reagendar estas chamadas obrigatórias:

ID da tarefa Comando Intervalo
sessions-prune sessions:prune 30 minutos
jobs-run jobs:run 10 1 minuto
jobs-prune jobs:prune --days=30 24 horas
history-prune history:prune --days=90 24 horas
agent-prune agent:prune --days=30 24 horas
stats-rollup stats:rollup 1 hora

Entradas existentes da aplicação com esses IDs ou comandos são deduplicadas. Substitua uma implementação, não o agendamento dela: defina o mesmo comando em ext/cli/commands.php, seguindo o contrato normal do registro de comandos. O core confia no status de saída da implementação e não inspeciona o trabalho interno. O registro do host lista projetos, não escolhas de tarefas; flags enabled antigas não impedem mais projetos registrados. Remova do registro os deployments desativados. Antes de registrar um projeto antigo sem agendamento, revise os jobs na fila e o agendamento da aplicação: ativar o processamento pode entregar e-mails pendentes ou executar outro trabalho da fila.

./nimbly schedule:status informa se as tarefas obrigatórias foram concluídas com sucesso e sai com erro quando uma tarefa falhou, nunca teve sucesso ou está atrasada mais de dois intervalos. Uma execução bem-sucedida com fila vazia é saudável. A faixa Needs attention do admin e a auditoria do host leem esse estado cada uma por conta própria, então também detectam um agendador parado. Verifique esse comando depois de todo deployment; só instalar o cron não prova que a manutenção funciona. A auditoria do host instalada traz, ao lado do coletor, um auxiliar nimbly-maintenance.php pertencente ao root.

Em instalações VPS, use o orquestrador existente do host e registre todos os projetos ativos. As imagens de contêiner já chamam o agendador do projeto a cada minuto. Outras plataformas de nuvem precisam chamar a mesma CLI a cada minuto, com um único agendador que tenha acesso ao ext/data persistente e à configuração de runtime da aplicação. Monitore schedule:status de forma independente desse executor. Não é preciso nenhum daemon extra de limpeza de sessões nem registro por projeto.

As funções de sessão, incluindo a inspeção e a limpeza de arquivos, ficam em core/lib/session.php. O padrão são limites contínuos de inatividade: anônimo 4 horas, usuários autenticados 3 dias, admins 7 dias. Não há limite absoluto de idade do login: atividade regular mantém viva uma sessão autenticada. Trocar o ID da sessão não muda a identidade nem desconecta o usuário. Leituras anônimas e verificações de permissão não criam sessões; formulários e outros recursos com estado podem criar. Os cookies de usuários autenticados persistem depois de reiniciar o navegador; os cookies anônimos continuam sendo cookies de sessão do navegador.

Todo cookie que o core define tem as mesmas opções (session_cookie_options()): o caminho base do site, HttpOnly, SameSite=Lax e Secure em https. Isso vale para o cookie de sessão, para a chave de formulário de um visitante sem sessão (key) e para uma variável guardada com [#set session ...#] quando não há sessão; esses dois últimos duram 30 dias (persistent_cookie_options()). Scripts na página não conseguem lê-los.

Os projetos podem configurar as durações em segundos em session no .config/site:

{
  "session": {
    "anonymous": 14400,
    "authenticated": 259200,
    "roles": { "admin": 604800, "editor": 259200 }
  }
}

Vence a exceção mais longa entre as funções atribuídas que correspondem; senão vale o padrão de usuários autenticados. Trocar de função temporariamente não muda o registro das funções atribuídas. Login, cookies e limpeza seguem a mesma regra. O core desativa a coleta de lixo do PHP disparada por requisição na sua pasta privada de sessões.

./nimbly sessions:prune --dry-run informa totais sem mudar sessões. A execução normal lê os arquivos um a um, pula arquivos bloqueados e links simbólicos e remove só sessões expiradas. Arquivos malformados, objetos serializados ou referências não suportados e arquivos maiores que 1 MiB são informados como falha em vez de excluídos. O leitor aceita as codificações php e php_serialize com valores e arrays. A ação separada Sign out everyone remove de propósito todas as sessões. Uma opção "lembrar de mim" com duração maior ficou para depois.

Estatísticas de requisições (stats:*)

O core registra toda requisição que chega a index.php, sem cookies nem JavaScript. O registro fica ligado para APP_ENV=prod e stage; defina STATS_ENABLED=1 ou 0 para mudar isso. Os arquivos servidos diretamente pelo .htaccess (ext/static, core/static, miniaturas em /img/) e as sondagens .php bloqueadas nunca chegam ao PHP e não são registrados.

Uma função de encerramento acrescenta uma linha JSON por requisição a ext/data/.tmp/stats/running-YYYY-MM-DD.log: hora UTC, método, host, caminho com consulta (relativo à URL base), status, tipo de conteúdo, duração, IP, user agent, referenciador, a primeira tag de Accept-Language e uma indicação de login. Acima de STATS_MAX_DAY_BYTES (padrão 200 MB), um dia passa a só contar as requisições seguintes como excedente.

stats:rollup (manutenção obrigatória, a cada hora) arquiva os dias encerrados em ext/data/.stats/<env>/, que o ext:sync faz commit:

Caminho Conteúdo
raw/YYYY/YYYY-MM-DD.log.gz.enc o dia completo, compactado com gzip e criptografado com AES-256-GCM
raw/YYYY/YYYY-MM-DD.apache.log.gz.enc complemento opcional do Apache, de apache-YYYY-MM-DD.log
months/YYYY-MM.json contagens completas por dia, por status, rota, tipo de visitante, dispositivo, navegador, idioma, bot, referenciador e caminho, mais hours (requisições, visualizações de página, visitantes, bots, scanners por hora UTC)
years/YYYY.json as mesmas contagens somadas por mês, mais um total do ano

Todas as datas, nomes de arquivo e viradas de dia estão em UTC. As interfaces convertem para o fuso horário de quem vê reagrupando hours; um visitante conta na hora da primeira visualização de página do dia UTC. Aproxime de ano para mês, para dia, até as requisições brutas; semanas e totais de todo o período são derivados desses arquivos. Acima do nível de dia, visitors vira visits, a soma dos visitantes únicos diários. Os arquivos JSON guardam só contagens, nunca rankings ou endereços IP; as interfaces derivam as listas de destaque a partir deles. A classificação (humano, editor, bot, scanner, suspeito, monitor, ferramenta) acontece no rollup, então stats:rollup --rebuild recalcula todos os meses a partir de raw/ quando as regras melhoram.

stats:import-apache lê linhas do log de acesso do Apache (combined ou vhost_combined) pela entrada padrão, de modo que só a leitura do log precisa de root:

zcat -f /var/log/apache2/site-access.log.* | sudo -u www-data ./nimbly stats:import-apache --mode=full --before=2026-09-25T02:02:00Z
zcat -f /var/log/apache2/site-access.log.1 | sudo -u www-data ./nimbly stats:import-apache

--mode=full preenche todas as linhas antes de --before (dias antes do início do registro). O padrão --mode=enrich acrescenta só o que nunca chega ao PHP: arquivos servidos de ext/static ou core/static, miniaturas em /img/ e sondagens .php bloqueadas. As linhas são gravadas em apache-YYYY-MM-DD.log (datas UTC) e arquivadas no rollup seguinte; uma marca em .tmp/stats/apache.watermark pula as linhas já importadas. Use --base=/subdir/ em instalações numa subpasta e --host= quando SITE_URL não está definido. As linhas do Apache não têm Accept-Language, tipo de conteúdo nem estado de login: os tipos de conteúdo são deduzidos do caminho e os editores contam como visitantes.

STATS_KEY (64 caracteres hexadecimais, gerada por system:setup) criptografa o arquivo bruto. Faça backup dela: sem ela, o histórico bruto não pode ser lido. Enquanto ela faltar, o rollup falha e mantém os logs em andamento. stats:raw YYYY-MM-DD [--apache] mostra um dia descriptografado.

scheduler:*

Executa os agendadores de vários projetos a partir de um único cron no nível do servidor. Os arquivos de agendamento continuam no próprio projeto, em ext/cli/; o registro do servidor é a única fonte sobre quais projetos são orquestrados.

O orquestrador usa por padrão estes arquivos, gerenciados no servidor:

Caminho Finalidade
/usr/local/bin/nimbly-scheduler-orchestrator Wrapper executado pelo cron
/etc/nimbly/scheduler-projects.json Projetos registrados e padrões
/etc/cron.d/nimbly-scheduler Entrada de cron do sistema
/var/log/nimbly-scheduler.log Log do agendador
/etc/logrotate.d/nimbly-scheduler Rotação semanal do log, oito semanas guardadas

Instale uma vez por servidor, a partir de qualquer checkout atual do core do Nimbly:

sudo php core/cli/nimbly.php scheduler:install
sudo php core/cli/nimbly.php scheduler:project:add site-name /var/www/site-name
sudo php core/cli/nimbly.php scheduler:cron:install --user=www-data

Comandos úteis de gerenciamento:

php core/cli/nimbly.php scheduler:project:list
sudo -u www-data php core/cli/nimbly.php scheduler:run --dry-run
sudo -u www-data /usr/local/bin/nimbly-scheduler-orchestrator
php core/cli/nimbly.php scheduler:cron:status

scheduler:run pega um lock global curto e dispara dois workers por projeto registrado: um executa jobs:run 10 para entregar a fila sem demora, e o outro executa o schedule:run do projeto. Os workers registram a conclusão e o código de saída no log do agendador do host. Assim, uma tarefa longa de uma aplicação não atrasa as tarefas de minuto de outro projeto nem os próprios alertas na fila. Os executores de jobs usam um lock por projeto, para que o worker direto da fila e a tarefa obrigatória jobs-run não peguem o mesmo job ao mesmo tempo. O agendador do projeto mantém o próprio lock, para que os ticks seguintes do cron não dupliquem uma execução longa. Valores antigos de default_delay_after_seconds no registro são ignorados.

scheduler:run sempre dispara imediatamente os workers de todos os projetos registrados (nunca espera antes de iniciar), para que um projeto lento não atrase o disparo no tick seguinte do cron. Em vez disso, a concorrência é limitada dentro de cada scheduler:worker: antes de executar o jobs:run/schedule:run real do projeto, um worker espera por uma vaga num semáforo compartilhado e a libera ao terminar. Isso evita que uma frota grande jogue todos os workers num host de uma CPU no mesmo instante. O limite padrão é de 5 workers simultâneos no host inteiro e pode ser configurado com NIMBLY_SCHEDULER_MAX_CONCURRENCY; os arquivos de lock das vagas ficam em NIMBLY_SCHEDULER_SEMAPHORE_DIR (padrão: a pasta temporária do sistema).

Ver esta página em Markdown