# Custom Monitoring System — PHP-Only Version Lightweight, self-hosted monitoring for your drilling-company systems. Built with **only PHP, HTML, CSS, JavaScript, and MySQL**. No Node.js, no npm, no WebSocket server. "Real-time" is achieved with smart polling: the dashboard asks the server every 2 seconds *"anything new since ID X?"* The server uses an indexed PK lookup (`WHERE id > ?`), which is essentially free. 2-second latency is imperceptible for a monitoring dashboard. ## What's in the box ``` monitoring/ ├── schema.sql # MySQL schema ├── php/ │ ├── config.php # DB creds + tokens + alert thresholds │ ├── Monitor.php # Logger class used by PHP APIs │ ├── bootstrap.php # One-line instrumentation helper │ ├── api/ │ │ ├── ingest.php # Ingest from browsers/mobile │ │ └── query.php # Dashboard read API + "live" polling │ └── cron/ │ ├── alert_engine.php # Evaluates alerts every minute │ └── retention.php # Trims old rows weekly ├── dashboard/ │ ├── index.html │ ├── dashboard.css │ └── dashboard.js # Polls /api/query.php?action=live ├── client-sdk/ │ └── monitor-client.js # Frontend/mobile SDK └── deploy/ └── nginx-monitor.conf # Nginx vhost (static + PHP-FPM) ``` ## Architecture ``` PHP APIs (Monitor::start/end) ──► MySQL ◄── Dashboard polls every 2s Web+Mobile (MonitorClient) ──► ingest.php ──► MySQL ▲ every 1min: alert_engine.php cron │ Email (SMTP) ``` Key choices: - PHP writes log rows **after** calling `fastcgi_finish_request()` so your APIs see no added latency. - Dashboard polls every 2s using a cursor (`WHERE id > last_seen_id`) — PK lookups stay fast forever. - Alerts evaluated by a PHP cron every minute. Cooldowns stored in a small `alert_cooldowns` table so they survive across runs. - No persistent processes, no daemons. Restarts cleanly, fails cleanly. --- ## Step-by-step deployment ### Before you start — gather this info ``` Monitoring domain: monitor.yourdomain.com Server public IP: _______________ MySQL host: 127.0.0.1 MySQL root password: _______________ Generate tokens now: run openssl rand -hex 32 twice INGEST_TOKEN: _______________________________________________________________ DASHBOARD_TOKEN: _______________________________________________________________ SMTP (for email alerts — only needed for server_error/downtime/critical): From: alerts@yourdomain.com To: ops@yourdomain.com ``` ### Step 1 — Install prerequisites ```bash sudo apt update sudo apt install -y nginx mysql-server php8.2-fpm php8.2-mysql \ php8.2-curl certbot python3-certbot-nginx unzip ``` ### Step 2 — Upload the code ```bash sudo mkdir -p /var/www cd /var/www sudo unzip /path/to/monitoring-php.zip sudo chown -R www-data:www-data /var/www/monitoring ``` ### Step 3 — Create the database ```bash sudo mysql -u root -p < /var/www/monitoring/schema.sql sudo mysql -u root -p ``` Inside MySQL: ```sql CREATE USER 'monitoring'@'localhost' IDENTIFIED BY 'PICK-A-STRONG-PASSWORD'; GRANT SELECT, INSERT, UPDATE, DELETE ON monitoring.* TO 'monitoring'@'localhost'; FLUSH PRIVILEGES; EXIT; ``` ### Step 4 — Edit `/var/www/monitoring/php/config.php` ```bash sudo nano /var/www/monitoring/php/config.php ``` Set: - `db_pass` — the MySQL password from step 3 - `ingest_token` — the INGEST_TOKEN you generated - `dashboard_token` — the DASHBOARD_TOKEN you generated - `allowed_origins` — add every web app origin that will send metrics - `alert_to` and `alert_from` — your SMTP addresses Lock it down: ```bash sudo chmod 640 /var/www/monitoring/php/config.php sudo chown root:www-data /var/www/monitoring/php/config.php ``` ### Step 5 — Edit `/var/www/monitoring/dashboard/dashboard.js` ```bash sudo nano /var/www/monitoring/dashboard/dashboard.js ``` Find the `CONFIG` block at the top (line ~16). Replace with: ```js const CONFIG = { queryBase: 'https://monitor.yourdomain.com/api/query.php', dashboardToken: 'YOUR_DASHBOARD_TOKEN', pollMs: 2000, slowPollMs: 10000, }; ``` ### Step 6 — Serve the client SDK from the dashboard folder ```bash sudo cp /var/www/monitoring/client-sdk/monitor-client.js \ /var/www/monitoring/dashboard/monitor-client.js ``` ### Step 7 — Point DNS at the server Add an A record in your DNS provider: ``` monitor.yourdomain.com A ``` Wait a few minutes, verify: ```bash dig +short monitor.yourdomain.com ``` ### Step 8 — Set up Nginx ```bash sudo nano /var/www/monitoring/deploy/nginx-monitor.conf ``` Replace `monitor.yourdomain.com` with your real hostname. Check the `fastcgi_pass` line matches your PHP-FPM socket (find with `ls /run/php/`). Install: ```bash sudo cp /var/www/monitoring/deploy/nginx-monitor.conf \ /etc/nginx/sites-available/monitor.yourdomain.com sudo ln -s /etc/nginx/sites-available/monitor.yourdomain.com \ /etc/nginx/sites-enabled/ ``` Edit `/etc/nginx/nginx.conf` and add this inside the `http { ... }` block: ``` limit_req_zone $binary_remote_addr zone=ingest:10m rate=20r/s; ``` ### Step 9 — Get TLS cert ```bash sudo certbot --nginx -d monitor.yourdomain.com sudo nginx -t sudo systemctl reload nginx ``` ### Step 10 — Install the cron jobs ```bash sudo crontab -e ``` Add: ``` # Alert engine — every minute * * * * * /usr/bin/php /var/www/monitoring/php/cron/alert_engine.php >> /var/log/monitoring-alerts.log 2>&1 # Data retention — Sundays at 03:00 0 3 * * 0 /usr/bin/php /var/www/monitoring/php/cron/retention.php >> /var/log/monitoring-retention.log 2>&1 ``` Create the log files: ```bash sudo touch /var/log/monitoring-alerts.log /var/log/monitoring-retention.log sudo chown root:root /var/log/monitoring-alerts.log /var/log/monitoring-retention.log ``` ### Step 11 — Verify end-to-end ```bash # 1. Ingest endpoint works curl -X POST https://monitor.yourdomain.com/api/ingest.php \ -H "X-Ingest-Token: YOUR_INGEST_TOKEN" \ -H "Content-Type: application/json" \ -d '{"type":"metric","system_name":"jobcard_api","endpoint":"/test","method":"GET","response_time":50,"status_code":200}' # Expect: {"ok":true} # 2. Row landed in DB mysql -u monitoring -p monitoring -e "SELECT * FROM metrics ORDER BY id DESC LIMIT 1;" # 3. Query API works curl -H "X-Dashboard-Token: YOUR_DASHBOARD_TOKEN" \ "https://monitor.yourdomain.com/api/query.php?action=overview" # Expect: JSON with systems array # 4. Open dashboard: https://monitor.yourdomain.com/ # Green dot = polling OK. Your test metric should appear within 2 seconds. # 5. Manually trigger the alert engine (should exit cleanly) sudo -u www-data /usr/bin/php /var/www/monitoring/php/cron/alert_engine.php ``` ### Step 12 — Connect your existing systems **For each PHP API:** add at the top of `index.php`: ```php require_once '/var/www/monitoring/php/bootstrap.php'; monitor_bootstrap('jobcard_api'); // unique name per system ``` If the system is new, add it to the DB first: ```sql INSERT INTO systems (name) VALUES ('new_system_name'); ``` **For each web app:** add inside ``: ```html ``` Also add the app's origin to `allowed_origins` in `config.php`. **For your mobile app:** POST the same JSON to `/api/ingest.php` after each HTTP call. Fire-and-forget on a background thread. --- ## Alert rules Evaluated every minute by `alert_engine.php`: | Condition | Alert type | Severity | Email? | |---|---|---|---| | `response_time >= 2000ms` (configurable) | `high_response_time` | warning | no | | `status_code >= 500` | `server_error` | critical | yes | | System `last_seen` > 90s ago | `downtime` | critical | yes | | System comes back after downtime | `recovery` | warning | no | | Error logged with severity=critical | `critical_error` | critical | yes | Per-(system,type) cooldown: 5 minutes by default. Tune in `config.php`. ## Security checklist - [x] PDO prepared statements everywhere, `EMULATE_PREPARES=false` - [x] `hash_equals()` token comparison (timing-safe) - [x] Separate tokens for ingest vs dashboard - [x] Strict input validation + length caps on every field - [x] CORS allowlist (no wildcards) - [x] Alert engine uses flock to prevent overlapping runs - [x] Monitor::logMetric/logError swallow their own exceptions — cannot break host apps - [ ] **You** serve everything behind HTTPS (certbot above) - [ ] **You** keep `config.php` out of version control ## Performance notes - Logging adds ~1ms server-side, **0ms client-perceived** (flushed after response) - Persistent PDO connection avoids per-request handshake - `idx_system_created` composite index matches the dashboard's queries - Dashboard poll uses PK cursor — stable regardless of table size - Polling pauses automatically when the dashboard tab is hidden - After 3 consecutive failures the dashboard backs off to 10s polls ## Scaling notes This stack is comfortable up to ~200 req/sec sustained across all your systems. If you exceed that: 1. Move MySQL to a dedicated host 2. Add a read replica for the dashboard 3. Consider partitioning `metrics` by day Past that point, reconsider the Node.js + WebSocket version. But most small businesses never reach this bar.