
여러 Nextcloud 인스턴스를 위한 중앙 집중식 모니터링
여러 Nextcloud 인스턴스를 위한 중앙 집중식 모니터링
NcStatusCheck는 단일 웹 인터페이스에서 여러 Nextcloud 서버의 상태를 추적할 수 있는 모니터링 도구입니다. Nextcloud 및 PHP 버전을 분석하고 업데이트 권장 사항을 제공합니다.

< 15일 / < 7일)occ app:list를 Nextcloud 앱 스토어와 교차 확인하여 검토가 필요한 앱 표시 — 차단 항목 (업그레이드 차단, 호환되지 않음, 프로덕션 환경의 테스트 앱) 및 정보 제공용 성숙도/변동 신호 (최근 게시됨, 1.0 미만, 알파/베타/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.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
## 🔌 수집 모드
모드는 상호 배타적이지 않습니다. 하나의 서버가 Extended와 Push를 동시에 가질 수 있습니다.
| 모드 | 배지 | 소스 | 수집 데이터 |
|------|-------|--------|----------------|
| **Basic** | *(없음)* | `/status.php` + HTTP 헤더 | Nextcloud 버전 (노출된 경우 PHP/웹서버) |
| **Extended** | `⚡ Extended` (보라색 → 오류/오래된 경우 주황색) | `/ocs/v2.php/apps/serverinfo/api/v1/info` 및 `NC-Token` | NC 버전, PHP, 웹 서버, OPcache, Redis, DB, 활성 사용자… |
| **Push** | `📡 Push` (파란색 → 오류/오래된 경우 주황색) | `push-api.php`로 POST | 원격 NC 인스턴스가 cron 스크립트를 통해 푸시한 데이터 |
**serverinfo NC-Token**은 **Nextcloud 설정 → 관리 → 시스템**에서 확인할 수 있습니다.
**푸시 토큰**은 관리 인터페이스에서 생성됩니다. 관리자는 모니터링 대상 인스턴스에 배포할 수 있는 바로 사용 가능한 bash cron 스크립트(`chmod 700`)를 제공합니다.
Extended 모드 데이터는 [nextcloud/serverinfo](https://github.com/nextcloud/serverinfo) 앱에서 제공하며, 모니터링 대상 인스턴스에 설치 및 활성화되어 있어야 합니다.
**대체 동작**: Extended API에 접근할 수 없는 경우(연결 오류, 잘못된 토큰, 앱 미설치), NcStatusCheck는 자동으로 `/status.php`로 대체하여 최소한 Nextcloud 버전을 검색합니다.
**푸시 오래됨 임계값**: `auto_push_interval + 30분` 동안 데이터를 수신하지 못하면 푸시 서버가 오래된 것으로 간주됩니다. 기본 푸시 간격은 12시간입니다.
### 대시보드 테이블 열
기본 대시보드에는 **서버** | **NC 버전** | **PHP** | **프로브** | **상태**의 5개 열이 표시됩니다.
**프로브** 열에는 각 서버의 활성 수집 모드가 표시됩니다:
- `⚡ Extended` 배지 (보라색, 연결 오류 또는 오래된 데이터 시 주황색으로 변경)
- `📡 Push` 배지 (파란색, 임계값 내 데이터 미수신 시 주황색으로 변경)
- 두 배지 모두 활성화된 경우 동시에 표시될 수 있습니다.
- 배지 없음 = Basic 모드만
### 상태 열
상태 열은 조치가 필요한 경우에만 무언가를 표시합니다:
| 지표 | 배지 | 의미 |
|-----------|-------|---------|
| 오프라인 | `🔴 Offline` | 인스턴스에 접근 불가 (HTTP 프로브 실패), "X 동안 오프라인" 표시 |
| 활성 경고 | `⚠️ N` | N개의 구성 문제 |
| 앱 감사 | `📦 N` | 검토할 설치된 앱 N개 (업그레이드 차단/호환 불가) |
| SSL 만료 | `🔒 N d` | 인증서 곧 만료 — 주황색 `< 15일`, 빨간색 `< 7일` 또는 만료됨 |
| Docker 업데이트 | `🔄 M` | M개의 컨테이너 업데이트 가능 |
| 모두 정상 | *(비어 있음)* | 보고할 사항 없음 |
| 데이터 없음 | `?` | 푸시 데이터가 없는 Basic 모드 |
#### 업/다운 및 SSL 만료
NcStatusCheck는 서버별로 **최소한의** 업/다운 상태를 유지합니다(현재 상태 + 마지막 변경 날짜만 — 시계열 없음, 기록 페이지 없음). "업"은 아웃바운드 HTTPS 프로브가 인스턴스에 도달했음을 의미합니다. 빨간색 **Offline** 배지는 다운된 경우에만 나타납니다. 동일한 HTTPS 프로브 중에 **SSL 인증서 만료** 정보가 무료로 읽히며(`CURLOPT_CERTINFO`), 만료가 임박하면 표시됩니다. 둘 다 상세 페이지에서 전체적으로 확인할 수 있습니다. *참고: 이러한 아웃바운드 검사는 모니터가 연락하지 않는 Push 전용 인스턴스에는 적용되지 않습니다.*
#### 앱 감사 (`📦`)
Push 서버가 설치된 앱을 보고하면(`occ app:list`, push 스크립트 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**: 저장소에는 위의 `deny` 규칙을 반영한 `.htaccess` 파일이 포함되어 있습니다
> (루트: `*.log`/`*.json`/`*.txt` 및 `.git` 차단; `cache/`, `tools/`,
> `deploy/`, `tests/`: `Require all denied`). 이 파일들은 vhost에서
> `AllowOverride FileInfo AuthConfig` (또는 `All`)가 설정된 경우에만 작동합니다 —
> Debian의 `/var/www` 기본값은 `AllowOverride None`이므로,
> 이 경우 vhost에서 직접 규칙을 복제하십시오. 어느 쪽이든 HTTP 기본 인증은 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
sudo chown -R www-data:www-data .
sudo find . -type f -exec chmod 644 {} \;
sudo find . -type d -exec chmod 755 {} \;
``````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으로 자체 chmod 처리하므로, 관리자 UI를 통해 생성(또는 재생성)된servers.json은 내부의 serverinfo/push 토큰이 그룹이나 전역에서 읽히는 상태로 남지 않습니다.
6. **예약된 작업 (선택 사항)**
두 개의 보완적인 cron — 둘 다 **동일한 `crontab -` 호출에** 설치하십시오:
`crontab -`는 stdin에서 완전히 새로운 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만 확인하고 up/down 상태(cache/uptime_state.json)를 업데이트하므로 부하 없이 자주 실행될 수 있습니다. 인스턴스는 UPTIME_FAIL_THRESHOLD 연속 실패 프로브(기본값 2 → 5분 간격으로 약 10분) 후에만 down으로 표시되며, up으로의 복구는 즉시 이루어집니다. 전체 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로 이동아무 서버 이름 또는 상태 표시기를 클릭하여 접근 가능합니다.
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)
push_token 헤더와 함께 POST — 원격 NC 인스턴스에서 푸시 데이터 수신POST ?action=request_push_all — 구성된 모든 푸시 서버에 즉시 푸시 요청 (원격 cron 스크립트가 소비하는 트리거 플래그 설정)관리 UI에서 생성된 cron 스크립트는 두 부분으로 나뉩니다: a 일반 코어
/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→ ): , , , , 상태 . 코어만 공유되므로 같은 위치의 인스턴스가 충돌하지 않습니다.
Detail API (detail-api.php)
GET ?server=<url> — Extended/Push 서버에 대한 전체 서버 정보 데이터 + 계산된 경고관리 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)모니터링과 별도의 하위 시스템: 웹 + PHP + 데이터베이스 튜닝에 대한 일회성/월간 감사를 위해 Nextcloud 서버에서 root로 실행되는 독립적인 읽기 전용 bash 스크립트(tools/nc-audit.sh)이며, 머신의 물리적 용량(RAM, CPU, 디스크 유형)과 교차 확인합니다. 관리형 감독 제품을 대상으로 합니다: 클라이언트가 설치하고, 모니터는 보고서만 수신합니다 — 머신/네트워크 접근 불필요. 스크립트는 구성을 읽기만 하며(변경 없음), 컬러 보고서를 출력하고 /tmp에 복사본을 기록합니다.
확인하는 항목: 서버 용량 (RAM/CPU/SSD-HDD, 스왑피니스, 공유 서버 감지) · Nextcloud (버전, cron, 캐시, Redis 런타임, DB 유형, 로그) · 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, 나머지는 DB + 웹 + OS를 차지합니다 — DB가 많이 사용되는 서버에서는 낮게 설정하세요)는 그 중 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
**보고서 푸시백** (선택 사항, Push 인프라 재사용): `nc-audit.sh --push
/etc/ncstatuscheck/<slug>.conf` 감사를 실행하고 보고서를 모니터(들)에 POST합니다. 모니터는 이를 저장하고 서버 상세 페이지("🩺 서버 감사" 섹션)에 표시합니다. 일반적으로 월별 크론 작업입니다. `audit.php` 웹 페이지(admin, beta)는 스크립트를 배포하고(다운로드 + 인라인 + 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는 gitignore 처리되어 서버별로 수동 편집되므로 차이가 발생합니다. 거의 모든 상수가 코드에 대체 값을 가지므로 자동으로 조용히 넘어갑니다. 관리자 페이지 상단의 배너는 실제로 문제가 있는 경우에만 보고합니다: 업데이트 후 남은 PUSH_SCRIPT_VERSION, 경고 전송이 전혀 구성되지 않음, header() 앞에 바이트를 내보내는 닫는 태그, 쓰기 불가능한 캐시 디렉터리, 상수가 없어 기본값으로 조용히 대체됨.
읽기 전용이며 저장 기능이 없습니다. 알림 탭과 동일한 이유입니다: config.php는 루트 소유이며 비밀을 보관합니다. 파일 내용은 절대 전송되지 않으며(관련 정보만 전송), 비밀도 읽히지 않습니다.
위의 모든 것은 애플리케이션 수준입니다. 인터넷의 누구나 여전히 모니터에 도달하여 조사할 수 있으며, 비밀번호만이 이를 막습니다. IP 필터링 탭은 앱 앞에 허용 목록을 배치하는 규칙을 생성하므로 알 수 없는 호스트는 앱과 전혀 통신할 수 없습니다. 이는 심층 방어이며 기본 인증이나 푸시 토큰을 대체하지 않습니다. 그리고 생성하는 것은 검토하고 붙여넣을 텍스트일 뿐이며, 웹 서버나 방화벽 설정을 직접 작성하지 않습니다.
의도적으로 불평등하게 설계된 두 가지 소스 클래스가 있습니다. 손상된 모니터링 서버가 관리자에 도달할 수 없도록 합니다:
| Class | 대상 | 도달 가능 |
|---|---|---|
push | 모니터링 대상 인스턴스 Push 모드만 | /push-api.php, 그 외에는 없음 |
admin | 베스천 / VPN / 고정 사무실 IP | 전체 |
Basic/Extended 모드에서 폴링되는 인스턴스는 인바운드 연결을 열지 않으며 허용 목록 항목을 전혀 얻지 못합니다.
주소는 두 가지 소스에서 제공되며 그 차이가 중요합니다. 모니터링되는 도메인의 DNS 레코드는 인그레스 주소인 반면, 푸시는 이그레스에서 나옵니다. 둘이 다른 경우 두 번째 주소만 작동합니다. 따라서 push-api.php는 모든 푸시의 실제 소스 주소를 기록하고(푸시 캐시의 source_ip), 탭은 이를 허용 목록에 추가하며 불일치를 보고합니다. 서버가 한 번 푸시할 때까지 DNS A+AAAA로 대체되며 그렇게 명시됩니다.
세 가지 출력:
conf.d 파일(geo + map)과 vhost의 단일 if ($ncsc_forbidden) { return 403; } 줄. fastcgi 블록을 중복할 필요가 없으며 정적 파일도 포함됩니다(admin.html이 그중 하나). /.well-known/acme-challenge/는 열려 있어 인증서 갱신이 조용히 실패하지 않습니다.<LocationMatch>와 푸시 엔드포인트용 <Location>을 사용하여 두 섹션이 겹치지 않고 Apache의 병합 순서에 의존하지 않습니다. 규칙의 모든 주소는 한 줄의 Require ip에 넣습니다. <RequireAll> 내의 여러 줄은 AND 처리되어 누구도 만족할 수 없습니다.관리자 주소가 제공되지 않으면 생성기는 아무 것도 내보내지 않으며, 운영자 자신의 주소가 포함되지 않은 경우 경고하고, 요청이 프록시를 통해 들어온 경우 경고합니다(geo와 Require ip 모두 전송 피어를 읽으므로 프록시 뒤에서는 모든 클라이언트가 동일하게 보입니다). 생성된 ufw 스니펫은 SSH 규칙을 먼저 배치하고, HTTP-01 챌린지를 위해 포트 80을 열어두며, IPv6 함정을 명시합니다. nginx는 목록에 없는 v6 주소를 거부하는 반면, ufw는 IPV6=yes가 설정되지 않는 한 v6를 전혀 필터링하지 않습니다. 듀얼 스택 호스트는 그렇지 않으면 IPv6를 통해 완전히 열려 있게 됩니다.
알려진 한계 (페이지 자체에 표시됨): 규칙이 적용되면 이 탭은 새로운 것을 발견하지 못합니다. 거부된 푸시는 PHP에 도달하기 전에 웹 서버에 의해 차단되므로 기록된 주소는 마지막으로 통과한 주소로 유지되며 여전히 확인된 것처럼 보입니다. 두 가지 결과: Push 서버를 추가하려면 규칙을 다시 생성하고 다시 적용해야 합니다. 그렇지 않으면 첫 번째 푸시가 거부됩니다. 인스턴스의 주소가 변경되면 새 주소는 웹 서버 액세스 로그(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
| 응답 | 의미 |
|---|---|
| `403` | IP 필터링에 의해 차단됨 |
| `401` | 필터링 통과, Basic auth가 응답 중 — 문제는 다른 곳에 있음 |
| `405` | 요청이 애플리케이션에 도달함 (GET이 해당 메서드로 허용되지 않음) |
| 없음 / 타임아웃 | 필터링 아님: 필터가 응답하지만 조용해지지 않음 |
`403`에 대한 의문을 해소하려면 `-u user:password`로 다시 실행하세요. 코드가 변하지 않으면 실제로 필터링입니다. 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`도 없어 스캔할 것이 없습니다. 위험은 사용자 정의 코드(PHP 약 15,000줄, JS 약 6,000줄)와 모니터링되는 인스턴스에서 **root로** 실행되는 셸 스크립트(`tools/*.sh`)에 있습니다. 파이프라인은 여기에 초점을 맞춥니다.
| 작업 | 도구 | 차단 여부 | 범위 |
|---|---|---|---|
| `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`) | 벤더된 고정 버전과 업스트림 릴리스 비교 |
| `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를 제공하지만, CVE-2026-33630을 수정한 1.34.8-r0가 이미 게시되었습니다). 따라서 베이스 태그를 스캔하면 배포 이미지에 더 이상 존재하지 않는 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
어떠한 중요한 발견(소스 누출, 차단되지 않은 비밀 파일, 전 세계에서 읽을 수 있는 토큰 저장소, Basic auth 누락)이 있으면 0이 아닌 종료 코드를 반환하므로, 배포를 게이트할 수 있습니다. 이를 sync/deploy 스크립트에 사후 단계로 연결하세요. 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` — 사전 경고 전송(웹훅/이메일), 웹훅 비밀 또는 SMTP 자격 증명은 절대 로깅하지 않음
### 테스트 스위트```bash
php tests/run.php # plain-PHP assertions, no framework — exit 0 = all green
순수 비즈니스 로직을 다룹니다 (버전/앱 규칙, 경고, 가동 시간 상태 머신 및 가용성, 알림 중복 제거/재무장 상태 머신, 이메일 생성기).
다음 내용과 함께 새 이슈를 열어주세요:
이 프로젝트는 GNU AGPL v3에 따라 라이선스가 부여됩니다.
NcStatusCheck는 오픈 소스 솔루션을 전문으로 하는 디지털 협동조합 ézéo에 의해 개발되었습니다.
도움이 필요하신가요? 이슈를 확인하거나 ézéo 팀에 연락하세요.
| 섹션 | 필드 |
|---|
| Nextcloud 시스템 | 버전, 디버그 모드, 로컬/분산 memcache, 파일 잠금, 디스크 공간 |
| PHP | 버전, memory_limit, upload_max_filesize, max_execution_time, FPM, OPcache |
| 웹 서버 | 이름 + 버전, 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>.lastDocker에서 실행 중인 Nextcloud (공식 이미지, compose, AIO): 완전 지원 — 스크립트는 호스트(root cron + Docker 데몬 접근)에 설치되며, 컨테이너 내부에는 절대 설치되지 않으며, 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를 거부합니다).
플릿 배포/업데이트: 코어가 단일 동일 파일이므로 여러 서버에서 로직 업데이트 = 해당 파일 하나 교체 (↑ 표시는 이전 버전을 실행하는 서버를 표시합니다). 즉시 사용 가능한 플레이북(또는 일반 scp 루프)은 deploy/ansible/를 참조하세요. 모니터는 수동적으로 유지됩니다 — 플릿에 코드를 보내지 않습니다; 신뢰 앵커는 귀하의 SSH 접근이지 모니터가 아닙니다.
v4 이전 설치에서 마이그레이션 (모놀리식 인스턴스별 스크립트): 코어 + 구성을 설치하기 전에 이전 /usr/local/bin/ncstatuscheck-push-<slug>.sh 및 /etc/cron.d/ncstatuscheck-<slug>를 제거하십시오 (targets-<slug>.conf는 그대로 재사용됨), 그렇지 않으면 이중 푸시됩니다.
targets.conf의 추가 줄이 모든 푸시를 제3자에게 조용히 복사합니다.<>"'&가 제거되고 렌더링 시 이스케이프 처리됩니다.try/catch로 잡을 수 없는 치명적인 오류로, 수집 실행을 중간에 중단시키고 전체 플릿에 대한 모든 경고를 무효화합니다.