
Monitoraggio centralizzato per istanze Nextcloud multiple
Monitoraggio centralizzato per più istanze Nextcloud
NcStatusCheck è uno strumento di monitoraggio che consente di tracciare lo stato di salute di più server Nextcloud da un'unica interfaccia web. Analizza le versioni di Nextcloud e PHP e fornisce raccomandazioni di aggiornamento.

< 15d / < 7d)occ app:list con l'app store di Nextcloud per segnalare le app da revisionare — bloccanti (che bloccano l'aggiornamento, incompatibili, app di test in produzione) più segnali informativi di maturità/turbolenza (pubblicate di recente, pre-1.0, build alpha/beta/rc, esplosione di release, versione appena rilasciata)📦) e un gemello "Container da controllare" (🐳) aggregano ogni app / immagine Docker segnalata tra tutte le istanze in un'unica voce, con filtri per tipo per gruppo e un popup che elenca le istanze interessate e le loro versioniALERT_WEBHOOK_URL), e/o una email di riepilogo per batch (ALERT_EMAIL_TO) inviata tramite invio SMTP diretto al tuo server di posta (PHPMailer incluso; torna al MTA locale quando non è configurato un relay SMTP). Copre anche segnali a cambiamento lento (ALERT_CHECKS): scadenza certificato SSL (a livelli), versione Nextcloud vulnerabile/deprecata, sonda Push non aggiornata, report di audit critico, risultato app bloccante — un avviso per ogni nuova condizione, nessuno spam di promemorianc_update_grace_days), per evitare di inseguire una release con bug dello stesso giorno⚠️ avvisi, 📦 app, 🔄 Docker, 🔒 SSL, 🔴 offline, 🚧 manutenzione) quando c'è qualcosa da segnalarenc-audit.shlocalStoragechmod 700) per l'istanza Nextcloud remota, aggiornato automaticamente al variare delle opzionincstatuscheck/ ├── 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
## 🔌 Modalità di raccolta
Le modalità non si escludono a vicenda — un server può essere contemporaneamente Esteso e Push.
| Modalità | Badge | Fonte | Dati raccolti |
|----------|-------|-------|---------------|
| **Base** | *(nessuno)* | `/status.php` + intestazioni HTTP | Versione di Nextcloud (PHP/web server se esposti) |
| **Estesa** | `⚡ Estesa` (viola → arancione in caso di errore/dati obsoleti) | `/ocs/v2.php/apps/serverinfo/api/v1/info` con `NC-Token` | Versione NC, PHP, web server, OPcache, Redis, DB, utenti attivi… |
| **Push** | `📡 Push` (blu → arancione in caso di errore/dati obsoleti) | POST a `push-api.php` | Dati inviati dall'istanza NC remota tramite script cron |
Il **serverinfo NC-Token** è disponibile in **Impostazioni Nextcloud → Amministrazione → Sistema**.
Il **token push** viene generato dall'interfaccia di amministrazione; l'amministratore fornisce uno script bash cron pronto all'uso (`chmod 700`) da distribuire sull'istanza monitorata.
I dati della modalità estesa sono forniti dall'app [nextcloud/serverinfo](https://github.com/nextcloud/serverinfo), che deve essere installata e abilitata sull'istanza monitorata.
**Comportamento di fallback**: se l'API Estesa non è raggiungibile (errore di connessione, token non valido, app non installata), NcStatusCheck torna automaticamente a `/status.php` per recuperare almeno la versione di Nextcloud.
**Soglia di obsolescenza Push**: un server push è considerato obsoleto se non sono stati ricevuti dati entro `auto_push_interval + 30 minuti`. L'intervallo predefinito per push è di 12 ore.
### Colonne della tabella della dashboard
La dashboard principale mostra 5 colonne: **Server** | **Versione NC** | **PHP** | **Probes** | **Salute**
La colonna **Probes** mostra le modalità di raccolta attive per ciascun server:
- `⚡ Estesa` badge (viola, diventa arancione in caso di errore di connessione o dati obsoleti)
- `📡 Push` badge (blu, diventa arancione quando non vengono ricevuti dati entro la soglia)
- Entrambi i badge possono apparire simultaneamente se entrambe le modalità sono attive
- Nessun badge = solo modalità Base
### Colonna Salute
La colonna Salute mostra qualcosa solo quando c'è qualcosa su cui intervenire:
| Indicatore | Badge | Significato |
|------------|-------|-------------|
| Offline | `🔴 Offline` | Istanza non raggiungibile (sonda HTTP fallita), con "offline da X" |
| Avvisi attivi | `⚠️ N` | N problemi di configurazione |
| Revisione app | `📦 N` | N app installate da controllare (blocco aggiornamento/incompatibili) |
| Scadenza SSL | `🔒 N g` | Certificato in scadenza — arancione `< 15 g`, rosso `< 7 g` o scaduto |
| Aggiornamenti Docker | `🔄 M` | M aggiornamenti contenitori disponibili |
| Tutto OK | *(vuoto)* | Niente da segnalare |
| Nessun dato | `?` | Modalità Base senza dati push |
#### UP/DOWN e scadenza SSL
NcStatusCheck mantiene uno stato **minimale** UP/DOWN per server (solo stato corrente + data ultima modifica — nessuna serie temporale, nessuna pagina storico). "UP" significa che la sonda HTTPS in uscita ha raggiunto l'istanza; un badge rosso **Offline** appare solo quando è giù. Durante quella stessa sonda HTTPS, viene letta la **scadenza del certificato SSL** gratuitamente (`CURLOPT_CERTINFO`) e segnalata quando si avvicina. Entrambi sono visibili per intero nella pagina dei dettagli. *Nota: questi controlli in uscita non si applicano alle istanze solo Push che il monitor non contatta mai.*
#### Revisione app (`📦`)
Quando un server Push segnala le proprie app installate (`occ app:list`, script push v3+), NcStatusCheck le incrocia con il catalogo dell'app store di Nextcloud e segnala le app da esaminare. **Solo segnalazione** — lo strumento non disattiva mai nulla; mostra candidati (non può sapere se un'app è effettivamente utilizzata). Vengono utilizzati solo segnali **fattuali, binari**. Il badge `📦 N` conta i risultati bloccanti (nessuna release compatibile per la versione NC corrente, nessuna release per NC N+1 → blocca l'aggiornamento, o un'app di test/sviluppo lasciata abilitata in produzione). I risultati informativi (app obsoleta sull'istanza, abbandonata upstream, PHP incompatibile) vengono mostrati solo nella pagina dei dettagli. I risultati possono essere silenziati tramite lo stesso meccanismo di acknowledge degli avvisi.
### Formato `servers.json````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**: il repository include file `.htaccess` che rispecchiano le regole `deny` sopra (root: blocca `*.log`/`*.json`/`*.txt` e `.git`; `cache/`, `tools/`, `deploy/`, `tests/`: `Require all denied`). Funzionano solo se il vhost imposta `AllowOverride FileInfo AuthConfig` (o `All`) — il valore predefinito di Debian per `/var/www` è `AllowOverride None`, nel qual caso replicare le regole direttamente nel vhost. L'autenticazione HTTP Basic deve comunque essere configurata nel vhost in entrambi i casi.
### Distribuzione
1. **Clona il repository**```bash
git clone https://gitlab.com/jp.louvel/ncstatuscheck.git
cd ncstatuscheck
Modifica `config.php` e regola i percorsi e l'URL al tuo 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');
I server sono gestiti direttamente dall'interfaccia di amministrazione (pulsante ⚙️ Admin).
Puoi anche creare manualmente servers.json:```json
[
{"url": "https://cloud.example.com"},
{"url": "https://nextcloud.mycompany.org", "serverinfo_token": "your_token_here"}
]
> **Migrazione da `servers.txt`**: se esiste un file `servers.txt`, viene automaticamente convertito in `servers.json` al primo accesso. Puoi quindi eliminare `servers.txt`.
4. **Imposta i permessi**
nginx/PHP-FPM vengono eseguiti con il proprio utente (`www-data` su Debian/Ubuntu, `nginx`/`apache` su famiglia RHEL — regola di seguito) — se hai clonato come utente di login personale, quell'utente quasi certamente non sarà `www-data` o nel suo gruppo, quindi solo `chmod` lascia il server web **senza alcun accesso**, nemmeno in lettura (ogni richiesta restituisce 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.jsonnon esiste ancora (si lascia che l'interfaccia di amministrazione lo crei invece del passaggio manuale sopra), ilchmod 600sopra semplicemente non ha nulla su cui agire — va bene:ServersStore::save()imposta il permesso del file a0600stesso ad ogni scrittura, quindi unservers.json(ri)creato tramite l'interfaccia di amministrazione non rimane mai leggibile da gruppo/mondo con token serverinfo/push al suo interno.
6. **Attività pianificate (opzionale)**
Due cron complementari — installa entrambi **nella stessa chiamata `crontab -`**:
`crontab -` installa un nuovo crontab da stdin, non aggiunge, quindi eseguirlo due volte (una per riga) lascia solo il *secondo* job — il primo scompare silenziosamente, nessun errore. Questo preserva anche tutto ciò che è già nel tuo crontab (`crontab -l` reindirizzato prima) invece di cancellarlo:```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-eseguire questa operazione aggiunge duplicati se queste righe sono già presenti — controlla
prima con crontab -l in caso di dubbi.
cron-ping.php è intenzionalmente minimale: controlla solo status.php di ogni
istanza e aggiorna lo stato su/giù (cache/uptime_state.json), quindi può essere
eseguito frequentemente senza carico. Un'istanza viene segnata giù solo dopo
UPTIME_FAIL_THRESHOLD tentativi consecutivi falliti (default 2 → ~10 min con una
cadenza di 5 min); il ripristino a su è immediato. Il cron-update.php completo
rimane invariato per tutto il resto.
Invece dei passaggi 1–6 sopra, NcStatusCheck può anche essere eseguito come un piccolo
stack docker compose (PHP-FPM + nginx + un container cron) — il repository è montato
così com'è, nessun passaggio di build o Composer, quindi rispecchia esattamente la
disposizione bare-metal, solo containerizzata. Serve solo HTTP semplice (porta 8080
di default) — metti il tuo proxy inverso con terminazione TLS davanti.
La configurazione completa — le insidie dei permessi (uid 82, creazione preventiva di
servers.json), HTTP Basic Auth, cron, aggiornamenti e backup — è interamente in
deploy/docker/README.md. Inizia da lì; questa
sezione è intenzionalmente solo un indicatore, per evitare di mantenere due copie
delle stesse istruzioni sincronizzate.
https://monitoring.tuo-dominio.comAccessibile cliccando su qualsiasi nome del server o sul suo indicatore Health.
Per i server Basic (nessuna sonda Extended o Push), una pagina semplificata mostra i dati disponibili (versione NC, web server, protocollo HTTP) con un avviso e un suggerimento per abilitare una sonda.
Per i server Extended / Push, la pagina dei dettagli completa mostra sezioni separate:
NcStatusCheck espone diversi endpoint REST:
API principale (api.php)
GET ?action=get_data — Recupera dati (cache o refresh)POST ?action=refresh_data — Forza l'aggiornamento di tutti i serverAPI Push (push-api.php)
POST con header push_token — Riceve dati push da un'istanza NC remotaPOST ?action=request_push_all — Richiede un push immediato da tutti i server push configurati (imposta un flag di trigger consumato dallo script cron remoto)Lo script cron generato dall'interfaccia di amministrazione è diviso in due: un core generico
/usr/local/bin/ncstatuscheck-push.sh— identico su ogni server (tutta la logica) — guidato da una piccola configurazione per istanza/etc/ncstatuscheck/<slug>.conf(SERVER_URL,SLUG,OCC_CMD,DOCKER_ENABLED,SKOPEO_ENABLED). Viene invocato comencstatuscheck-push.sh /etc/ncstatuscheck/<slug>.conf [--test]. Il core rifiuta di caricare una configurazione scrivibile da gruppo/globale (anti code-injection).È multi-target (fan-out): i dati vengono raccolti una volta e inviati a ogni monitor elencato in
/etc/ncstatuscheck/targets-<slug>.conf(una rigaurl|push_token[|http_user|http_pass]per monitor). L'amministratore di ogni monitor emette un comando idempotente per registrarsi.Più istanze di Nextcloud su un host: i percorsi per istanza sono suffissati da un derivato dall'URL monitorato (es. → ): , , , , stato . Solo il core è condiviso, quindi le istanze co-localizzate non entrano mai in conflitto.
API Dettagli (detail-api.php)
GET ?server=<url> — Dati completi del serverinfo + avvisi calcolati per un server Extended/PushAPI di Amministrazione
admin-api.php — Configurazione delle versioniservers-admin-api.php — Gestione dei server (get_servers, add_server, remove_server, update_server_token, generate_push_token, remove_push_token)nextcloud-versions-api.php — Versioni ufficialinc-audit.sh)Un sottosistema separato dal monitoraggio: uno script bash autonomo e di sola lettura
(tools/nc-audit.sh) eseguito come root su un server Nextcloud per un audit una tantum / mensile
delle impostazioni di web + PHP + database, incrociato con la capacità fisica della macchina
(RAM, CPU, tipo di disco). Destinato a un'offerta di supervisione gestita:
il cliente lo installa, il monitor riceve solo i report — nessun accesso alla macchina/rete
richiesto. Lo script legge solo la configurazione (nessuna modifica), stampa
un report colorato e ne scrive una copia in /tmp.
Cosa controlla: capacità del server (RAM/CPU/SSD-HDD, swappiness, rilevamento
server condiviso) · Nextcloud (versioni, cron, cache, Redis runtime, tipo DB, log) ·
PHP/PHP-FPM (SAPI di servizio reale, OPcache runtime, memoria multi-pool) · Apache
(memoria worker MPM-aware) · Nginx · PostgreSQL · MariaDB · igiene di sicurezza
(fail2ban o CrowdSec + bouncer + blocklist della community; aggiornamenti in sospeso /
riavvio / servizi su librerie obsolete) · riconciliazione del budget RAM (InnoDB +
FPM + Apache vs RAM reale) · analisi approfondita con strumenti opzionali se già presenti
(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
**Host multi-istanza** (diverse istanze Nextcloud + un database condiviso). `NC_RAM_BUDGET_PCT`
è quindi il budget **totale** dello stack; il `NC_PHP_SHARE_PCT`% di esso (default 60, il resto
copre DB + web + OS — ridurlo su server pesanti per DB) è la quota PHP, suddivisa tra
i pool FPM per **peso** (una importanza relativa — non una percentuale, non MB) per
assegnare un target `pm.max_children` per pool:```
target = PHP_share × (weight / Σ weights) / ~50 MB per process
L'obiettivo è un tetto consentito dal budget, non un valore che devi impostare (aumenta solo un pool che si satura effettivamente). I pesi sono una tua scelta — lo strumento non li indovina mai.```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
**Push-back del report** (opzionale, riutilizza l'infrastruttura Push): `nc-audit.sh --push
/etc/ncstatuscheck/<slug>.conf` esegue l'audit e invia il report tramite POST al/i monitor, che lo memorizzano e lo mostrano nella pagina dei dettagli del server (sezione "🩺 Server audit"). Tipicamente un cron mensile. La pagina web (admin, beta) su `audit.php` distribuisce lo script (download + inline + GitLab one-liner) e mostra la sua versione.
> **Gli strumenti di analisi approfondita non vengono mai installati** dallo script — vengono eseguiti solo se già presenti (nessun `curl | bash`, nessuna installazione automatica), ciascuno limitato da `timeout`.
## 🔧 Configurazione avanzata
### Personalizzazione delle regole di versione
Le regole di valutazione sono configurabili tramite l'interfaccia di amministrazione:
**Stati di Nextcloud:**
- `dev` — Versione di sviluppo
- `stable` — Versione stabile corrente
- `oldstable` — Versione stabile precedente supportata
- `deprecated` — Versione deprecata
**Stati PHP:**
- `recommended` — Versione raccomandata
- `supported` — Versione supportata
- `deprecated` — Versione deprecata
### Variabili di configurazione
Modifica `config.php` per adattare la configurazione:```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', '');
Vedi
config-example.phpper l'elenco completo e commentato delle opzioni (incluseDEMO_MODEePUSH_SCRIPT_VERSION).
lib/csrf-client.js + csrf_require()).htaccess distribuiti per Apache; servers.json con permessi 0600 automaticamente (token al suo interno)X-Frame-Options, nosniff, Referrer-Policy) su ogni pagina servita da PHPconfig.php è in gitignore e viene modificato manualmente per ogni server, quindi si allontana — silenziosamente,
poiché quasi ogni costante ha un fallback nel codice. Un banner nella parte superiore della
pagina admin riporta cosa è effettivamente sbagliato, e solo quando qualcosa lo è:
un PUSH_SCRIPT_VERSION lasciato da un aggiornamento, nessun trasporto di avvisi configurato,
un tag di chiusura finale che emette un byte prima di qualsiasi header(), una directory cache
non scrivibile, costanti assenti che cadono silenziosamente nei valori predefiniti.
Solo lettura per progettazione e senza azione di salvataggio, per lo stesso motivo della
scheda Notifiche: config.php è di proprietà di root e contiene segreti. Il contenuto del file
non viaggia mai — solo informazioni su di esso — e nessun segreto viene letto.
Tutto quanto sopra è a livello di applicazione: chiunque su internet può comunque raggiungere il monitor e sondarlo, e solo la password li ferma. La scheda di filtraggio IP genera le regole che mettono una lista bianca davanti all'app, quindi host sconosciuti non possono parlarci affatto. È difesa in profondità, non un sostituto dell'autenticazione Basic o dei token push — e produce solo testo da rivedere e incollare, non scrive mai una configurazione del server web o del firewall.
Due classi di sorgente, deliberatamente diseguali, così un server monitorato compromesso non può raggiungere l'admin:
| Classe | Chi | Può raggiungere |
|---|---|---|
push | istanze monitorate solo in modalità Push | /push-api.php, nient'altro |
admin | bastion / VPN / IP fisso ufficio | tutto |
Le istanze interpellate in modalità Basic/Extended non aprono alcuna connessione in entrata e non ricevono alcuna voce nella lista bianca.
Gli indirizzi provengono da due fonti, e la differenza è importante: il record DNS di un dominio
monitorato è il suo indirizzo di ingresso, mentre il suo push parte dal suo indirizzo di
uscita. Dove differiscono, solo il secondo funziona. push-api.php pertanto
registra l'indirizzo sorgente reale di ogni push (source_ip nella cache push),
e la scheda lo inserisce nella lista bianca, segnalando la discrepanza. Finchè un server non ha effettuato
il push almeno una volta, si ricade sui record DNS A+AAAA e lo segnala.
Tre output:
conf.d autonomo (geo + map) più una
singola riga if ($ncsc_forbidden) { return 403; } nel vhost. Non è necessario
duplicare il blocco fastcgi, anche i file statici sono coperti (admin.html lo è),
e /.well-known/acme-challenge/ rimane aperto così il rinnovo del certificato
non può fallire silenziosamente.<LocationMatch> con un lookahead negativo più un <Location>
per l'endpoint push, così le due sezioni non possono sovrapporsi e nulla dipende
dall'ordine di merge di Apache. Tutti gli indirizzi di una regola vanno su una
riga Require ip: più righe dentro <RequireAll> vengono combinate con AND, che nessuno
può soddisfare.Il generatore rifiuta di emettere nulla quando nessun indirizzo di amministrazione è fornito,
avvisa quando l'indirizzo dell'operatore non è coperto, e avvisa quando la richiesta
è arrivata attraverso un proxy (sia geo che Require ip leggono il peer di trasporto, quindi
dietro un proxy ogni client appare uguale). Lo snippet ufw generato mette la regola SSH
per prima, mantiene aperta la porta 80 per la sfida HTTP-01, e illustra la
trappola IPv6: a differenza di nginx, che rifiuta un indirizzo v6 non elencato, ufw non
filtra affatto il v6 a meno che IPV6=yes non sia impostato — un host dual-stack sarebbe altrimenti
completamente aperto su IPv6.
Limite noto, segnalato nella pagina stessa: una volta applicate le regole, questa scheda
non scopre nulla di nuovo. Un push rifiutato viene respinto dal server web prima
che arrivi a PHP, quindi l'indirizzo registrato rimane l'ultimo che è passato —
e sembra ancora verificato. Due conseguenze: aggiungere un server Push significa
rigenerare e riapplicare le regole, o il suo primo push viene rifiutato; e se
l'indirizzo di un'istanza cambia, il nuovo è leggibile solo nel log di accesso del
server web (grep 'push-api.php' access.log | grep ' 403 '). La scheda mostra quindi
la data dell'ultima visione di ogni indirizzo osservato e lo segnala quando è più vecchio
di un intero ciclo di push mancato — la stessa soglia dell'avviso push_stale,
che copre la stessa zona d'ombra dall'altro lato.
Distinguere un problema di filtraggio da qualsiasi altro: un semplice GET sull'endpoint
push separa i livelli in modo pulito, senza effetti collaterali e senza bisogno del token —
eseguilo dalla macchina interessata, poiché ciò che viene giudicato è l'indirizzo in uscita di quella macchina:```bash
curl -sS -o /dev/null -w '%{http_code}\n' https://your-monitor/push-api.php
| Risposta | Significato |
|---|---|---|
| `403` | bloccato dal filtro IP |
| `401` | filtro superato, Basic auth risponde — il problema è altrove |
| `405` | la richiesta ha raggiunto l'applicazione (GET non è un metodo accettato lì) |
| nessuna risposta / timeout | non è il filtro: un filtro risponde, non rimane in silenzio |
Rieseguire con `-u user:password` per fugare un dubbio su un `403`: se il codice non
cambia, è davvero il filtro. Verificato sia su nginx che su Apache (incluso
con `Require valid-user` abilitato), il filtro risponde *prima* dell'autenticazione —
e un `403` proveniente dall'applicazione stessa contiene sempre JSON nel corpo.
La logica degli snippet risiede in `lib/hardening-rules.php`, che è puro e coperto
da `tests/run.php`: gli snippet sono il prodotto qui, e uno sbagliato o
blocca l'operatore o lascia un buco. Sia l'output nginx che quello Apache
sono stati verificati comportamentalmente (server reali, indirizzi sorgente reali, inclusi
tentativi di path-traversal della classe `push`).
### Analisi automatizzata (stage `security` della CI)
La scansione delle dipendenze (`npm/pnpm audit`, Snyk Open Source, Dependabot) è inutile
qui: non c'è `package.json` e non c'è `composer.json` — nulla da scansionare. Il
rischio risiede nel codice personalizzato (~15k righe di PHP, ~6k di JS) e negli script
shell che girano **come root** sulle istanze monitorate (`tools/*.sh`). La pipeline
è mirata lì:
| Job | Strumento | Bloccante | Ambito |
|---|---|---|---|
| `secrets_scan` | gitleaks | sì | segreti committati (working tree) |
| `sast_semgrep` | semgrep (`p/php`, `p/javascript`, `p/owasp-top-ten`) | sì | SSRF, authz/CSRF mancanti, XSS |
| `shellcheck` | shellcheck (`--severity=warning`) | sì | `tools/*.sh` — root sui client host |
| `dockerfile_misconfig` | trivy misconfig | sì | `deploy/docker/` |
| `container_cve` | trivy image | no (`allow_failure`) | l'immagine che `deploy/docker` costruisce, più `nginx:alpine` |
| `ui_tests` | node (nessuna dipendenza) | sì | invarianti di escape di `lib/ui-common.js` (entrambe le regressioni XSS passate) |
| `phpmailer_freshness` | GitHub API | no (`allow_failure`) | pin venduto vs rilascio upstream |
| `deploy_selfcheck` | nc-selfcheck.sh | sì | il ruleset nginx distribuito (regole di deny + header di sicurezza) attivato in un contenitore usa-e-getta |
Tutti i job bloccanti hanno una **baseline zero-finding**, quindi qualsiasi nuovo avviso è un vero
segnale. Due scelte deliberate, documentate inline in `.gitlab-ci.yml`:
- **`php.lang.security.injection.echoed-request` è esclusa** da semgrep: segnala
ogni `echo json_encode()` come XSS, che è ciò che ogni endpoint API qui
legittimamente fa (risposte JSON, non HTML). Ha rappresentato 10 su 10
risultati al primo avvio, tutti falsi positivi. Mantenerla abituerebbe tutti a
ignorare il job.
- **I due job `allow_failure` riportano fatti a monte** (una CVE in `nginx:alpine`
o una la cui correzione non è ancora arrivata al branch Alpine, una nuova release
di PHPMailer) che una merge request non può risolvere. Rosso ma tollerato è il
segnale accurato — "è ora di ricostruire/aggiornare il vendoring" — non un motivo
per bloccare lavoro non correlato. `phpmailer_freshness` riporta una GitHub API
irraggiungibile o rate-limited come *skip*, mai come "obsoleto".
- **`container_cve` scansiona l'immagine che costruisce, non il tag `FROM`.** Il
Dockerfile indurisce la base con `apk --no-cache upgrade` (l'immagine PHP ufficiale
è in ritardo rispetto ai repos Alpine — aveva c-ares 1.34.6-r0 mentre
1.34.8-r0, che corregge CVE-2026-33630, era già pubblicato). Scansionare il tag
base riporterebbe quindi CVE che l'immagine distribuita non ha più: un
lavoro permanentemente arancione che nessuno legge.
Le whitelist sono intenzionalmente ristrette: `.gitleaks.toml` esonera i **letterali**
placeholder string, mai interi file di documentazione (whitelistare `README.md`
accecherebbe la scansione il giorno in cui un vero segreto vi viene incollato) — quindi
un nuovo token di esempio nella documentazione deve essere aggiunto lì. `.trivyignore`
contiene una singola voce, `DS-0002`, argomentata nel file: il master php-fpm deve
avviarsi come root per abbassare i suoi worker a `www-data` (uid 82).
### Verifica post-deployment (`nc-selfcheck.sh`)
La CI può bloccare la configurazione *distribuita* (il job `deploy_selfcheck` sopra
attiva il ruleset nginx in un contenitore e lo sonda), ma non può verificare il server
su cui hai effettivamente distribuito — host diverso, credenziali Basic-auth, permessi
del filesystem. `tools/nc-selfcheck.sh` colma quel divario. È uno script bash
autonomo e di sola lettura (stesso modello di `nc-audit.sh`) che esegui dopo ogni deploy:```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
Esce con codice non-zero per qualsiasi scoperta critica (perdita di sorgente, file segreto non bloccato, archivio token leggibile a livello mondiale, mancanza di autenticazione Basic), quindi può bloccare un rollout — collegalo al tuo script di sincronizzazione/deploy come post-step. WARN/INFO non causano mai il fallimento dell'esecuzione.
// In config.php define('ENV', 'dev');
In modalità sviluppo, vengono visualizzate informazioni aggiuntive (versione PHP, server web).
### Test del server
Usa l'interfaccia di amministrazione per aggiungere un server tramite URL. Il server verrà interrogato al successivo aggiornamento dei dati.
### Log di debug
Controlla i file di log in `cache/`:
- `monitor.log` — Log generali dell'applicazione
- `cron.log` — Log completi dello script di raccolta (`cron-update.php`)
- `ping.log` — Log leggeri dei probe up/down (`cron-ping.php`)
- `alerts.log` — Invio proattivo di avvisi (webhook/email), non registra mai segreti webhook o credenziali SMTP
### Suite di test```bash
php tests/run.php # plain-PHP assertions, no framework — exit 0 = all green
Copre la pura logica di business (regole di versione/app, avvisi, macchina a stati di uptime e disponibilità, macchina a stati di deduplicazione/riarmo degli alert, costruttori di email).
Apri un nuovo ticket con:
Questo progetto è rilasciato sotto licenza GNU AGPL v3.
NcStatusCheck è sviluppato da ézéo, una cooperativa digitale specializzata in soluzioni open source.
Hai bisogno di aiuto? Consulta i ticket o contatta il team ézéo.
| Sezione | Campi |
|---|
| Sistema Nextcloud | Versione, modalità debug, memcache locale/distribuita, file locking, spazio su disco |
| PHP | Versione, memory_limit, upload_max_filesize, max_execution_time, FPM, OPcache |
| Web server | Nome + versione, protocollo HTTP |
| Database | Tipo, versione, dimensione |
| Cache | Redis, tasso di hit APCu |
| Utenti attivi | Ultimi 5 min, 1 h, 24 h, 7 giorni |
<slug>latest.ezeo.cooplatest_ezeo_coop<slug>.conf/etc/cron.d/ncstatuscheck-<slug>targets-<slug>.confncstatuscheck-push-<slug>.log…-<slug>.<md5>.lastNextcloud in esecuzione in Docker (immagine ufficiale, compose, AIO): completamente supportato — lo
script è installato sull'host (root cron + accesso al demone Docker), mai all'interno
del container, e occ passa attraverso docker exec:
OCC_CMD=docker exec -u www-data <container> php occ (container AIO: nextcloud-aio-nextcloud).
Il generatore di script dell'amministratore ha un preset tipo di installazione che precompila questo valore. Non aggiungere mai -t (nessun TTY sotto cron); mantieni -u www-data (l'immagine ufficiale rifiuta occ come root).
Distribuzione/aggiornamenti della flotta: poiché il core è un unico file identico,
aggiornare la logica su molti server = sostituire quel file (il marcatore ↑ segnala i server
che eseguono una versione precedente). Vedi deploy/ansible/ per un
playbook pronto all'uso (o un semplice ciclo scp). Il monitor rimane passivo — non invia mai codice
alla flotta; il perno di fiducia è il tuo accesso SSH, non il monitor.
Migrazione da un'installazione pre-v4 (script per istanza monolitico): rimuovi il vecchio
/usr/local/bin/ncstatuscheck-push-<slug>.sh e /etc/cron.d/ncstatuscheck-<slug>
prima di installare il core + configurazione (il file targets-<slug>.conf viene riutilizzato così com'è),
altrimenti fai double-push.
hash_equals()request_pushrequest_push_alltargets.conf copia silenziosamente ogni push a una terza parte altrimenti<>"'& all'ingestione, oltre all'escape al momento del renderingtry/catch può intercettare, che ucciderebbe l'esecuzione della raccolta a metà del ciclo e, con essa, ogni avviso per l'intera flotta