集中监控多个 Nextcloud 实例
NcStatusCheck 是一个监控工具,可让您通过单一 Web 界面跟踪多个 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 版本、陈旧推送探测、关键审计报告、阻塞性应用发现——每个新条件触发一次告警,无提醒骚扰nc_update_grace_days),以避免追逐当天有缺陷的版本⚠️ 警告、📦 应用、🔄 Docker、🔒 SSL、🔴 离线、🚧 维护)nc-audit.sh 报告时显示localStorage 中chmod 700),选项更改时自动更新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
## 🔌 收集模式
模式并非互斥——一台服务器可以同时启用扩展模式和推送模式。
| 模式 | 徽章 | 源 | 收集数据 |
|------|-------|--------|----------------|
| **基本** | *(无)* | `/status.php` + HTTP 头 | Nextcloud 版本(若暴露则包含 PHP/Web 服务器) |
| **扩展** | `⚡ Extended`(错误/过期时紫色变橙色) | `/ocs/v2.php/apps/serverinfo/api/v1/info` 使用 `NC-Token` | NC 版本、PHP、Web 服务器、OPcache、Redis、数据库、活跃用户…… |
| **推送** | `📡 Push`(错误/过期时蓝色变橙色) | POST 到 `push-api.php` | 远程 NC 实例通过 cron 脚本推送的数据 |
**serverinfo NC-Token** 可在 **Nextcloud 设置 → 管理 → 系统** 中找到。
**推送令牌** 通过管理界面生成;管理员提供一个可直接使用的 bash cron 脚本(`chmod 700`)以部署在被监控的实例上。
扩展模式数据由 [nextcloud/serverinfo](https://github.com/nextcloud/serverinfo) 应用提供,该应用必须在被监控的实例上安装并启用。
**回退行为**:如果扩展 API 不可达(连接错误、无效令牌、应用未安装),NcStatusCheck 会自动回退到 `/status.php` 以至少获取 Nextcloud 版本。
**推送过期阈值**:如果在 `auto_push_interval + 30 分钟` 内未收到任何数据,则视推送服务器为过期。默认推送间隔为 12 小时。
### 仪表盘表格列
主仪表盘显示 5 列:**服务器** | **NC 版本** | **PHP** | **探测** | **健康**
**探测** 列显示每台服务器的活跃收集模式:
- `⚡ Extended` 徽章(紫色,连接错误或数据过期时变橙色)
- `📡 Push` 徽章(蓝色,在阈值内未收到数据时变橙色)
- 如果两种模式均活跃,两个徽章可同时出现
- 无徽章 = 仅基本模式
### 健康列
健康列仅在需要处理时显示内容:
| 指示器 | 徽章 | 含义 |
|-----------|-------|---------|
| 离线 | `🔴 Offline` | 实例不可达(HTTP 探测失败),显示“离线 X 时间” |
| 活跃警告 | `⚠️ N` | N 个配置问题 |
| 应用审计 | `📦 N` | N 个已安装应用需要审查(阻止升级/不兼容) |
| SSL 过期 | `🔒 N d` | 证书即将过期——橙色 `< 15 天`,红色 `< 7 天` 或已过期 |
| Docker 更新 | `🔄 M` | M 个容器更新可用 |
| 一切正常 | *(空)* | 无报告内容 |
| 无数据 | `?` | 基本模式且无推送数据 |
#### 上下线状态与 SSL 过期
NcStatusCheck 为每台服务器维护一个**最小化**的上下线状态(仅当前状态和最后变更日期——无时间序列,无历史页面)。“上线”表示出站 HTTPS 探测成功到达实例;红色 **Offline** 徽章仅在下线时出现。在同一个 HTTPS 探测中,**SSL 证书过期时间** 会被免费读取(`CURLOPT_CERTINFO`),并在即将过期时显示。详细信息在详情页完整展示。*注:这些出站检查不适用于监控器从不联系的纯推送实例。*
#### 应用审计(`📦`)
当推送服务器报告其已安装的应用时(`occ app:list`,推送脚本 v3+),NcStatusCheck 会将其与 Nextcloud 应用商店目录交叉检查,并标记值得审查的应用。**仅发出信号**——该工具从不禁用任何东西;它只是列出候选(无法知道应用是否实际使用)。只使用**事实性的、二元的**信号。`📦 N` 徽章计数阻止性发现(当前 NC 版本无兼容版本,NC N+1 无版本 → 阻止升级,或测试/开发应用在生产环境中保持启用)。信息性发现(实例上应用过时、上游弃用、PHP 不兼容)仅在详情页显示。发现可以通过与警告相同的确认机制静音。
### `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**:仓库附带了 `.htaccess` 文件,与上述 `deny` 规则一致(根目录:阻止 `*.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');
服务器直接通过管理界面(⚙️ 管理按钮)进行管理。
您也可以手动创建 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 以它们自己的用户身份运行(在 Debian/Ubuntu 上是 `www-data`,在 RHEL 系列上是 `nginx`/`apache` — 请根据实际情况调整)——如果你以你自己的登录用户克隆,那么该用户几乎肯定不是 `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尚不存在(您让管理 UI 创建它,而不是执行上面的手动步骤),那么上面的chmod 600就没有任何作用对象——这没问题:ServersStore::save()在每次写入时都会自动将文件权限设置为0600,因此通过管理 UI(重新)创建的servers.json永远不会让其中包含的服务器信息和推送令牌保持组/全局可读。
6. **定时任务(可选)**
两个互补的定时任务——使用**同一个 `crontab -` 调用**安装:
`crontab -` 从标准输入安装一个全新的 crontab,它不会追加,因此运行两次(每次一行)只会留下*第二个*任务——第一个会静默消失,没有错误。这也能保留你的 crontab 中已有的内容(首先通过管道输入 `crontab -l`),而不是覆盖它:```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),因此可以频繁运行而不造成负载。一个实例只有在连续 UPTIME_FAIL_THRESHOLD 次探测失败(默认 2 → 若以5分钟为间隔,约10分钟)后才会被标记为宕机;恢复为上线则是即时的。完整的 cron-update.php 保持不变用于其他一切。
除了上述步骤 1–6 之外,NcStatusCheck 也可以作为一个小的 docker compose 堆栈运行(PHP-FPM + nginx + 一个 cron 容器)——仓库被原样绑定挂载,无需构建步骤或 Composer,因此它完全镜像了裸机布局,只是容器化了。仅提供纯 HTTP(默认端口 8080)——在你的前端放置自己的 TLS 终止反向代理。
完整的设置——配置、权限陷阱(uid 82、servers.json 预先创建)、HTTP 基本认证、cron、更新和备份——全部位于 deploy/docker/README.md 中。从那里开始;本节刻意仅作指引,以避免维护同一步骤的两个副本保持同步。
https://monitoring.your-domain.com可通过点击任意服务器名称或其健康指示器访问。
对于基本服务器(无扩展或推送探测),一个简化的页面显示可用数据(NC 版本、Web 服务器、HTTP 协议),并附带通知和建议启用探测。
对于扩展/推送服务器,完整详情页面显示不同的部分:
NcStatusCheck 暴露了几个 REST 端点:
主要 API (api.php)
GET ?action=get_data — 获取数据(缓存或刷新)POST ?action=refresh_data — 强制更新所有服务器推送 API (push-api.php)
POST 带有 push_token 头部 — 从远程 NC 实例接收推送数据POST ?action=request_push_all — 请求所有已配置的推送服务器立即推送(设置一个由远程 cron 脚本使用的触发标志)管理 UI 生成的 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]。核心拒绝加载组/全局可写的配置文件(防止代码注入)。它是多目标(扇出):数据收集一次并推送到
/etc/ncstatuscheck/targets-<slug>.conf中列出的每个监控器(每行一个url|push_token[|http_user|http_pass]行)。每个监控器的管理员会发出一个幂等命令来注册自身。一台主机上多个 Nextcloud 实例:每个实例的路径通过从受监控 URL 派生的
<slug>后缀(例如latest.ezeo.coop→ ): 、、、 、状态 。只有核心是共享的, 因此同一位置上的实例永远不会冲突。
详情 API (detail-api.php)
GET ?server=<url> — 完整的服务器信息数据 + 针对扩展/推送服务器的计算警告管理 API
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 服务器上运行,用于一次性/每月的审计,检查 Web + PHP + 数据库调优,并与机器的物理容量(RAM、CPU、磁盘类型)交叉核对。面向托管监控服务:客户端安装它,监控器仅接收报告——无需机器/网络访问。脚本仅读取配置(不做更改),打印彩色报告并将副本写入 /tmp。
它检查的内容:服务器容量(RAM/CPU/SSD-HDD、swappiness、共享服务器检测)· Nextcloud(版本、cron、缓存、Redis 运行时、数据库类型、日志)· PHP/PHP-FPM(实际服务 SAPI、OPcache 运行时、多池内存)· Apache(MPM 感知的工作内存)· Nginx · PostgreSQL · MariaDB · 安全卫生(fail2ban 或 CrowdSec + 拦截器 + 社区黑名单;待处理的更新/重启/基于陈旧库的服务)· 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,其余部分覆盖数据库 + Web + 操作系统——在数据库密集型服务器上应降低此值)是 PHP 份额,按**权重**(相对重要性——而非百分比或 MB)分配到各个 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
**报告推送(可选,复用推送基础设施):** `nc-audit.sh --push /etc/ncstatuscheck/<slug>.conf` 运行审计并将报告以 POST 方式发送至监控器,监控器存储报告并在服务器详情页("🩺 服务器审计"部分)显示。通常作为月度定时任务运行。管理后台(beta 版)中的 `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 文件实现;servers.json 自动设为 0600 权限(包含令牌)X-Frame-Options、nosniff、Referrer-Policy)应用于每个 PHP 提供的页面hash_equals() 通过该条目的令牌时,才接受推送,因此受感染的受监控服务器既不能读取也不能覆盖另一台服务器的数据。request_push / request_push_all 位于管理认证 + CSRF 之后。已针对受感染客户端场景进行了端到端验证config.php 是 Git 忽略的,并需按服务器手动编辑,因此会逐渐偏离——而由于几乎每个常量在代码中都有回退值,这种偏离是静默的。管理页面顶部的提示横幅会报告实际错误,并且仅在存在错误时显示:版本升级后遗留的 PUSH_SCRIPT_VERSION、未配置任何警报传输、尾随结束标签在任何 header() 之前输出了一个字节、缓存目录不可写、常量缺失并静默回退到默认值。
设计为只读且无保存操作,原因与“通知”标签页相同:config.php 由 root 拥有且包含机密信息。文件内容从不传输——仅传输关于它的事实——并且不读取任何机密。
以上所有内容都是应用层面的:互联网上的任何人都仍然可以访问监控器并探测它,只有密码能够阻止他们。IP 过滤标签页生成的规则会在应用之前放置一个白名单,因此未知主机根本无法与其通信。这是纵深防御,而非基本认证或推送令牌的替代品——并且它仅生成供审查和粘贴的文本,从不写入 Web 服务器或防火墙配置。
两类来源,故意设计为不平等,因此受感染的受监控服务器无法访问管理页面:
| 类别 | 谁 | 可访问范围 |
|---|---|---|
push | 仅限推送模式的受监控实例 | /push-api.php,其他均不可 |
admin | 堡垒机 / VPN / 固定办公 IP | 所有 |
以基本/扩展模式轮询的实例不打开入站连接,也根本不会获得白名单条目。
地址来自两个来源,其差异很重要:受监控域的 DNS 记录是其入站地址,而其推送则从其出站地址发出。当两者不同时,只有出站地址有效。因此 push-api.php 记录每次推送的真实源地址(推送缓存中的 source_ip),并且该标签页将其加入白名单,同时报告不匹配。在服务器完成一次推送之前,它会回退到 DNS A+AAAA 记录并说明这一点。
三种输出:
conf.d 文件(geo + map)加上虚拟主机中的单行 if ($ncsc_forbidden) { return 403; }。无需复制 fastcgi 块,静态文件也同时被覆盖(admin.html 即为一例),且 /.well-known/acme-challenge/ 保持开放,以免证书续期无声中断。<LocationMatch> 配合负向前瞻,加上用于推送端点的 <Location>,使两个部分不会重叠,且不依赖于 Apache 的合并顺序。一条规则的所有地址放在一行 Require ip 上:<RequireAll> 内的多行会被 AND 运算,无人能同时满足。当未提供任何管理地址时,生成器拒绝输出任何内容;当操作员自身的地址未被覆盖时发出警告;当请求通过代理到达时也发出警告(geo 和 Require ip 都读取传输对端,因此在代理后面所有客户端看起来都一样)。生成的 ufw 片段将 SSH 规则放在首位,保持 80 端口开放用于 HTTP-01 验证,并明确指出 IPv6 陷阱:与 nginx(拒绝未列出的 v6 地址)不同,ufw 除非设置了 IPV6=yes,否则根本不会过滤 v6——否则双栈主机将通过 IPv6 完全开放。
已知限制,已在页面本身标明:一旦规则应用,此标签页将无法发现新内容。被拒绝的推送在到达 PHP 之前就被 Web 服务器拒绝,因此记录的地址仍然是最后一个通过的地址——并且看起来仍然是已验证的。这导致两个后果:添加推送服务器意味着重新生成并重新应用规则,否则其首次推送将被拒绝;如果实例的地址发生变化,新地址只能在 Web 服务器访问日志中读取(grep 'push-api.php' access.log | grep ' 403 ')。因此,该标签页显示每个观察到的地址的最后发现日期,并在其超过一个完整的错过推送周期时标记——与 push_stale 警报使用相同的阈值,该警报从另一端覆盖了相同的盲区。
区分过滤问题与其他问题:对推送端点执行一次裸 GET 即可清晰地区分各层,无副作用且无需令牌——从相关机器上运行此命令,因为被判断的是该机器的出站地址:```bash
curl -sS -o /dev/null -w '%{http_code}\n' https://your-monitor/push-api.php
| Answer | 含义 |
|---|---|
| `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)以及在受监控实例上**以 root 身份**运行的 shell 脚本(`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 (no deps) | 是 | 转义 `lib/ui-common.js` 的不变条件(过去的两次 XSS 回归) |
| `phpmailer_freshness` | GitHub API | 否(`allow_failure`) | 供应商固定版本与上游发布版本 |
| `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` 作业报告上游事实**(`nginx:alpine` 中的 CVE,或修复尚未到达 Alpine 分支的 CVE,新的 PHPMailer 发布),合并请求无法修复这些。红色但可容忍是准确的信号——"是时候重建/刷新供应商依赖了"——而不是阻止无关工作的理由。`phpmailer_freshness` 将不可达或受限的 GitHub API 报告为*跳过*,绝不报告为"过时"。
- **`container_cve` 扫描它构建的镜像,而非 `FROM` 标签。** Dockerfile 使用 `apk --no-cache upgrade` 加固基础镜像(官方 PHP 镜像落后于 Alpine 仓库——它随附 c-ares 1.34.6-r0,而 1.34.8-r0(修复 CVE-2026-33630)已经发布)。因此,扫描基础标签会报告已发布镜像不再具有的 CVE:一个永久橙色的、无人阅读的作业。
白名单故意狭窄:`.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` 弥补了这一差距。它是一个独立的、只读的 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
它在发现任何严重问题时(源代码泄露、未阻止的秘密文件、全局可读的令牌存储、缺少基本认证)会返回非零退出码,因此可以用来控制发布流程——将其作为后置步骤接入你的同步/部署脚本中。WARN/INFO 级别永远不会导致运行失败。
// In config.php define('ENV', 'dev');
在开发模式下,会显示额外信息(PHP 版本、Web 服务器)。
### 服务器测试
使用管理界面通过 URL 添加服务器。该服务器将在下次数据刷新时被轮询。
### 调试日志
检查 `cache/` 中的日志文件:
- `monitor.log` — 通用应用程序日志
- `cron.log` — 完整收集脚本日志(`cron-update.php`)
- `ping.log` — 轻量级在线/离线探测日志(`cron-ping.php`)
- `alerts.log` — 主动告警分发(Webhook/邮件),从不记录 Webhook 密钥或 SMTP 凭据
### 测试套件```bash
php tests/run.php # plain-PHP assertions, no framework — exit 0 = all green
覆盖纯业务逻辑(版本/应用规则、警告、正常运行状态机与可用性、警报去重/重新武装状态机、邮件构建器)。
新建一个 issue,包含:
本项目采用 GNU AGPL v3 许可证。
NcStatusCheck 由 ézéo 开发,这是一家专注于开源解决方案的数字合作社。
需要帮助? 查看问题页面或联系 ézéo 团队。
| 部分 | 字段 |
|---|
| Nextcloud 系统 | 版本、调试模式、本地/分布式内存缓存、文件锁定、磁盘空间 |
| PHP | 版本、memory_limit、upload_max_filesize、max_execution_time、FPM、OPcache |
| Web 服务器 | 名称 + 版本、HTTP 协议 |
| 数据库 | 类型、版本、大小 |
| 缓存 | Redis、APCu 命中率 |
| 活跃用户 | 最近5分钟、1小时、24小时、7天 |
latest_ezeo_coop<slug>.conf/etc/cron.d/ncstatuscheck-<slug>targets-<slug>.confncstatuscheck-push-<slug>.log…-<slug>.<md5>.lastNextcloud 在 Docker 中运行(官方镜像、compose、AIO):完全支持——脚本安装在主机上(root cron + Docker daemon 权限),
绝不安装在容器内部,occ 通过 docker exec 执行:
OCC_CMD=docker exec -u www-data <container> php occ(AIO 容器:nextcloud-aio-nextcloud)。
管理员脚本生成器有一个安装类型预设,可以预填充此项。绝不要添加 -t(在 cron 下没有 TTY);保留 -u www-data(官方镜像拒绝以 root 身份运行 occ)。
批量部署/更新:由于核心是一个单一相同的文件,跨多台服务器更新逻辑 = 替换那个文件(↑ 标记表示服务器运行旧版本)。参见 deploy/ansible/ 获取可直接使用的 playbook(或简单的 scp 循环)。监控器保持被动——它永远不会向集群发送代码;信任锚是你自己的 SSH 访问,而不是监控器。
从 v4 之前的版本迁移(单体每个实例脚本):在安装核心+配置之前删除旧的 /usr/local/bin/ncstatuscheck-push-<slug>.sh 和 /etc/cron.d/ncstatuscheck-<slug> (targets-<slug>.conf 原样重用),
否则会重复推送。
targets.conf 中多出一行会悄悄将每次推送复制给第三方<>"'& 字符,并在渲染时进行转义try/catch 无法捕获的致命错误,将导致采集循环中途终止,进而丢失整个集群的所有警报