
Monitoramento centralizado para múltiplas instâncias Nextcloud
Monitorização centralizada para múltiplas instâncias Nextcloud
O NcStatusCheck é uma ferramenta de monitorização que permite acompanhar o estado de vários servidores Nextcloud a partir de uma única interface web. Analisa as versões do Nextcloud e do PHP e fornece recomendações de atualização.

< 15d / < 7d)occ app:list em relação à loja de aplicações Nextcloud para sinalizar aplicações a rever — bloqueantes (bloqueiam atualização, incompatíveis, aplicações de teste em produção) mais sinais informativos de maturidade/turbulência (publicado recentemente, pré-1.0, compilação alpha/beta/rc, explosão de lançamentos, versão acabada de lançar)📦) e um gémeo "Contentores a vigiar" (🐳) agregam cada aplicação/imagem Docker sinalizada em todas as instâncias numa única entrada cada, com filtros por tipo por grupo e um popup a listar as instâncias afetadas e as suas versõesALERT_WEBHOOK_URL), e/ou um email de resumo por lote (ALERT_EMAIL_TO) enviado por submissão SMTP direta ao seu servidor de correio (PHPMailer incorporado; recai no MTA local quando não está configurado nenhum relay SMTP). Também abrange sinais de evolução lenta (ALERT_CHECKS): expiração de certificado SSL (escalonado), versão Nextcloud vulnerável/depreciada, sonda Push obsoleta, relatório de auditoria crítico, descoberta de aplicação bloqueante — um alerta por nova condição, sem spam de lembretesnc_update_grace_days), para evitar perseguir um lançamento com bugs do mesmo dia⚠️ avisos, 📦 aplicações, 🔄 Docker, 🔒 SSL, 🔴 offline, 🚧 manutenção) quando há algo a reportarnc-audit.shlocalStoragechmod 700) para a instância Nextcloud remota, atualizado automaticamente à medida que as opções mudamncstatuscheck/ ├── Frontend │ ├── index.php # Main entry point │ ├── template.html # HTML template (dashboard) │ ├── admin.html # Administration interface │ ├── detail.php # Server detail page │ ├── audit.php # Server-audit script distribution page (nc-audit.sh) │ ├── troubleshooting.php # Probe troubleshooting guide │ ├── app.js / admin.js / detail.js / audit.js / troubleshooting.js │ └── style*.css # One stylesheet per page family ├── APIs (HTTP) │ ├── api.php # Main monitoring API │ ├── detail-api.php # Detail page API (serverinfo + warnings + acks + availability) │ ├── push-api.php # Push reception + trigger + audit-report reception │ ├── ack-api.php # Warning acknowledge / unmute │ ├── admin-api.php # Version configuration routing │ ├── servers-admin-api.php # Server list management │ ├── nextcloud-versions-api.php # Official version scraping │ ├── nextcloud-apps-api.php # App store catalog (slim cache) for the apps audit │ ├── php-versions-api.php # PHP branch support data │ └── apps-warnings-api.php # Manual app warnings (known-bug list) CRUD ├── Shared modules (lib/) │ ├── auth.php # Auth + CSRF + URL redaction (defense in depth) │ ├── csrf-client.js # Auto-inject X-CSRF-Token in fetch() │ ├── nextcloud-client.php # Centralized HTTP client → remote Nextclouds │ ├── servers-store.php # Single source of truth for servers.json │ ├── uptime-state.php # Up/down state machine + transition journal + availability │ ├── alerts.php # Proactive alert dispatch: webhook + email digest │ ├── alerts-checks.php # Slow-signal alerts (SSL/version/push/audit/apps) + dedup state │ ├── smtp-mailer.php # SMTP transport adapter over vendored PHPMailer │ ├── phpmailer/ # Vendored PHPMailer (3 files + LICENSE, pinned in VERSION) │ ├── apps-warnings-manager.php # Manual app warnings storage │ ├── ui-common.js # NcUI: notify / confirm / prompt + shared app-audit messages │ ├── url-guard.php # Anti-SSRF (loopback, RFC1918, link-local…) │ ├── json-cache.php # Locked JSON read/write helpers │ └── version-config-manager.php # Version rules CRUD ├── Business logic │ ├── version-rules.php # NC / PHP status analysis engine │ ├── warnings-rules.php # Configuration warning engine │ ├── apps-rules.php # Installed-apps audit engine (store catalog cross-check) │ ├── cron-update.php # Full collection script, CLI only (twice a day) │ └── cron-ping.php # Lightweight up/down probe, CLI only (every 5 min) ├── Tools (never web-served — blocked by nginx/.htaccess) │ ├── tools/nc-audit.sh # Standalone server audit script (root, read-only) │ └── tools/ncstatuscheck-push-core.sh # Generic Push probe core (fleet-shared) ├── Tests │ └── tests/run.php # Plain-PHP test suite (no framework): php tests/run.php ├── Configuration │ ├── config.php # Central configuration (git-ignored) │ └── servers.json # Server list with tokens (git-ignored) └── Cache ├── servers_data.json # All server data ├── serverinfo_.json # Raw per-server cache (Extended) ├── push_.json # Last push payload per server ├── ack_.json # Acknowledged warnings per server ├── audit_.json # Last nc-audit.sh report per server ├── version-config.json # Version configuration ├── uptime_state.json # Up/down state per server (mini uptime) ├── uptime_history.json # Bounded up/down transition journal (availability % + incidents) ├── alerts_state.json # "Already alerted" memory of the check alerts ├── nextcloud_versions.json # Official NC versions ├── nextcloud_apps.json # App store slim catalog (apps audit) ├── apps-warnings.json # Manual app warnings (admin-curated) ├── .csrf_secret # CSRF HMAC secret (binary, 0600) └── *.log # Activity logs
deploy/ansible/ # Fleet deployment of the Push core (Ansible / scp) deploy/docker/ # Container packaging of the monitor itself
## 🔌 Modos de recolha
Os modos não são mutuamente exclusivos — um servidor pode ser Estendido e Push simultaneamente.
| Modo | Emblema | Fonte | Dados recolhidos |
|------|---------|-------|------------------|
| **Básico** | *(nenhum)* | `/status.php` + cabeçalhos HTTP | Versão do Nextcloud (PHP/servidor web se exposto) |
| **Estendido** | `⚡ Estendido` (roxo → laranja em erro/obsoleto) | `/ocs/v2.php/apps/serverinfo/api/v1/info` com `NC-Token` | Versão do NC, PHP, servidor web, OPcache, Redis, BD, utilizadores ativos… |
| **Push** | `📡 Push` (azul → laranja em erro/obsoleto) | POST para `push-api.php` | Dados enviados pela instância NC remota via script cron |
O **NC-Token do serverinfo** está disponível em **Definições do Nextcloud → Administração → Sistema**.
O **token push** é gerado a partir da interface de administração; o administrador fornece um script bash cron pronto a usar (`chmod 700`) para implementar na instância monitorizada.
Os dados do modo Estendido são fornecidos pela aplicação [nextcloud/serverinfo](https://github.com/nextcloud/serverinfo), que deve estar instalada e ativada na instância monitorizada.
**Comportamento de fallback**: se a API Estendida estiver inacessível (erro de ligação, token inválido, aplicação não instalada), o NcStatusCheck recorre automaticamente ao `/status.php` para obter pelo menos a versão do Nextcloud.
**Limite de obsolescência do Push**: um servidor push é considerado obsoleto se não forem recebidos dados dentro de `auto_push_interval + 30 minutos`. O intervalo push predefinido é de 12 horas.
### Colunas da tabela do painel
O painel principal mostra 5 colunas: **Servidor** | **Versão NC** | **PHP** | **Sondas** | **Saúde**
A coluna **Sondas** apresenta os modos de recolha ativos para cada servidor:
- Emblema `⚡ Estendido` (roxo, fica laranja em caso de erro de ligação ou dados obsoletos)
- Emblema `📡 Push` (azul, fica laranja quando não são recebidos dados dentro do limiar)
- Ambos os emblemas podem aparecer simultaneamente se ambos os modos estiverem ativos
- Sem emblema = apenas modo Básico
### Coluna Saúde
A coluna Saúde só mostra algo quando há algo a resolver:
| Indicador | Emblema | Significado |
|-----------|---------|-------------|
| Offline | `🔴 Offline` | Instância inacessível (sonda HTTP falhou), com "offline há X" |
| Avisos ativos | `⚠️ N` | N problemas de configuração |
| Auditoria de aplicações | `📦 N` | N aplicações instaladas para rever (bloqueantes de atualização/incompatíveis) |
| Expiração SSL | `🔒 N d` | Certificado expira em breve — laranja `< 15d`, vermelho `< 7d` ou expirado |
| Atualizações Docker | `🔄 M` | M atualizações de contentores disponíveis |
| Tudo OK | *(vazio)* | Nada a relatar |
| Sem dados | `?` | Modo Básico sem dados push |
#### Up/down & expiração SSL
O NcStatusCheck mantém um estado **mínimo** up/down por servidor (apenas estado atual + data da última alteração — sem séries temporais, sem página de histórico). "Up" significa que a sonda HTTPS de saída alcançou a instância; um emblema vermelho **Offline** aparece apenas quando está down. Durante essa mesma sonda HTTPS, a **expiração do certificado SSL** é lida gratuitamente (`CURLOPT_CERTINFO`) e é mostrada quando se aproxima. Ambos são visíveis na totalidade na página de detalhe. *Nota: estas verificações de saída não se aplicam a instâncias exclusivamente Push com as quais o monitor nunca contacta.*
#### Auditoria de aplicações (`📦`)
Quando um servidor Push reporta as suas aplicações instaladas (`occ app:list`, script push v3+), o NcStatusCheck cruza-as com o catálogo da loja de aplicações do Nextcloud e sinaliza aplicações que merecem revisão. **Apenas sinalização** — a ferramenta nunca desativa nada; mostra candidatos (não pode saber se uma aplicação é realmente utilizada). Apenas são utilizados sinais **factuais e binários**. O emblema `📦 N` conta descobertas bloqueantes (nenhuma versão compatível para a versão atual do NC, nenhuma versão para NC N+1 → bloqueia a atualização, ou uma aplicação de teste/desenvolvimento deixada ativa em produção). Descobertas informativas (aplicação desatualizada na instância, abandonada a montante, PHP incompatível) são mostradas apenas na página de detalhe. As descobertas podem ser silenciadas através do mesmo mecanismo de reconhecimento que os avisos.
### `servers.json` format```json
[
{"url": "https://cloud.example.com"},
{"url": "https://cloud2.example.com", "serverinfo_token": "abc123def456"},
{"url": "https://cloud3.example.com", "serverinfo_token": "...", "push_token": "xyz789"}
]
server { server_name monitoring.your-domain.com; root /var/www/ncstatuscheck; index index.php;
# HTTP Basic Authentication
auth_basic "Monitoring Access";
auth_basic_user_file /etc/nginx/.htpasswd;
# Protect sensitive files/dirs (tests/run.php has no CLI-only guard — it must
# never be reachable over HTTP; same blocklist as deploy/docker/nginx.conf)
location ~ ^/(cache/|\.git|deploy/|tools/|tests/) {
deny all;
return 404;
}
# .txt covers servers.txt (legacy server list — real monitored URLs)
location ~* \.(log|json|txt)$ {
deny all;
return 404;
}
# Security headers for static HTML pages (admin.html, template.html).
# PHP pages (index.php, detail.php) send the same headers themselves
# via send_security_headers() in lib/auth.php.
location ~* \.html$ {
add_header X-Content-Type-Options nosniff always;
add_header X-Frame-Options DENY always;
add_header Referrer-Policy no-referrer always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'; frame-ancestors 'none'; base-uri 'self'" always;
try_files $uri =404;
}
# Standard PHP configuration (adjust the socket to your PHP version —
# use a security-supported one: 8.2 has been EOL since December 2025)
location ~ \.php$ {
fastcgi_pass unix:/run/php/php8.4-fpm.sock;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
location / {
try_files $uri $uri/ =404;
}
}
> **Apache**: o repositório inclui arquivos `.htaccess` espelhando as regras de `deny`
> acima (raiz: bloqueia `*.log`/`*.json`/`*.txt` e `.git`; `cache/`, `tools/`,
> `deploy/`, `tests/`: `Require all denied`). Eles só funcionam se o vhost definir
> `AllowOverride FileInfo AuthConfig` (ou `All`) — o padrão do Debian para
> `/var/www` é `AllowOverride None`, nesse caso replique as regras diretamente no
> vhost. A autenticação HTTP Basic ainda precisa ser configurada no vhost de qualquer
> forma.
### Implantação
1. **Clone o repositório**```bash
git clone https://gitlab.com/jp.louvel/ncstatuscheck.git
cd ncstatuscheck
Edite `config.php` e ajuste os caminhos e a URL para o seu ambiente:```php
define('MONITOR_PATH', '/var/www/ncstatuscheck');
define('MONITOR_URL', 'https://monitoring.your-domain.com'); // your public URL
define('CACHE_DIR', MONITOR_PATH . '/cache');
Os servidores são gerenciados diretamente da interface de administração (⚙️ botão Admin).
Você também pode criar servers.json manualmente:```json
[
{"url": "https://cloud.example.com"},
{"url": "https://nextcloud.mycompany.org", "serverinfo_token": "your_token_here"}
]
> **Migração de `servers.txt`**: se um arquivo `servers.txt` existir, ele é automaticamente convertido para `servers.json` no primeiro acesso. Você pode então deletar `servers.txt`.
4. **Definir permissões**
nginx/PHP-FPM executam como seu próprio usuário (`www-data` no Debian/Ubuntu, `nginx`/`apache` na família RHEL — ajuste abaixo) — se você clonou como seu próprio usuário de login, esse usuário quase certamente não será `www-data` ou estará em seu grupo, então apenas `chmod` deixa o servidor web **sem acesso algum**, nem mesmo leitura (cada requisição retorna 403/404):```bash
chown -R www-data:www-data /var/www/ncstatuscheck # adjust the user:group to your distro
chmod 750 /var/www/ncstatuscheck
chmod 750 cache img
chmod 640 *.php *.html *.js *.css *.md
chmod 600 config.php servers.json servers.txt # secrets / serverinfo & push tokens
chmod 660 cache/*.json cache/*.log
Se
servers.jsonainda não existir (você está deixando a IU de administração criá-lo em vez da etapa manual acima), ochmod 600acima simplesmente não tem nada para agir — tudo bem:ServersStore::save()faz o chmod do arquivo para0600por si só em cada gravação, então umservers.json(re)criado através da IU de administração nunca fica legível para grupo/mundo com tokens de serverinfo/push dentro.
6. **Tarefas agendadas (opcional)**
Dois crons complementares — instale ambos **na mesma chamada `crontab -`**:
`crontab -` instala um crontab inteiro novo a partir do stdin, não anexa, então
executá-lo duas vezes (uma por linha) deixa apenas o *segundo* job — o primeiro
desaparece silenciosamente, sem erro. Isso também preserva qualquer coisa já no seu
crontab (`crontab -l` pipeado primeiro) em vez de apagá-lo:```bash
(crontab -l 2>/dev/null; cat <<'EOF'
# Full collection (NC/PHP versions, serverinfo, app-store catalog) — twice a day
0 6,18 * * * cd /var/www/ncstatuscheck && php cron-update.php
# Lightweight reachability probe (status.php only -> up/down state) — every 5 min
*/5 * * * * cd /var/www/ncstatuscheck && php cron-ping.php
EOF
) | crontab -
Re-executar isto anexa duplicados se estas linhas já estiverem presentes — verifique
com crontab -l primeiro se não tiver a certeza.
O cron-ping.php é intencionalmente mínimo: apenas verifica o status.php de cada
instância e atualiza o estado ativo/inativo (cache/uptime_state.json), para que possa
ser executado com frequência sem carga. Uma instância só é sinalizada como inativa após
UPTIME_FAIL_THRESHOLD sondagens falhadas consecutivas (padrão 2 → ~10 min com uma
cadência de 5 min); a recuperação para ativo é imediata. O cron-update.php completo
permanece inalterado para todo o resto.
Em vez dos passos 1–6 acima, o NcStatusCheck também pode ser executado como uma pequena
stack docker compose (PHP-FPM + nginx + um contentor cron) — o repositório é montado
por bind tal como está, sem passo de compilação ou Composer, por isso espelha exatamente
o layout bare-metal, apenas contentorizado. Serve apenas HTTP simples (porta 8080 por
padrão) — coloque o seu próprio proxy reverso de terminação TLS à frente.
A configuração completa — configuração, as armadilhas de permissões (uid 82, pré-criação de
servers.json), HTTP Basic Auth, cron, atualizações e backups — reside inteiramente em
deploy/docker/README.md. Comece por aí; esta secção é
intencionalmente apenas um apontador, para evitar manter duas cópias dos mesmos passos
sincronizadas.
https://monitoring.seu-dominio.comAcessível clicando em qualquer nome de servidor ou no seu indicador de Saúde.
Para servidores Básicos (sem sonda Alargada ou Push), uma página simplificada mostra os dados disponíveis (versão NC, servidor web, protocolo HTTP) com um aviso e uma sugestão para ativar uma sonda.
Para servidores Alargados / Push, a página de detalhe completa exibe secções separadas:
O NcStatusCheck expõe vários endpoints REST:
API principal (api.php)
GET ?action=get_data — Obter dados (cache ou atualização)POST ?action=refresh_data — Forçar atualização de todos os servidoresAPI Push (push-api.php)
POST com cabeçalho push_token — Receber dados push de uma instância NC remotaPOST ?action=request_push_all — Solicitar um push imediato de todos os servidores push configurados (define um sinalizador de acionamento consumido pelo script cron remoto)O script cron gerado pela IU de administração está dividido em dois: um núcleo genérico
/usr/local/bin/ncstatuscheck-push.sh— idêntico em todos os servidores (toda a lógica) — controlado por uma pequena configuração por instância/etc/ncstatuscheck/<slug>.conf(SERVER_URL,SLUG,OCC_CMD,DOCKER_ENABLED,SKOPEO_ENABLED). É invocado comoncstatuscheck-push.sh /etc/ncstatuscheck/<slug>.conf [--test]. O núcleo recusa-se a ler uma configuração gravável por grupo/outros (anti-injeção de código).É multi-alvo (fan-out): os dados são recolhidos uma vez e enviados para cada monitor listado em
/etc/ncstatuscheck/targets-<slug>.conf(uma linhaurl|push_token[|http_user|http_pass]por monitor). O administrador de cada monitor emite um comando idempotente para se registar.Várias instâncias Nextcloud no mesmo anfitrião: os caminhos por instância são sufixados por um derivado do URL monitorizado (ex.: → ): , , , , estado . Apenas o núcleo é partilhado, pelo que instâncias co-localizadas nunca colidem.
API de detalhe (detail-api.php)
GET ?server=<url> — Dados completos do serverinfo + avisos calculados para um servidor Alargado/PushAPIs de administração
admin-api.php — Configuração de versõesservers-admin-api.php — Gestão de servidores (get_servers, add_server, remove_server, update_server_token, generate_push_token, remove_push_token)nextcloud-versions-api.php — Versões oficiaisnc-audit.sh)Um subsistema separado da monitorização: um script bash apenas de leitura autónomo
(tools/nc-audit.sh) executado como root num servidor Nextcloud para uma auditoria
única / mensal da afinação web + PHP + base de dados, cruzada com a capacidade física da
máquina (RAM, CPU, tipo de disco). Destinado a uma oferta de supervisão gerida:
o cliente instala-o, o monitor apenas recebe relatórios — sem necessidade de acesso à
máquina/rede. O script apenas lê a configuração (sem alterações), imprime um relatório
colorido e escreve uma cópia em /tmp.
O que verifica: capacidade do servidor (RAM/CPU/SSD-HDD, swappiness, deteção de servidor
partilhado) · Nextcloud (versões, cron, cache, Redis runtime, tipo de BD, logs) ·
PHP/PHP-FPM (SAPI de serviço real, OPcache runtime, memória multi-pool) · Apache
(memória de trabalhador consciente de MPM) · Nginx · PostgreSQL · MariaDB · higiene de
segurança (fail2ban ou CrowdSec + bouncer + blocklist comunitária; atualizações
pendentes / reinício / serviços em bibliotecas obsoletas) · reconciliação do orçamento
de RAM (InnoDB + FPM + Apache vs RAM real) · análise aprofundada com ferramentas opcionais
se já presentes (mysqltuner, pt-variable-advisor, apache2buddy, sar/iostat).```bash
curl -fsSL https://gitlab.com/jp.louvel/ncstatuscheck/-/raw/master/tools/nc-audit.sh -o /usr/local/bin/nc-audit.sh chmod 700 /usr/local/bin/nc-audit.sh
sudo nc-audit.sh # auto-detect, dedicated server sudo nc-audit.sh /var/www/nextcloud # explicit path (or NC_PATH=…) sudo NC_RAM_BUDGET_PCT=50 nc-audit.sh # shared host: size to 50% of RAM
**Hosts multi-instância** (vários Nextclouds + um banco de dados compartilhado). `NC_RAM_BUDGET_PCT`
é então o orçamento **total** da pilha; `NC_PHP_SHARE_PCT`% disso (padrão 60, o restante
cobre DB + web + SO — reduza-o em servidores com uso intensivo de DB) é a parcela do PHP, dividida entre
pools FPM por **peso** (uma importância relativa — não uma porcentagem, não MB) para fornecer um
valor alvo de `pm.max_children` por pool:```
target = PHP_share × (weight / Σ weights) / ~50 MB per process
O alvo é um teto que o orçamento permite, não um valor que você deve definir (apenas levante um pool que realmente sature). Os pesos são por sua conta — a ferramenta nunca os adivinha.```bash
sudo NC_RAM_BUDGET_PCT=70 NC_INSTANCES="poolA:4,poolB:2,poolC:1" nc-audit.sh
sudo NC_RAM_BUDGET_PCT=70 nc-audit.sh --tune-fpm
**Retorno de relatório (push-back)** (opcional, reutiliza a infraestrutura Push): `nc-audit.sh --push
/etc/ncstatuscheck/<slug>.conf` executa a auditoria e envia (POST) o relatório para o(s) monitor(es), que o armazenam e mostram na página de detalhes do servidor (seção "🩺 Auditoria do servidor"). Normalmente um cron mensal. A página web (admin, beta) em `audit.php` distribui o script (download + inline + one-liner do GitLab) e mostra sua versão.
> **Ferramentas de análise profunda nunca são instaladas** pelo script — elas só são executadas se já estiverem presentes (sem `curl | bash`, sem instalação automática), cada uma limitada por `timeout`.
## 🔧 Configuração avançada
### Personalização das regras de versão
As regras de avaliação são configuráveis através da interface de administração:
**Status do Nextcloud:**
- `dev` — Versão de desenvolvimento
- `stable` — Versão estável atual
- `oldstable` — Versão estável anterior suportada
- `deprecated` — Versão obsoleta
**Status do PHP:**
- `recommended` — Versão recomendada
- `supported` — Versão suportada
- `deprecated` — Versão obsoleta
### Variáveis de configuração
Edite `config.php` para adaptar a configuração:```php
// Environment: 'dev' or 'prod'
define('ENV', 'prod');
// Paths and URLs
define('MONITOR_PATH', '/var/www/ncstatuscheck');
define('MONITOR_URL', 'https://monitoring.your-domain.com');
// Main server cache duration
define('CACHE_MAX_AGE', 86400); // 24 hours
// Official Nextcloud versions cache duration
define('VERSIONS_CACHE_AGE', 86400);
// Consecutive failed probes before a server is marked "down" (min 1)
define('UPTIME_FAIL_THRESHOLD', 2);
// Proactive alerts — webhook on a confirmed up/down state change.
// Empty URL = disabled. Format: 'slack' (default, also Mattermost/Google Chat),
// 'discord', or 'raw' (structured JSON). The URL usually carries a secret, so it
// is never logged in full — see config-example.php for details.
define('ALERT_WEBHOOK_URL', '');
define('ALERT_WEBHOOK_FORMAT', 'slack');
// Check alerts on top of up/down (cron-update cadence, 2×/day): SSL expiry
// tiers, vulnerable (below min_secure) or deprecated Nextcloud version, stale
// Push data, critical audit report, blocking apps-audit finding. Edge-triggered with a persisted state
// (cache/alerts_state.json): one alert per NEW condition, no reminders, re-arms
// when resolved (renewed cert, fixed/acked app…). First run arms silently.
define('ALERT_CHECKS', 'ssl,version,push_stale,audit,apps'); // '' = up/down only
define('ALERT_SSL_DAYS', '30,14,7'); // days-left tiers
// Email channel, independent of the webhook (either one arms the alerting).
// One digest mail per batch. Recommended transport: direct SMTP submission to
// your mail server (vendored PHPMailer, lib/phpmailer/ — nothing to set up on
// the host). Without ALERT_SMTP_HOST it falls back to PHP mail() (local MTA).
define('ALERT_EMAIL_TO', ''); // comma list of recipients, '' = off
define('ALERT_EMAIL_FROM', ''); // default: ncstatuscheck@<hostname>
define('ALERT_SMTP_HOST', ''); // e.g. 'mail.example.org', '' = mail() fallback
define('ALERT_SMTP_PORT', 587);
define('ALERT_SMTP_SECURITY', 'starttls'); // 'starttls' | 'tls' | 'none'
define('ALERT_SMTP_USER', '');
define('ALERT_SMTP_PASS', '');
Veja
config-example.phppara a lista completa e comentada de opções (incluindoDEMO_MODEePUSH_SCRIPT_VERSION).
lib/csrf-client.js + csrf_require()).htaccess fornecidos para Apache; servers.json com chmod 0600 automaticamente (tokens internos)X-Frame-Options, nosniff, Referrer-Policy) em cada página servida por PHPO config.php está no gitignore e é editado manualmente por servidor, então ele se desvia — silenciosamente, já que quase toda constante tem um fallback no código. Um banner no topo da página admin relata o que está realmente errado, e apenas quando algo está: um PUSH_SCRIPT_VERSION deixado para trás por um bump, nenhum transporte de alerta configurado, uma tag de fechamento à direita emitindo um byte antes de qualquer header(), um diretório de cache não gravável, constantes ausentes e silenciosamente caindo para padrões.
Apenas leitura por design e sem ação de salvar, pela mesma razão que a guia Notificações: config.php é de propriedade do root e contém segredos. O conteúdo do arquivo nunca viaja — apenas fatos sobre ele — e nenhum segredo é lido.
Tudo acima é a nível de aplicação: qualquer um na internet ainda pode alcançar o monitor e sondá-lo, e apenas a senha os impede. A guia de filtragem de IP gera as regras que colocam uma lista de permissões na frente do aplicativo, para que hosts desconhecidos não possam falar com ele. É defesa em profundidade, não uma substituição para autenticação Basic ou os tokens de push — e ela apenas produz texto para revisar e colar, nunca escreve uma configuração de servidor web ou firewall.
Duas classes de origem, deliberadamente desiguais, para que um servidor monitorado comprometido não possa alcançar o admin:
| Classe | Quem | Pode alcançar |
|---|---|---|
push | instâncias monitoradas apenas no modo Push | /push-api.php, nada mais |
admin | bastião / VPN / IP fixo do escritório | tudo |
Instâncias consultadas no modo Básico/Estendido não abrem conexão de entrada e não recebem entrada na lista de permissões.
Os endereços vêm de duas fontes, e a diferença importa: o registro DNS de um domínio monitorado é seu endereço de ingresso, enquanto seu push sai de seu egresso. Onde diferem, apenas o segundo funciona. push-api.php portanto registra o endereço real de origem de cada push (source_ip no cache de push), e a guia permite esse endereço, relatando a incompatibilidade. Até que um servidor tenha feito push uma vez, ele recai para DNS A+AAAA e informa isso.
Três saídas:
conf.d autocontido (geo + map) mais uma única linha if ($ncsc_forbidden) { return 403; } no vhost. Não é necessário duplicar o bloco fastcgi, arquivos estáticos também são cobertos (admin.html é um), e /.well-known/acme-challenge/ permanece aberto para que a renovação do certificado não quebre silenciosamente.<LocationMatch> com um lookahead negativo mais um <Location> para o endpoint de push, para que as duas seções não se sobreponham e nada dependa da ordem de merge do Apache. Todos os endereços de uma regra vão em uma linha Require ip: várias linhas dentro de <RequireAll> são combinadas com AND, o que ninguém pode satisfazer.O gerador se recusa a emitir algo quando nenhum endereço de administração é fornecido, avisa quando o próprio endereço do operador não está coberto, e avisa quando a requisição veio através de um proxy (tanto geo quanto Require ip leem o par de transporte, então atrás de um proxy todo cliente parece igual). O trecho ufw gerado coloca a regra SSH primeiro, mantém a porta 80 aberta para o desafio HTTP-01, e explica a armadilha IPv6: ao contrário do nginx, que recusa um endereço v6 não listado, o ufw não filtra v6 a menos que IPV6=yes esteja definido — um host dual-stack estaria amplamente aberto via IPv6.
Limite conhecido, exibido na própria página: uma vez que as regras são aplicadas, esta guia não descobre nada novo. Um push recusado é rejeitado pelo servidor web antes de chegar ao PHP, então o endereço registrado permanece o último que passou — e ainda parece verificado. Duas consequências: adicionar um servidor Push significa regenerar e reaplicar as regras, ou seu primeiro push será recusado; e se o endereço de uma instância mudar, o novo só é legível no log de acesso do servidor web (grep 'push-api.php' access.log | grep ' 403 '). A guia, portanto, mostra a data da última visualização de cada endereço observado e o sinaliza quando está mais antigo que um ciclo completo de push perdido — o mesmo limite que o alerta push_stale, que cobre o mesmo ponto cego do outro lado.
Distinguindo um problema de filtragem de qualquer outro: um GET simples no endpoint de push separa as camadas claramente, sem efeito colateral e sem necessidade de token — execute-o da máquina em questão, já que o que é julgado é o endereço de saída dessa máquina:```bash
curl -sS -o /dev/null -w '%{http_code}\n' https://your-monitor/push-api.php
| Resposta | Significado |
|---|---|
| `403` | bloqueado pela filtragem de IP |
| `401` | filtragem passou, Basic auth está respondendo — o problema está em outro lugar |
| `405` | a requisição chegou à aplicação (GET não é um método aceito ali) |
| nothing / timeout | não é a filtragem: um filtro responde, ele não fica silencioso |
Reexecute com `-u user:password` para resolver uma dúvida sobre um `403`: se o código não mudar, é realmente a filtragem. Verificado tanto no nginx quanto no Apache (inclusive com `Require valid-user` ativado), o filtro responde *antes* da autenticação — e um `403` vindo da própria aplicação sempre carrega JSON no corpo.
A lógica dos snippets reside em `lib/hardening-rules.php`, que é pura e coberta por `tests/run.php`: os snippets são o produto aqui, e um snippet errado ou bloqueia o operador ou deixa uma brecha. Tanto a saída do nginx quanto a do Apache foram verificadas comportamentalmente (servidores reais, endereços de origem reais, incluindo tentativas de path-traversal da classe `push`).
### Análise automatizada (estágio `security` do CI)
A varredura de dependências (`npm/pnpm audit`, Snyk Open Source, Dependabot) é uma não-operação aqui: não há `package.json` e nem `composer.json` — nada a escanear. O risco está no código personalizado (~15 mil linhas de PHP, ~6 mil de JS) e nos scripts shell que são executados **como root** nas instâncias monitoradas (`tools/*.sh`). O pipeline está direcionado para isso:
| Job | Ferramenta | Bloqueante | Escopo |
|---|---|---|---|
| `secrets_scan` | gitleaks | sim | segredos commitados (working tree) |
| `sast_semgrep` | semgrep (`p/php`, `p/javascript`, `p/owasp-top-ten`) | sim | SSRF, falta de authz/CSRF, XSS |
| `shellcheck` | shellcheck (`--severity=warning`) | sim | `tools/*.sh` — root em hosts clientes |
| `dockerfile_misconfig` | trivy misconfig | sim | `deploy/docker/` |
| `container_cve` | trivy image | não (`allow_failure`) | a imagem que `deploy/docker` constrói, mais `nginx:alpine` |
| `ui_tests` | node (sem deps) | sim | invariantes de escaping de `lib/ui-common.js` (ambas as regressões XSS passadas) |
| `phpmailer_freshness` | GitHub API | não (`allow_failure`) | pin vendido vs versão upstream |
| `deploy_selfcheck` | nc-selfcheck.sh | sim | o ruleset nginx enviado (regras de negação + cabeçalhos de segurança) montado em um contêiner descartável |
Todos os trabalhos bloqueantes têm uma **linha de base com zero achados**, então qualquer novo alerta é um sinal real. Duas decisões deliberadas, documentadas inline em `.gitlab-ci.yml`:
- **`php.lang.security.injection.echoed-request` é excluída** do semgrep: ela sinaliza todo `echo json_encode()` como XSS, que é exatamente o que todo endpoint de API aqui faz legitimamente (respostas JSON, não HTML). Representou 10 de 10 achados na primeira execução, todos falsos. Mantê-la treinaria todos a ignorar o trabalho.
- **Os dois trabalhos `allow_failure` relatam fatos upstream** (um CVE no `nginx:alpine` ou um cujo conserto ainda não chegou ao branch do Alpine, um novo lançamento do PHPMailer) que uma solicitação de merge não pode corrigir. Vermelho mas tolerado é o sinal preciso — "hora de reconstruir/atualizar o vendoring" — não um motivo para bloquear trabalho não relacionado. `phpmailer_freshness` relata uma API GitHub inacessível ou com limite de taxa como um *skip*, nunca como "desatualizado".
- **`container_cve` escaneia a imagem que constrói, não a tag `FROM`.** O Dockerfile endurece a base com `apk --no-cache upgrade` (a imagem oficial do PHP fica atrás dos repositórios Alpine — ela enviou c-ares 1.34.6-r0 enquanto 1.34.8-r0, corrigindo CVE-2026-33630, já havia sido publicada). Escanear a tag base reportaria, portanto, CVEs que a imagem enviada não possui mais: um trabalho permanentemente laranja que ninguém lê.
As listas de permissão são intencionalmente estreitas: `.gitleaks.toml` desculpa strings de placeholder **literais**, nunca arquivos de documentação inteiros (permitir `README.md` cegaria a varredura no dia em que um segredo real for colado nele) — então um novo token de exemplo na documentação deve ser adicionado ali. `.trivyignore` contém uma única entrada, `DS-0002`, argumentada no arquivo: o mestre php-fpm deve iniciar como root para rebaixar seus workers para `www-data` (uid 82).
### Verificação pós-implantação (`nc-selfcheck.sh`)
O CI pode travar a configuração *enviada* (o trabalho `deploy_selfcheck` acima monta o ruleset nginx em um contêiner e o testa), mas não pode verificar o servidor para o qual você realmente fez a implantação — host diferente, credenciais Basic-auth, permissões de sistema de arquivos. `tools/nc-selfcheck.sh` fecha essa lacuna. É um script bash independente e somente leitura (mesmo modelo que `nc-audit.sh`) que você executa após cada implantação:```bash
# Black-box, no credentials: confirms Basic auth is enforced (401) and that
# sensitive files are blocked (cache/, servers.*, .git, config.php source).
bash tools/nc-selfcheck.sh https://monitoring.example.com
# + security headers behind Basic auth:
bash tools/nc-selfcheck.sh -u user:pass https://monitoring.example.com
# + filesystem checks (run ON the host): servers.json / config.php / CSRF-secret
# permissions, and a stray closing "?>" in config.php.
bash tools/nc-selfcheck.sh --webroot /var/www/ncstatuscheck https://monitoring.example.com
Ele sai com código não-zero em qualquer descoberta crítica (vazamento de fonte, arquivo secreto desbloqueado, armazenamento de token legível mundialmente, falta de autenticação Basic), então pode barrar uma implantação — conecte-o ao seu script de sincronização/implantação como uma etapa posterior. WARN/INFO nunca falham a execução.
// In config.php define('ENV', 'dev');
Em modo de desenvolvimento, informações adicionais são exibidas (versão do PHP, servidor web).
### Teste do servidor
Use a interface de administração para adicionar um servidor pela URL. O servidor será consultado na próxima atualização de dados.
### Logs de depuração
Verifique os arquivos de log em `cache/`:
- `monitor.log` — Logs gerais da aplicação
- `cron.log` — Logs completos do script de coleta (`cron-update.php`)
- `ping.log` — Logs leves de verificação de atividade (`cron-ping.php`)
- `alerts.log` — Despacho proativo de alertas (webhook/email), nunca registra segredos de webhook ou credenciais SMTP
### Suíte de testes```bash
php tests/run.php # plain-PHP assertions, no framework — exit 0 = all green
Cobre a lógica de negócio pura (regras de versão/aplicativos, avisos, máquina de estado de uptime e disponibilidade, máquina de estado de deduplicação/rearme de alertas, construtores de e-mail).
Abra uma nova issue com:
Este projeto está licenciado sob GNU AGPL v3.
NcStatusCheck é desenvolvido pela ézéo, uma cooperativa digital especializada em soluções de código aberto.
Precisa de ajuda? Consulte as issues ou entre em contato com a equipe ézéo.
| Secção | Campos |
|---|
| Sistema Nextcloud | Versão, modo de depuração, memcache local/distribuído, bloqueio de ficheiros, espaço em disco |
| PHP | Versão, memory_limit, upload_max_filesize, max_execution_time, FPM, OPcache |
| Servidor web | Nome + versão, protocolo HTTP |
| Base de dados | Tipo, versão, tamanho |
| Cache | Redis, taxa de acertos APCu |
| Utilizadores ativos | Últimos 5 min, 1 h, 24 h, 7 dias |
<slug>latest.ezeo.cooplatest_ezeo_coop<slug>.conf/etc/cron.d/ncstatuscheck-<slug>targets-<slug>.confncstatuscheck-push-<slug>.log…-<slug>.<md5>.lastNextcloud a correr em Docker (imagem oficial, compose, AIO): totalmente suportado — o
script é instalado no anfitrião (cron root + acesso ao daemon Docker), nunca dentro do
contentor, e o occ passa por docker exec:
OCC_CMD=docker exec -u www-data <container> php occ (contentor AIO: nextcloud-aio-nextcloud).
O gerador de scripts de administração tem uma predefinição de tipo de instalação que preenche
isto automaticamente. Nunca adicione -t (sem TTY sob cron); mantenha -u www-data (a imagem
oficial recusa occ como root).
Implementação / atualizações em frota: como o núcleo é um único ficheiro idêntico, atualizar
a lógica em muitos servidores = substituir esse ficheiro (o marcador ↑ sinaliza servidores
a executar uma versão antiga). Consulte deploy/ansible/ para um playbook
pronto a usar (ou um simples loop scp). O monitor permanece passivo — nunca envia código
para a frota; a âncora de confiança é o seu próprio acesso SSH, não o monitor.
Migração de uma instalação pré-v4 (script monolítico por instância): remova o antigo
/usr/local/bin/ncstatuscheck-push-<slug>.sh e /etc/cron.d/ncstatuscheck-<slug>
antes de instalar o núcleo + configuração (o targets-<slug>.conf é reutilizado tal como está),
caso contrário fará push duplicado.
hash_equals()request_pushrequest_push_alltargets.conf copia silenciosamente cada push para um terceiro caso contrário<>"'& na ingestão, além de escapados no momento da renderizaçãotry/catch pode capturar, o que mataria a execução da coleta no meio do loop e, com ela, todos os alertas para toda a frota