
Централизованный мониторинг для нескольких экземпляров Nextcloud
Централизованный мониторинг для нескольких экземпляров Nextcloud
NcStatusCheck — это инструмент мониторинга, который позволяет отслеживать работоспособность нескольких серверов Nextcloud из единого веб-интерфейса. Он анализирует версии Nextcloud и PHP и предоставляет рекомендации по обновлению.

< 15 дн. / < 7 дн.)occ app:list с магазином приложений Nextcloud для выявления приложений, требующих проверки — блокирующих (блокирующие обновление, несовместимые, тестовые приложения в продакшене) плюс информационные сигналы зрелости/турбулентности (недавно опубликованные, до версии 1.0, сборки alpha/beta/rc, всплеск релизов, свежевыпущенная версия)📦) и двойной блок «Контейнеры для наблюдения» (🐳) агрегируют каждое отмеченное приложение / образ Docker во всех экземплярах в одну запись, с фильтрами по типу для каждой группы и всплывающим списком затронутых экземпляров и их версийALERT_WEBHOOK_URL), и/или дайджест по электронной почте за пакет (ALERT_EMAIL_TO), отправленный через прямую SMTP-отправку на ваш почтовый сервер (встроенный PHPMailer; при отсутствии настроенного SMTP-ретранслятора использует локальный MTA). Также охватывает медленные сигналы (ALERT_CHECKS): истечение SSL-сертификата (уровневый), уязвимая/устаревшая версия Nextcloud, устаревший Push-зонд, критический отчет аудита, блокирующая находка приложения — одно оповещение на каждое новое условие, без спама напоминаниямиnc_update_grace_days), чтобы не гоняться за вышедшим в тот же день ошибочным релизом⚠️ предупреждения, 📦 приложения, 🔄 Docker, 🔒 SSL, 🔴 офлайн, 🚧 обслуживание) только при наличии чего-то для отчетаnc-audit.shlocalStoragechmod 700) для удаленного экземпляра Nextcloud, автоматически обновляется при изменении опцийncstatuscheck/ ├── 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
## 🔌 Режимы сбора
Режимы не являются взаимоисключающими — сервер может одновременно находиться в режимах Extended и Push.
| Режим | Значок | Источник | Собираемые данные |
|------|-------|--------|----------------|
| **Basic** | *(нет)* | `/status.php` + HTTP-заголовки | Версия Nextcloud (PHP/веб-сервер, если раскрыты) |
| **Extended** | `⚡ Extended` (фиолетовый → оранжевый при ошибке/устаревании) | `/ocs/v2.php/apps/serverinfo/api/v1/info` с `NC-Token` | Версия NC, PHP, веб-сервер, OPcache, Redis, БД, активные пользователи… |
| **Push** | `📡 Push` (синий → оранжевый при ошибке/устаревании) | POST на `push-api.php` | Данные, отправленные удаленным экземпляром NC через cron-скрипт |
**NC-Token** для serverinfo доступен в **Nextcloud Настройки → Администрирование → Система**.
**Push-токен** генерируется из интерфейса администрирования; администратор предоставляет готовый bash-скрипт cron (`chmod 700`) для развертывания на контролируемом экземпляре.
Данные режима Extended предоставляются приложением [nextcloud/serverinfo](https://github.com/nextcloud/serverinfo), которое должно быть установлено и включено на контролируемом экземпляре.
**Поведение при ошибке**: если Extended API недоступен (ошибка соединения, неверный токен, приложение не установлено), NcStatusCheck автоматически переключается на `/status.php`, чтобы получить хотя бы версию Nextcloud.
**Порог устаревания Push**: push-сервер считается устаревшим, если данные не поступали в течение `auto_push_interval + 30 минут`. Интервал push по умолчанию — 12 часов.
### Столбцы таблицы на главной панели
На главной панели отображаются 5 столбцов: **Сервер** | **Версия NC** | **PHP** | **Проверки** | **Состояние**
Столбец **Проверки** показывает активные режимы сбора для каждого сервера:
- Значок `⚡ Extended` (фиолетовый, становится оранжевым при ошибке соединения или устаревших данных)
- Значок `📡 Push` (синий, становится оранжевым, когда данные не получены в течение порога)
- Оба значка могут появляться одновременно, если активны оба режима
- Нет значка = только режим Basic
### Столбец состояния
Столбец состояния показывает информацию только когда есть что-то требующее внимания:
| Индикатор | Значок | Значение |
|-----------|-------|---------|
| Офлайн | `🔴 Offline` | Экземпляр недоступен (HTTP-проверка не удалась), с указанием "офлайн X" |
| Активные предупреждения | `⚠️ N` | N проблем с конфигурацией |
| Аудит приложений | `📦 N` | N установленных приложений для проверки (блокирующих обновление/несовместимых) |
| Истечение SSL | `🔒 N дн` | Сертификат скоро истекает — оранжевый `< 15 дн`, красный `< 7 дн` или истек |
| Обновления Docker | `🔄 M` | M доступных обновлений контейнеров |
| Всё в порядке | *(пусто)* | Нечего сообщать |
| Нет данных | `?` | Режим Basic без push-данных |
#### Up/down & срок действия SSL
NcStatusCheck хранит **минимальное** состояние up/down для каждого сервера (только текущее состояние + дата последнего изменения — нет временных рядов, нет страницы истории). "Up" означает, что исходящий HTTPS-запрос достиг экземпляра; красный значок **Offline** появляется только при недоступности. Во время того же HTTPS-запроса бесплатно считывается **срок действия SSL-сертификата** (`CURLOPT_CERTINFO`) и отображается, когда он приближается к истечению. Оба параметра полностью видны на странице детализации. *Примечание: эти исходящие проверки не применяются к экземплярам только с Push, с которыми монитор никогда не связывается.*
#### Аудит приложений (`📦`)
Когда Push-сервер сообщает свои установленные приложения (`occ app:list`, push-скрипт v3+), NcStatusCheck сверяет их с каталогом приложений Nextcloud и помечает приложения, заслуживающие проверки. **Только сигнализация** — инструмент никогда ничего не отключает; он выявляет кандидатов (не может знать, используется ли приложение на самом деле). Используются только **фактические, бинарные** сигналы. Значок `📦 N` подсчитывает блокирующие находки (нет совместимой версии для текущей версии NC, нет версии для NC N+1 → блокирует обновление, или тестовое/разработочное приложение оставлено включенным в production). Информационные находки (приложение устарело на экземпляре, заброшено upstream, несовместимо с PHP) отображаются только на странице детализации. Находки можно отключать через тот же механизм подтверждения, что и предупреждения.
### `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**: репозиторий поставляется с файлами `.htaccess`, отражающими правила `deny` выше (root: блокирует `*.log`/`*.json`/`*.txt` и `.git`; `cache/`, `tools/`, `deploy/`, `tests/`: `Require all denied`). Они работают только если vhost задаёт `AllowOverride FileInfo AuthConfig` (или `All`) — значение по умолчанию Debian для `/var/www` — `AllowOverride None`, в таком случае продублируйте правила непосредственно в vhost. HTTP Basic auth всё равно должна быть настроена в vhost в любом случае.
### Развёртывание
1. **Клонируйте репозиторий**```bash
git clone https://gitlab.com/jp.louvel/ncstatuscheck.git
cd ncstatuscheck
Отредактируйте `config.php` и настройте пути и URL для вашего окружения:```php
define('MONITOR_PATH', '/var/www/ncstatuscheck');
define('MONITOR_URL', 'https://monitoring.your-domain.com'); // your public URL
define('CACHE_DIR', MONITOR_PATH . '/cache');
Серверы управляются непосредственно через интерфейс администратора (кнопка ⚙️ Admin).
Вы также можете создать servers.json вручную:```json
[
{"url": "https://cloud.example.com"},
{"url": "https://nextcloud.mycompany.org", "serverinfo_token": "your_token_here"}
]
> **Миграция из `servers.txt`**: если файл `servers.txt` существует, он автоматически преобразуется в `servers.json` при первом обращении. Затем вы можете удалить `servers.txt`.
4. **Установка прав доступа**
nginx/PHP-FPM работают от имени своего собственного пользователя (`www-data` в Debian/Ubuntu, `nginx`/`apache` в семействе RHEL — настройте ниже) — если вы клонировали репозиторий от имени своего пользователя, этот пользователь почти наверняка не будет `www-data` или в его группе, поэтому один только `chmod` оставляет веб-сервер без **какого-либо доступа**, даже на чтение (каждый запрос возвращает 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
Если
servers.jsonеще не существует (вы позволяете интерфейсу администратора создать его вместо ручного шага выше), то указанный вышеchmod 600просто не на что воздействовать — это нормально:ServersStore::save()сам меняет права файла на0600при каждой записи, поэтомуservers.json, (пере)созданный через интерфейс администратора, никогда не остается доступным для чтения группой/всеми с информацией о сервере/токенами push внутри.
6. **Запланированные задачи (опционально)**
Два дополняющих друг друга cron’а — установите оба **в одном вызове `crontab -`**:
`crontab -` устанавливает полностью новый crontab из stdin, он не добавляет, поэтому
запуск его дважды (по одному разу на каждую строку) оставляет только *вторую* задачу — первая
бесшумно исчезает, без ошибок. Это также сохраняет всё, что уже есть в вашем
crontab (сначала передав `crontab -l` через pipe) вместо его очистки:```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 -
Повторный запуск добавляет дубликаты, если эти строки уже присутствуют — сначала проверьте с помощью crontab -l, если не уверены.
cron-ping.php намеренно минимален: он только проверяет status.php каждого экземпляра и обновляет состояние вверх/вниз (cache/uptime_state.json), поэтому может часто выполняться без нагрузки. Экземпляр помечается как down только после UPTIME_FAIL_THRESHOLD последовательных неудачных проверок (по умолчанию 2 → ~10 мин при 5-минутном интервале); восстановление до up происходит немедленно. Полный cron-update.php остаётся без изменений для всего остального.
Вместо шагов 1–6 выше, NcStatusCheck также может работать как небольшой стек docker compose (PHP-FPM + nginx + контейнер cron) — репозиторий монтируется как есть, без шага сборки или Composer, так что он точно повторяет bare-metal раскладку, только контейнеризован. Обслуживает только обычный HTTP (порт 8080 по умолчанию) — поставьте свой собственный обратный прокси с завершением TLS перед ним.
Полная настройка — конфигурация, подводные камни с правами (uid 82, предварительное создание servers.json), HTTP Basic Auth, cron, обновления и резервное копирование — полностью описана в deploy/docker/README.md. Начните оттуда; этот раздел намеренно является лишь указателем, чтобы не синхронизировать две копии одних и тех же шагов.
https://monitoring.your-domain.comДоступна по клику на любое имя сервера или его индикатор здоровья.
Для серверов Basic (без зондирования Extended или Push) упрощенная страница показывает доступные данные (версия NC, веб-сервер, HTTP протокол) с уведомлением и предложением включить зондирование.
Для серверов Extended / Push полная страница деталей отображает отдельные разделы:
NcStatusCheck предоставляет несколько REST-эндпоинтов:
Основной API (api.php)
GET ?action=get_data — Получить данные (кэш или обновление)POST ?action=refresh_data — Принудительное обновление всех серверовPush API (push-api.php)
POST с заголовком push_token — Получить push-данные от удаленного экземпляра NCPOST ?action=request_push_all — Запросить немедленный push от всех настроенных push-серверов (устанавливает флаг триггера, который обрабатывается удаленным cron-скриптом)Cron-скрипт, сгенерированный интерфейсом администратора, разделен на две части: общее ядро
/usr/local/bin/ncstatuscheck-push.sh— идентичное на каждом сервере (вся логика) — управляется небольшим конфигом для каждого экземпляра/etc/ncstatuscheck/<slug>.conf(SERVER_URL,SLUG,OCC_CMD,DOCKER_ENABLED,SKOPEO_ENABLED). Он вызывается какncstatuscheck-push.sh /etc/ncstatuscheck/<slug>.conf [--test]. Ядро отказывается использовать конфиг, доступный для записи группе или всем (защита от внедрения кода).Он многоцелевой (fan-out): данные собираются один раз и отправляются каждому монитору, перечисленному в
/etc/ncstatuscheck/targets-<slug>.conf(одна строкаurl|push_token[|http_user|http_pass]на монитор). Администратор каждого монитора выдает идемпотентную команду для его регистрации.Несколько экземпляров Nextcloud на одном хосте: пути для каждого экземпляра получают суффикс , полученный из отслеживаемого URL (например, → ): , , , , состояние . Только ядро является общим, поэтому расположенные на одном хосте экземпляры никогда не конфликтуют.
Detail API (detail-api.php)
GET ?server=<url> — Полные данные serverinfo + вычисленные предупреждения для сервера Extended/PushAPI администрирования
admin-api.php — Конфигурация версийservers-admin-api.php — Управление серверами (get_servers, add_server, remove_server, update_server_token, generate_push_token, remove_push_token)nextcloud-versions-api.php — Официальные версииnc-audit.sh)Отдельная подсистема от мониторинга: автономный, только для чтения bash-скрипт (tools/nc-audit.sh), запускаемый от root на сервере Nextcloud для разового / ежемесячного аудита настройки веб-сервера + PHP + базы данных, сверяясь с физическими ресурсами машины (RAM, CPU, тип диска). Ориентирован на предложение управляемого наблюдения: клиент устанавливает его, монитор только получает отчеты — не требуется доступ к машине/сети. Скрипт только читает конфигурацию (без изменений), выводит цветной отчет и сохраняет копию в /tmp.
Что проверяется: производительность сервера (RAM/CPU/SSD-HDD, swappiness, определение общего хостинга) · Nextcloud (версии, cron, кэш, работа Redis, тип БД, журналы) · PHP/PHP-FPM (реальный обслуживающий SAPI, работа OPcache, память нескольких пулов) · Apache (память рабочих процессов с учетом MPM) · Nginx · PostgreSQL · MariaDB · гигиена безопасности (fail2ban или CrowdSec + bouncer + список блокировки сообщества; ожидающие обновления / перезагрузка / службы на устаревших библиотеках) · согласование бюджета RAM (InnoDB + FPM + Apache vs реальная RAM) · глубокий анализ с дополнительными инструментами, если они уже присутствуют (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
**Мультиэкземплярные хосты** (несколько экземпляров Nextcloud + общая база данных). `NC_RAM_BUDGET_PCT`
тогда является **общим** бюджетом стека; `NC_PHP_SHARE_PCT`% из него (по умолчанию 60, остальное покрывает БД + веб + ОС — уменьшайте на серверах с тяжелой БД) является долей PHP, распределяется между пулами FPM по **весу** (относительная важность — не процент, не МБ), чтобы получить целевое значение `pm.max_children` для каждого пула:```
target = PHP_share × (weight / Σ weights) / ~50 MB per process
Цель — это потолок, который позволяет бюджет, а не значение, которое вы должны установить (только увеличивайте пул, который действительно насыщается). Веса — на ваше усмотрение — инструмент никогда их не угадывает.```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
**Report push-back** (опционально, использует инфраструктуру Push): `nc-audit.sh --push /etc/ncstatuscheck/<slug>.conf` запускает аудит и отправляет отчёт POST-запросом монитору(ам), который(ые) сохраняет(ют) его и показывает(ют) на странице сведений о сервере (раздел «🩺 Аудит сервера»). Обычно ежемесячный cron. Веб-страница (админ, бета) по адресу `audit.php` распространяет скрипт (загрузка + встроенный + GitLab однострочник) и показывает его версию.
> **Инструменты глубокого анализа никогда не устанавливаются** скриптом — они запускаются только если уже присутствуют (нет `curl | bash`, нет автоустановки), каждый ограничен `timeout`.
## 🔧 Расширенная конфигурация
### Настройка правил версий
Правила оценки настраиваются через интерфейс администрирования:
**Статусы Nextcloud:**
- `dev` — Версия для разработки
- `stable` — Текущая стабильная версия
- `oldstable` — Предыдущая поддерживаемая стабильная версия
- `deprecated` — Устаревшая версия
**Статусы PHP:**
- `recommended` — Рекомендуемая версия
- `supported` — Поддерживаемая версия
- `deprecated` — Устаревшая версия
### Переменные конфигурации
Отредактируйте `config.php` для адаптации конфигурации:```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', '');
Смотрите
config-example.phpдля полного, снабженного комментариями списка опций (включаяDEMO_MODEиPUSH_SCRIPT_VERSION).
lib/csrf-client.js + csrf_require()).htaccess для Apache; servers.json автоматически получает права 0600 (внутри токены)X-Frame-Options, nosniff, Referrer-Policy) на каждой странице, обслуживаемой PHPconfig.php находится в .gitignore и редактируется вручную на каждом сервере, поэтому он расходится — молча,
поскольку почти каждая константа имеет запасной вариант в коде. Баннер в верхней части
административной страницы сообщает, что именно не так, и только когда что-то не так:
PUSH_SCRIPT_VERSION, оставшаяся после обновления, отсутствие настроенного транспорта оповещений,
закрывающий тег в конце, который выдаёт байт до любого header(), недоступный для записи
каталог кэша, отсутствующие константы, молча использующие значения по умолчанию.
Только для чтения по замыслу и без действия сохранения, по той же причине, что и
вкладка «Уведомления»: config.php принадлежит root и содержит секреты. Содержимое файла
никогда не передаётся — только факты о нём — и ни один секрет не считывается.
Всё вышеперечисленное находится на уровне приложения: любой пользователь интернета всё ещё может достичь монитора и прощупать его, и только пароль останавливает их. Вкладка фильтрации по IP генерирует правила, которые помещают белый список перед приложением, так что неизвестные хосты вообще не могут с ним взаимодействовать. Это защита в глубину, а не замена базовой аутентификации или токенов push — и она всегда создаёт только текст для просмотра и вставки, она никогда не записывает конфигурацию веб-сервера или межсетевого экрана.
Два класса источников, намеренно неравных, чтобы скомпрометированный отслеживаемый сервер не мог получить доступ к админке:
| Класс | Кто | Имеет доступ к |
|---|---|---|
push | отслеживаемые экземпляры только в режиме Push | /push-api.php, ничего больше |
admin | бастион / VPN / фиксированный офисный IP | всё |
Экземпляры, опрашиваемые в режимах Basic/Extended, не открывают входящих соединений и не получают вообще никакой записи в белом списке.
Адреса берутся из двух источников, и разница важна: DNS-запись отслеживаемого домена — это его входной
адрес, в то время как его push отправляется с его выходного. Если они различаются,
работает только второй. Поэтому push-api.php записывает реальный исходный адрес каждого push
(source_ip в кэше push), и вкладка включает его в белый список, сообщая о несоответствии. Пока сервер
не отправил push хотя бы один раз, используется запасной вариант DNS A+AAAA с соответствующим уведомлением.
Три варианта вывода:
conf.d (geo + map) плюс одна
строка if ($ncsc_forbidden) { return 403; } в vhost. Нет необходимости дублировать
блок fastcgi, статические файлы также охвачены (admin.html — один из них),
а /.well-known/acme-challenge/ остаётся открытым, чтобы продление сертификата
не нарушило работу беззвучно.<LocationMatch> с отрицательным просмотром вперёд плюс <Location>
для конечной точки push, чтобы два раздела не пересекались, и ничто не зависело
от порядка слияния Apache. Все адреса одного правила помещаются на одну строку
Require ip: несколько строк внутри <RequireAll> работают как И, что никто не может выполнить.Генератор отказывается выдавать что-либо, когда не указан ни один административный адрес,
предупреждает, когда собственный адрес оператора не охвачен, и предупреждает, когда запрос
прошёл через прокси (как geo, так и Require ip читают транспортный одноранговый узел, поэтому
за прокси все клиенты выглядят одинаково). Сгенерированный фрагмент ufw помещает правило SSH
первым, оставляет порт 80 открытым для проверки HTTP-01 и разъясняет
ловушку IPv6: в отличие от nginx, который отклоняет незарегистрированный адрес IPv6, ufw не
фильтрует IPv6 вообще, если не установлено IPV6=yes — хост с двойным стеклом в противном случае
был бы полностью открыт через IPv6.
Известное ограничение, отображаемое на самой странице: после применения правил эта вкладка
не обнаруживает ничего нового. Отклонённый push отвергается веб-сервером до того, как
достигнет PHP, поэтому записанный адрес остаётся последним, который прошёл — и всё ещё выглядит
проверенным. Два последствия: добавление Push-сервера означает
повторную генерацию и повторное применение правил, иначе его первый push будет отклонён;
а если адрес экземпляра меняется, новый можно прочитать только в журнале доступа
веб-сервера (grep 'push-api.php' access.log | grep ' 403 '). Поэтому вкладка
показывает дату последнего наблюдения каждого адреса и помечает его, если оно старше
полного цикла пропущенных push — тот же порог, что и для предупреждения push_stale,
которое покрывает ту же слепую зону с другой стороны.
Отличие проблемы фильтрации от любой другой: простой GET на конечной точке push
чисто разделяет слои, без побочных эффектов и без необходимости токена —
запускайте его с самой заинтересованной машины, так как оценивается именно её
исходящий адрес:```bash
curl -sS -o /dev/null -w '%{http_code}\n' https://your-monitor/push-api.php
| Answer | Meaning |
|---|---|
| `403` | заблокировано IP-фильтрацией |
| `401` | фильтрация пройдена, Basic auth отвечает — проблема в другом |
| `405` | запрос достиг приложения (GET там не является разрешённым методом) |
| nothing / timeout | не фильтрация: фильтр отвечает, он не молчит |
Повторный запуск с `-u user:password` для разрешения сомнений по поводу `403`: если код не меняется, это действительно фильтрация. Проверено на nginx и Apache (включая включение `Require valid-user`), фильтр отвечает *до* аутентификации — а `403`, приходящий от самого приложения, всегда содержит JSON в теле.
Логика сниппетов находится в `lib/hardening-rules.php`, который является чистым и покрыт `tests/run.php`: сниппеты здесь являются продуктом, и неправильный либо блокирует оператора, либо оставляет дыру. И выходные данные nginx, и Apache были проверены поведенчески (реальные серверы, реальные исходные адреса, включая попытки обхода пути из класса `push`).
### Автоматизированный анализ (этап CI `security`)
Сканирование зависимостей (`npm/pnpm audit`, Snyk Open Source, Dependabot) здесь не имеет смысла: нет `package.json` и `composer.json` — нечего сканировать. Риск находится в пользовательском коде (~15k строк PHP, ~6k JS) и в shell-скриптах, которые выполняются **от root** на отслеживаемых экземплярах (`tools/*.sh`). Конвейер направлен туда:
| Job | Tool | Blocking | Scope |
|---|---|---|---|
| `secrets_scan` | gitleaks | да | зафиксированные секреты (рабочее дерево) |
| `sast_semgrep` | semgrep (`p/php`, `p/javascript`, `p/owasp-top-ten`) | да | SSRF, отсутствие авторизации/CSRF, XSS |
| `shellcheck` | shellcheck (`--severity=warning`) | да | `tools/*.sh` — root на клиентских хостах |
| `dockerfile_misconfig` | trivy misconfig | да | `deploy/docker/` |
| `container_cve` | trivy image | нет (`allow_failure`) | образ, который собирает `deploy/docker`, плюс `nginx:alpine` |
| `ui_tests` | node (без зависимостей) | да | инварианты экранирования `lib/ui-common.js` (обе прошлые регрессии XSS) |
| `phpmailer_freshness` | GitHub API | нет (`allow_failure`) | зафиксированная версия в поставке по сравнению с upstream-релизом |
| `deploy_selfcheck` | nc-selfcheck.sh | да | поставляемый набор правил nginx (правила запрета + заголовки безопасности), развёрнутый в одноразовом контейнере |
Все блокирующие задания имеют **нулевой базовый уровень совпадений**, поэтому любое новое предупреждение является реальным сигналом. Два намеренных решения, задокументированные в `.gitlab-ci.yml`:
- **`php.lang.security.injection.echoed-request` исключён** из semgrep: он помечает каждый `echo json_encode()` как XSS, хотя именно это здесь делает каждая точка API (JSON-ответы, а не HTML). На первом прогоне он дал 10 из 10 совпадений, все ложные. Если бы он остался, это приучило бы всех игнорировать задание.
- **Два задания с `allow_failure` сообщают факты из upstream** (CVE в `nginx:alpine` или та, исправление которой ещё не дошло до ветки Alpine, новый релиз PHPMailer), которые не может исправить merge request. Красный, но терпимый — это точный сигнал — «пора пересобрать/обновить поставку» — а не причина блокировать независящую работу. `phpmailer_freshness` сообщает о недоступном или ограниченном по частоте GitHub API как *пропуск* (skip), а не как «устарел».
- **`container_cve` сканирует образ, который он собирает, а не тег `FROM`.** Dockerfile укрепляет базовый образ с помощью `apk --no-cache upgrade` (официальный PHP-образ отстаёт от репозиториев Alpine — он поставлял c-ares 1.34.6-r0, в то время как 1.34.8-r0, исправляющий CVE-2026-33630, уже был опубликован). Сканирование тега base приводило бы к сообщению об уязвимостях, которых у поставляемого образа уже нет: вечно оранжевое задание, которое никто не читает.
Белые списки намеренно узкие: `.gitleaks.toml` пропускает **буквальные** строки-заполнители, но никогда целые файлы документации (включение `README.md` в белый список ослепило бы сканер в день, когда туда вставят настоящий секрет) — поэтому новый пример токена в документации должен быть добавлен туда. `.trivyignore` содержит одну запись, `DS-0002`, аргументированную в файле: мастер php-fpm должен запускаться от root, чтобы опустить свои рабочие процессы до `www-data` (uid 82).
### Проверка после развёртывания (`nc-selfcheck.sh`)
CI может проверить *поставляемую* конфигурацию (задание `deploy_selfcheck` выше поднимает набор правил nginx в контейнере и проверяет его), но он не может проверить сервер, на который вы фактически развернули — другой хост, учётные данные Basic-auth, права доступа к файловой системе. `tools/nc-selfcheck.sh` закрывает этот пробел. Это автономный, read-only bash-скрипт (той же модели, что и `nc-audit.sh`), который вы запускаете после каждого развёртывания:```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
Он завершается с ненулевым кодом при любом критическом обнаружении (утечка исходного кода, незаблокированный секретный файл, общедоступное хранилище токенов, отсутствие Basic auth), поэтому может служить шлюзом для развёртывания — встройте его в свой скрипт синхронизации/развёртывания как пост-шаг. WARN/INFO никогда не приводят к сбою.
// In config.php define('ENV', 'dev');
В режиме разработки отображается дополнительная информация (версия PHP, веб-сервер).
### Тестирование сервера
Используйте интерфейс администратора, чтобы добавить сервер по URL. Сервер будет опрашиваться при следующем обновлении данных.
### Журналы отладки
Проверьте файлы журналов в `cache/`:
- `monitor.log` — Общие журналы приложения
- `cron.log` — Полные журналы скрипта сбора (`cron-update.php`)
- `ping.log` — Легковесные журналы зондирования состояния (вверх/вниз) (`cron-ping.php`)
- `alerts.log` — Упреждающая отправка оповещений (вебхук/email), никогда не регистрирует секреты вебхуков или учетные данные SMTP
### Набор тестов```bash
php tests/run.php # plain-PHP assertions, no framework — exit 0 = all green
Охватывает чистую бизнес-логику (правила версий/приложений, предупреждения, автомат состояния времени работы и доступности, автомат состояния дедупликации/перевооружения оповещений, построители писем).
Откройте новый issue с:
Этот проект распространяется под лицензией GNU AGPL v3.
NcStatusCheck разработан ézéo, цифровым кооперативом, специализирующимся на решениях с открытым исходным кодом.
Нужна помощь? Посмотрите issues или свяжитесь с командой ézéo.
| Раздел | Поля |
|---|
| Система Nextcloud | Версия, режим отладки, локальный/распределенный memcache, блокировка файлов, дисковое пространство |
| PHP | Версия, memory_limit, upload_max_filesize, max_execution_time, FPM, OPcache |
| Веб-сервер | Имя + версия, HTTP протокол |
| База данных | Тип, версия, размер |
| Кэш | Redis, частота попаданий APCu |
| Активные пользователи | Последние 5 мин, 1 час, 24 часа, 7 дней |
<slug>latest.ezeo.cooplatest_ezeo_coop<slug>.conf/etc/cron.d/ncstatuscheck-<slug>targets-<slug>.confncstatuscheck-push-<slug>.log…-<slug>.<md5>.lastNextcloud, работающий в Docker (официальный образ, compose, AIO): полностью поддерживается — скрипт устанавливается на хост (root cron + доступ к демону Docker), никогда внутрь контейнера, и occ выполняется через docker exec: OCC_CMD=docker exec -u www-data <container> php occ (контейнер AIO: nextcloud-aio-nextcloud). Генератор скрипта администратора имеет предустановку типа установки, которая автоматически заполняет это. Никогда не добавляйте -t (нет TTY под cron); сохраняйте -u www-data (официальный образ отказывается выполнять occ от root).
Развёртывание/обновление на парке серверов: поскольку ядро представляет собой один идентичный файл, обновление логики на многих серверах = замена этого одного файла (маркер ↑ помечает серверы, работающие на старой версии). Смотрите deploy/ansible/ для готового плейбука (или простой цикл scp). Монитор остаётся пассивным — он никогда не отправляет код на серверы; точкой доверия является ваш собственный SSH-доступ, а не монитор.
Миграция с установки до v4 (монолитный скрипт для каждого экземпляра): удалите старые /usr/local/bin/ncstatuscheck-push-<slug>.sh и /etc/cron.d/ncstatuscheck-<slug> перед установкой ядра + конфига (targets-<slug>.conf используется как есть), иначе будет двойная отправка.
hash_equals()request_pushrequest_push_alltargets.conf беззвучно копирует каждый push третьей стороне<>"'& при приёме, в дополнение к экранированию при рендерингеtry/catch, и которая остановит сбор данных в середине цикла, а с ним и все оповещения для всего парка