{"id":46238543,"url":"https://github.com/jmlweb/webstatuspi","last_synced_at":"2026-03-03T19:34:23.788Z","repository":{"id":333185306,"uuid":"1136448796","full_name":"jmlweb/webstatuspi","owner":"jmlweb","description":"Ultra-lightweight web monitoring system designed for Raspberry Pi 1B+. Monitors URLs, tracks success/failure stats, and provides a JSON API.","archived":false,"fork":false,"pushed_at":"2026-01-26T07:49:42.000Z","size":1200,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-01-26T11:11:27.148Z","etag":null,"topics":["alerting","dashboard","home-lab","iot","lightweight","monitoring","python","raspberry","raspberry-pi","self-hosted","sqlite","status","uptime-monitor"],"latest_commit_sha":null,"homepage":"https://status.jmlweb.es","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jmlweb.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-01-17T17:56:09.000Z","updated_at":"2026-01-26T07:49:45.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/jmlweb/webstatuspi","commit_stats":null,"previous_names":["jmlweb/webstatuspi"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/jmlweb/webstatuspi","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jmlweb%2Fwebstatuspi","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jmlweb%2Fwebstatuspi/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jmlweb%2Fwebstatuspi/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jmlweb%2Fwebstatuspi/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jmlweb","download_url":"https://codeload.github.com/jmlweb/webstatuspi/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jmlweb%2Fwebstatuspi/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30056070,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-03T18:21:05.932Z","status":"ssl_error","status_checked_at":"2026-03-03T18:20:59.341Z","response_time":61,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["alerting","dashboard","home-lab","iot","lightweight","monitoring","python","raspberry","raspberry-pi","self-hosted","sqlite","status","uptime-monitor"],"created_at":"2026-03-03T19:34:23.022Z","updated_at":"2026-03-03T19:34:23.776Z","avatar_url":"https://github.com/jmlweb.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/platform-Raspberry%20Pi-C51A4A?style=for-the-badge\u0026logo=raspberry-pi\" alt=\"Raspberry Pi\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/python-3.11+-3776AB?style=for-the-badge\u0026logo=python\u0026logoColor=white\" alt=\"Python 3.11+\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/license-MIT-green?style=for-the-badge\" alt=\"MIT License\"\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://github.com/jmlweb/webstatuspi/actions/workflows/test.yml\"\u003e\u003cimg src=\"https://github.com/jmlweb/webstatuspi/actions/workflows/test.yml/badge.svg\" alt=\"Test\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://github.com/jmlweb/webstatuspi/actions/workflows/lint.yml\"\u003e\u003cimg src=\"https://github.com/jmlweb/webstatuspi/actions/workflows/lint.yml/badge.svg\" alt=\"Lint\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://status.jmlweb.es\"\u003e\u003cimg src=\"https://status.jmlweb.es/badge.svg\" alt=\"Status\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n\u003ch1 align=\"center\"\u003e🖥️ WebStatusπ\u003c/h1\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cstrong\u003eUltra-lightweight web monitoring for Raspberry Pi\u003c/strong\u003e\u003cbr\u003e\n  \u003cem\u003eTrack uptime, response times, and get instant alerts — all from a $35 computer\u003c/em\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"dashboard-home.png\" alt=\"Dashboard Preview\" width=\"600\"\u003e\n\u003c/p\u003e\n\n---\n\n## ✨ Why WebStatusπ?\n\n| Feature | Benefit |\n|---------|---------|\n| **🪶 Ultra-lightweight** | Runs on Raspberry Pi 1B+ (512MB RAM) |\n| **📊 Real-time Dashboard** | CRT-style cyberpunk interface |\n| **🔌 JSON API** | Integrate with anything |\n| **💾 Persistent Storage** | SQLite keeps your history safe |\n| **⚡ Zero Config** | Works out of the box |\n\n### Resource Comparison\n\nHow does WebStatusπ compare to popular alternatives?\n\n**Docker Benchmark** (5 URLs, 60s interval, 10 samples)\n\n| Tool | RAM Usage | CPU Usage | Docker Image |\n|------|-----------|-----------|--------------|\n| **WebStatusπ** | **26 MB** | 0.2% | 66 MB |\n| Statping-ng | 32 MB | 0.3% | 58 MB |\n| Uptime Kuma | 124 MB | 0.5% | 439 MB |\n\n**Real-world on Raspberry Pi 1B+** (ARMv6, 512MB RAM)\n\n| Metric | Value |\n|--------|-------|\n| RAM (application) | ~20 MB |\n| RAM (with Python runtime) | ~34 MB |\n| CPU | ~1% average |\n| Storage | \u003c1 MB (uses system Python) |\n\n**Installation Size** (native, no Docker)\n\n| Tool | Install Size | Requires |\n|------|--------------|----------|\n| **WebStatusπ** | **\u003c1 MB** | System Python 3.7+ |\n| Statping-ng | ~58 MB | Go binary |\n| Uptime Kuma | ~150 MB | Node.js runtime |\n\n*Run `./benchmark/benchmark.sh` to reproduce the Docker benchmark.*\n\n---\n\n## 🚀 Quick Start\n\n### One-Line Install (Recommended)\n\nRun this on your Raspberry Pi:\n\n```bash\ncurl -sSL https://raw.githubusercontent.com/jmlweb/webstatuspi/main/install.sh | bash\n```\n\nThe interactive installer will:\n- Install dependencies and create a virtual environment\n- Guide you through URL configuration\n- Optionally set up auto-start on boot\n\n**That's it!** Open `http://\u003cyour-pi-ip\u003e:8080` in your browser.\n\n\u003cdetails\u003e\n\u003csummary\u003e📦 Manual Installation\u003c/summary\u003e\n\n```bash\n# Clone and install\ngit clone https://github.com/jmlweb/webstatuspi.git\ncd webstatuspi\npython3 -m venv venv\nsource venv/bin/activate\npip install .\n\n# Configure\ncp config.example.yaml config.yaml\n# Edit config.yaml with your URLs\n\n# Run\nwebstatuspi\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e⚙️ Installer Options\u003c/summary\u003e\n\n```bash\n# Interactive installation\n./install.sh\n\n# Non-interactive with defaults\n./install.sh --non-interactive\n\n# System-wide installation (with systemd service)\nsudo ./install.sh --install-dir /opt/webstatuspi\n\n# Update existing installation\n./install.sh --update\n\n# Uninstall\n./install.sh --uninstall\n```\n\n\u003c/details\u003e\n\n---\n\n## 📺 Dashboard\n\n\u003ctable\u003e\n\u003ctr\u003e\n\u003ctd width=\"50%\"\u003e\n\n### Overview\n\u003cimg src=\"dashboard-home.png\" alt=\"Dashboard Home\" width=\"100%\"\u003e\n\nReal-time status cards with latency and 24h uptime metrics.\n\n\u003c/td\u003e\n\u003ctd width=\"50%\"\u003e\n\n### Detail View\n\u003cimg src=\"dashboard-detail.png\" alt=\"Dashboard Detail\" width=\"100%\"\u003e\n\nClick any card to see full check history with timestamps.\n\n\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/table\u003e\n\n**Features:**\n- 🔄 Auto-refresh every 10 seconds\n- 🟢🔴 Color-coded status indicators\n- 📈 Response time graphs\n- 🕹️ Retro CRT aesthetic with scanlines\n- 📱 **PWA Support** - Install as app on mobile/desktop\n\n### 📱 Install as App (PWA)\n\nThe dashboard is a Progressive Web App that can be installed on your device for quick access:\n\n**Desktop (Chrome/Edge):**\n1. Open the dashboard in your browser\n2. Click the install icon (⊕) in the address bar\n3. Click \"Install\"\n\n**Mobile (Android):**\n1. Open the dashboard in Chrome\n2. Tap the menu (⋮) → \"Add to Home Screen\"\n3. Tap \"Install\"\n\n**Mobile (iOS):**\n1. Open the dashboard in Safari\n2. Tap the share button (↑)\n3. Tap \"Add to Home Screen\"\n\n**PWA Features:**\n- Works offline (shows cached data when network unavailable)\n- App-like experience (no browser UI)\n- Automatic updates when new versions are deployed\n- Fast loading via Service Worker caching\n\n\u003e **Note:** For production deployments, HTTPS is required for PWA features to work. On `localhost`, PWA works without HTTPS for development.\n\n---\n\n## 🔧 API Reference\n\n### Endpoints\n\n| Method | Endpoint | Description |\n|--------|----------|-------------|\n| `GET` | `/` | Web dashboard |\n| `GET` | `/status` | All URLs status with 24h statistics |\n| `GET` | `/status/{name}` | Specific URL status |\n| `GET` | `/history/{name}` | Check history (last 24h, max 100 records) |\n| `GET` | `/health` | Health check |\n| `GET` | `/metrics` | Prometheus metrics |\n| `GET` | `/badge.svg` | Status badge (SVG image) |\n| `DELETE` | `/reset` | Reset all data (token auth if configured) |\n\n### Example Response\n\n```bash\ncurl http://localhost:8080/status\n```\n\n```json\n{\n  \"urls\": [\n    {\n      \"name\": \"MY_SITE\",\n      \"url\": \"https://example.com\",\n      \"is_up\": true,\n      \"status_code\": 200,\n      \"response_time_ms\": 150,\n      \"error\": null,\n      \"last_check\": \"2026-01-23T10:30:00Z\",\n      \"checks_24h\": 1440,\n      \"uptime_24h\": 99.5,\n      \"avg_response_time_24h\": 145.2,\n      \"min_response_time_24h\": 120,\n      \"max_response_time_24h\": 200,\n      \"p50_response_time_24h\": 142,\n      \"p95_response_time_24h\": 185,\n      \"p99_response_time_24h\": 198,\n      \"stddev_response_time_24h\": 15.3,\n      \"consecutive_failures\": 0,\n      \"ssl_cert_expires_in_days\": 365\n    }\n  ],\n  \"summary\": {\n    \"total\": 1,\n    \"up\": 1,\n    \"down\": 0\n  }\n}\n```\n\n### Delete/Reset Endpoint\n\nReset all monitoring data (useful for testing or fresh start):\n\n```bash\n# Without token (if api.reset_token not configured)\ncurl -X DELETE http://localhost:8080/reset\n\n# With token authentication\ncurl -X DELETE http://localhost:8080/reset \\\n  -H \"Authorization: Bearer your-secret-token\"\n```\n\n**Security notes:**\n- Blocked if accessed through Cloudflare (external access)\n- Requires Bearer token if `api.reset_token` is set in config\n\n### Status Badge\n\nEmbed a shields.io-style status badge in your README or website:\n\n```markdown\n\u003c!-- Overall system status --\u003e\n![Status](https://your-domain.com/badge.svg)\n\n\u003c!-- Specific service status --\u003e\n![API](https://your-domain.com/badge.svg?url=API_PROD)\n```\n\n**Badge colors:**\n\n| State | Color | Meaning |\n|-------|-------|---------|\n| UP | Green | All services operational (or specific service is up) |\n| DOWN | Red | All services down (or specific service is down) |\n| DEGRADED | Yellow | Some services up, some down |\n| UNKNOWN | Gray | No monitoring data available |\n\n**Headers:**\n- `Content-Type: image/svg+xml`\n- `Cache-Control: public, max-age=60` (CDN-friendly)\n\n---\n\n## 📊 Prometheus Integration\n\nWebStatusπ exposes metrics in Prometheus text format, allowing you to integrate with your existing monitoring stack and create custom Grafana dashboards.\n\n### Metrics Endpoint\n\n**GET** `/metrics`\n\nReturns metrics in Prometheus exposition format:\n\n```bash\ncurl http://localhost:8080/metrics\n```\n\n**Example output:**\n\n```prometheus\n# HELP webstatuspi_uptime_percentage Uptime percentage for the last 24 hours\n# TYPE webstatuspi_uptime_percentage gauge\nwebstatuspi_uptime_percentage{url_name=\"MY_SITE\",url=\"https://example.com\"} 99.5\n\n# HELP webstatuspi_response_time_ms Response time metrics in milliseconds\n# TYPE webstatuspi_response_time_ms gauge\nwebstatuspi_response_time_ms{url_name=\"MY_SITE\",url=\"https://example.com\",type=\"avg\"} 150.0\nwebstatuspi_response_time_ms{url_name=\"MY_SITE\",url=\"https://example.com\",type=\"min\"} 120\nwebstatuspi_response_time_ms{url_name=\"MY_SITE\",url=\"https://example.com\",type=\"max\"} 200\n\n# HELP webstatuspi_checks_total Total number of checks performed\n# TYPE webstatuspi_checks_total counter\nwebstatuspi_checks_total{url_name=\"MY_SITE\",url=\"https://example.com\",status=\"success\"} 143\nwebstatuspi_checks_total{url_name=\"MY_SITE\",url=\"https://example.com\",status=\"failure\"} 1\n\n# HELP webstatuspi_last_check_timestamp Unix timestamp of last check\n# TYPE webstatuspi_last_check_timestamp gauge\nwebstatuspi_last_check_timestamp{url_name=\"MY_SITE\",url=\"https://example.com\"} 1737544800\n```\n\n### Available Metrics\n\n| Metric | Type | Description | Labels |\n|--------|------|-------------|--------|\n| `webstatuspi_uptime_percentage` | gauge | Uptime percentage for the last 24 hours (0-100) | `url_name`, `url` |\n| `webstatuspi_response_time_ms` | gauge | Response time in milliseconds | `url_name`, `url`, `type` (avg/min/max) |\n| `webstatuspi_checks_total` | counter | Total number of checks performed | `url_name`, `url`, `status` (success/failure) |\n| `webstatuspi_last_check_timestamp` | gauge | Unix timestamp of last check | `url_name`, `url` |\n\n### Prometheus Configuration\n\nAdd this scrape config to your `prometheus.yml`:\n\n```yaml\nscrape_configs:\n  - job_name: 'webstatuspi'\n    static_configs:\n      - targets: ['\u003craspberry-pi-ip\u003e:8080']\n    metrics_path: /metrics\n    scrape_interval: 30s\n```\n\n### Example PromQL Queries\n\n**Average uptime across all URLs:**\n```promql\navg(webstatuspi_uptime_percentage)\n```\n\n**URLs with uptime below 99%:**\n```promql\nwebstatuspi_uptime_percentage \u003c 99\n```\n\n**Average response time per URL:**\n```promql\nwebstatuspi_response_time_ms{type=\"avg\"}\n```\n\n**Total failures in last hour:**\n```promql\nincrease(webstatuspi_checks_total{status=\"failure\"}[1h])\n```\n\n**URLs not checked in last 5 minutes:**\n```promql\n(time() - webstatuspi_last_check_timestamp) \u003e 300\n```\n\n### Grafana Dashboard\n\nCreate custom Grafana dashboards using these metrics:\n\n**Example panels:**\n\n1. **Uptime Overview** (Gauge panel)\n   - Query: `avg(webstatuspi_uptime_percentage)`\n   - Thresholds: Red (\u003c95%), Yellow (95-99%), Green (\u003e99%)\n\n2. **Response Time by URL** (Graph panel)\n   - Query: `webstatuspi_response_time_ms{type=\"avg\"}`\n   - Legend: `{{url_name}}`\n\n3. **Success Rate** (Stat panel)\n   - Query: `sum(webstatuspi_checks_total{status=\"success\"}) / sum(webstatuspi_checks_total) * 100`\n\n4. **Failed Checks (Last Hour)** (Stat panel)\n   - Query: `increase(webstatuspi_checks_total{status=\"failure\"}[1h])`\n\n5. **Check Status Table** (Table panel)\n   - Queries:\n     - `webstatuspi_uptime_percentage` (Uptime %)\n     - `webstatuspi_response_time_ms{type=\"avg\"}` (Avg Response Time)\n     - `time() - webstatuspi_last_check_timestamp` (Last Check)\n\n### Integration Notes\n\n- **Scraping frequency**: Recommended 30-60 seconds to match typical check intervals\n- **Cardinality**: Low - one metric series per URL (typically 5-20 URLs)\n- **Performance**: Zero overhead - metrics are computed from existing database queries\n- **History**: Metrics reflect 24-hour aggregates from SQLite database\n\n---\n\n## 🔔 Webhook Alerts\n\nGet instant notifications when URLs go down or recover. Integrate with Slack, Discord, PagerDuty, or any custom webhook endpoint.\n\n### Setup Webhooks\n\nAdd to your `config.yaml`:\n\n```yaml\nalerts:\n  webhooks:\n    - url: \"https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK\"\n      enabled: true\n      on_failure: true      # Alert when URL goes DOWN\n      on_recovery: true     # Alert when URL comes back UP\n      cooldown_seconds: 300 # Minimum time between alerts (prevents spam)\n```\n\n### Supported Services\n\n| Service | Webhook Type | Notes |\n|---------|--------------|-------|\n| **Slack** | Incoming Webhook | Configure in Apps \u0026 integrations |\n| **Discord** | Webhook URL | Copy webhook URL from channel settings |\n| **Telegram** | Bot API | [Setup guide](docs/TELEGRAM_SETUP.md) - requires relay service |\n| **PagerDuty** | Events API v2 | Sends to incidents/events endpoint |\n| **Custom HTTP** | Any endpoint | Receives JSON payload |\n\n### Webhook Payload\n\nEvery alert sends this JSON structure:\n\n```json\n{\n  \"event\": \"url_down\",\n  \"url\": {\n    \"name\": \"API_SERVER\",\n    \"url\": \"https://api.example.com\"\n  },\n  \"status\": {\n    \"code\": 503,\n    \"success\": false,\n    \"response_time_ms\": 5000,\n    \"error\": \"Service Unavailable\",\n    \"timestamp\": \"2026-01-21T10:30:00Z\"\n  },\n  \"previous_status\": \"up\"\n}\n```\n\n### Latency Degradation Alerts\n\nMonitor response times and get alerted when services become slow, even if they're still responding successfully. This helps detect performance degradation before it causes a complete outage.\n\n#### Setup Latency Monitoring\n\nAdd latency thresholds to any URL in your configuration:\n\n```yaml\nurls:\n  - name: \"CRITICAL_API\"\n    url: \"https://api.example.com/health\"\n    latency_threshold_ms: 2000        # Alert if response time exceeds 2000ms\n    latency_consecutive_checks: 3     # Must exceed threshold 3 times in a row (default: 3)\n\n  - name: \"PAYMENT_API\"\n    url: \"https://payments.example.com\"\n    latency_threshold_ms: 1000         # Alert if response time exceeds 1000ms\n    latency_consecutive_checks: 5      # Require 5 consecutive slow checks before alerting\n```\n\n#### How It Works\n\n1. **Threshold Detection**: After each check, if the response time exceeds `latency_threshold_ms`, a counter increments.\n2. **Alert Trigger**: When the counter reaches `latency_consecutive_checks`, a `latency_high` alert is sent.\n3. **Recovery**: When latency returns below the threshold, a `latency_normal` alert is sent and the counter resets.\n\nThis prevents false positives from temporary network spikes while catching sustained performance issues.\n\n#### Latency Alert Payload\n\nLatency alerts use the same webhook infrastructure as up/down alerts, but with a different event type:\n\n```json\n{\n  \"event\": \"latency_high\",\n  \"url\": {\n    \"name\": \"CRITICAL_API\",\n    \"url\": \"https://api.example.com/health\"\n  },\n  \"latency\": {\n    \"current_ms\": 2500,\n    \"threshold_ms\": 2000,\n    \"consecutive_checks\": 3\n  },\n  \"timestamp\": \"2026-01-23T10:30:00Z\"\n}\n```\n\n**Event Types:**\n- `latency_high`: Triggered when latency exceeds threshold for configured consecutive checks\n- `latency_normal`: Triggered when latency returns below threshold after an active alert\n\n**Note**: Latency alerts respect the same `cooldown_seconds` setting as up/down alerts to prevent spam.\n\n### Configuration Options\n\n```yaml\nalerts:\n  webhooks:\n    - url: \"https://example.com/webhook\"\n\n      # Whether this webhook is active (default: true)\n      enabled: true\n\n      # Send alert when URL transitions from UP → DOWN (default: true)\n      on_failure: true\n\n      # Send alert when URL transitions from DOWN → UP (default: true)\n      on_recovery: true\n\n      # Minimum seconds between alerts for the same URL (default: 300)\n      # Prevents alert spam if a service is flaky\n      cooldown_seconds: 300\n```\n\n### Test Your Webhooks\n\nVerify webhook configuration before deployment:\n\n```bash\nwebstatuspi test-alert\n```\n\nOutput:\n```\nTesting 2 webhook(s)...\n\n✓ SUCCESS: https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK\n✗ FAILED: https://example.com/broken-webhook\n\nResult: 1/2 webhooks successful\n```\n\n### Advanced: Webhook Security\n\nFor sensitive webhooks, use these patterns:\n\n**Option 1: Authentication Headers**\nConfigure your webhook endpoint to require an auth token, then test with curl:\n\n```bash\ncurl -X POST https://example.com/webhook \\\n  -H \"Authorization: Bearer YOUR_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"event\":\"test\"}'\n```\n\n**Option 2: HMAC Signature (Future)**\nWebStatusπ may support HMAC-SHA256 signatures in future versions.\n\n### Example: Slack Integration\n\n1. Create Slack Incoming Webhook: https://api.slack.com/apps → Your App → Incoming Webhooks\n2. Copy webhook URL\n3. Add to `config.yaml`:\n\n```yaml\nalerts:\n  webhooks:\n    - url: \"https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXX\"\n      enabled: true\n      on_failure: true\n      on_recovery: true\n      cooldown_seconds: 300\n```\n\n4. Test: `webstatuspi test-alert`\n5. Your Slack channel will receive alerts when services go down\n\n### Example: Discord Integration\n\n1. Enable Developer Mode in Discord (User Settings → Advanced)\n2. Right-click channel → Edit Channel → Webhooks → Create Webhook\n3. Copy webhook URL\n4. Add to `config.yaml`:\n\n```yaml\nalerts:\n  webhooks:\n    - url: \"https://discord.com/api/webhooks/123456789/abcdefg\"\n      enabled: true\n      on_failure: true\n      on_recovery: false  # Only alert on failures\n      cooldown_seconds: 600\n```\n\n---\n\n## 🔄 Auto-Start on Boot\n\nInstall as a systemd service:\n\n```bash\n# Preview what will be installed\nwebstatuspi install-service --dry-run\n\n# Install and start\nsudo webstatuspi install-service --enable --start\n```\n\n\u003cdetails\u003e\n\u003csummary\u003e📋 Service management commands\u003c/summary\u003e\n\n```bash\nsudo systemctl status webstatuspi   # Check status\nsudo journalctl -u webstatuspi -f   # View live logs\nsudo systemctl restart webstatuspi  # Restart\nsudo systemctl stop webstatuspi     # Stop\n```\n\n\u003c/details\u003e\n\n---\n\n## ⚙️ Configuration\n\n### Full Example\n\n```yaml\nmonitor:\n  interval: 60              # seconds between checks\n\nurls:\n  - name: \"PROD_API\"        # max 10 characters\n    url: \"https://api.example.com\"\n    timeout: 10\n\n  - name: \"STAGING\"\n    url: \"https://staging.example.com\"\n    timeout: 5\n\nserver:\n  port: 8080\n  host: 0.0.0.0             # listen on all interfaces\n\ndatabase:\n  path: \"./data/monitoring.db\"\n  retention_days: 7         # auto-cleanup old data\n```\n\n### Content Validation\n\nMonitor not just HTTP status codes, but also response content. This helps detect when services return error pages with 200 status codes.\n\n**Keyword Validation** - Check if response body contains a specific string:\n\n```yaml\nurls:\n  - name: \"API_PROD\"\n    url: \"https://api.example.com/health\"\n    timeout: 10\n    keyword: \"OK\"  # Case-sensitive substring check\n```\n\n**JSON Path Validation** - Check if JSON response has expected value at path:\n\n```yaml\nurls:\n  - name: \"API_STG\"\n    url: \"https://staging.example.com/api/status\"\n    timeout: 10\n    json_path: \"status.healthy\"  # Checks if response.status.healthy is truthy\n```\n\n**How it works:**\n\n- Validation only runs if HTTP status is 2xx or 3xx\n- If validation fails, URL is marked as DOWN with error message\n- Response body limited to 1MB for memory efficiency (Pi 1B+)\n- Keyword check: case-sensitive substring match\n- JSON path: supports dot-notation (e.g., `\"data.user.active\"`)\n- JSON values must be truthy (true, \"ok\", 1, etc.)\n\n**Use cases:**\n\n| Scenario | Solution |\n|----------|----------|\n| API returns error page with 200 status | Use `keyword` to check for success message |\n| Health endpoint returns JSON status | Use `json_path` to check specific field |\n| CDN returns cached error page | Use `keyword` to verify expected content |\n| Backend returns maintenance page | Use `keyword` to detect unexpected content |\n\n### Custom Success Codes\n\nBy default, HTTP status codes 200-399 are treated as success. You can customize this per URL using `success_codes`:\n\n```yaml\nurls:\n  # Accept specific codes (e.g., API that returns 201 on create)\n  - name: \"API_CREATE\"\n    url: \"https://api.example.com/create\"\n    timeout: 10\n    success_codes: [200, 201, 202]\n\n  # Accept code ranges (e.g., legacy API where 400 means \"no results\")\n  - name: \"API_LEGACY\"\n    url: \"https://legacy.example.com/api\"\n    timeout: 10\n    success_codes: [\"200-299\", 400]\n```\n\n**Supported formats:**\n\n| Format | Example | Meaning |\n|--------|---------|---------|\n| Single code | `200` | Exactly status code 200 |\n| Multiple codes | `[200, 201, 202]` | Any of 200, 201, or 202 |\n| Range | `\"200-299\"` | 200 to 299 inclusive |\n| Mixed | `[\"200-299\", 400]` | 2xx range plus 400 |\n\n**Notes:**\n- If `success_codes` is not specified, default range 200-399 is used\n- Status codes must be valid HTTP codes (100-599)\n- Ranges use string format with hyphen: `\"200-299\"`\n\n### TCP Port Monitoring\n\nMonitor non-HTTP services like databases, caches, and custom services by checking TCP port connectivity:\n\n```yaml\ntcp:\n  # Database connectivity check\n  - name: \"DB_PROD\"\n    host: \"db.example.com\"\n    port: 5432\n    timeout: 5\n\n  # Redis cache check\n  - name: \"REDIS\"\n    host: \"redis.example.com\"\n    port: 6379\n    timeout: 3\n```\n\n**How it works:**\n\n| Condition | Result |\n|-----------|--------|\n| TCP connection succeeds | Target marked UP |\n| Connection refused | Target marked DOWN |\n| Connection timeout | Target marked DOWN with timeout message |\n\n**Notes:**\n- TCP checks measure connection establishment time (response_time_ms)\n- No HTTP protocol overhead - faster than HTTP checks\n- Status code is `null` for TCP checks (no HTTP status)\n- URL format in API: `tcp://host:port`\n\n### DNS Resolution Monitoring\n\nMonitor DNS resolution for domain names to detect DNS propagation issues, server failures, or misconfigured records:\n\n```yaml\ndns:\n  # Basic DNS resolution check\n  - name: \"DNS_MAIN\"\n    host: \"example.com\"\n    record_type: A  # A (IPv4) or AAAA (IPv6)\n    timeout: 5\n\n  # Verify resolved IP matches expected value\n  - name: \"DNS_API\"\n    host: \"api.example.com\"\n    record_type: A\n    expected_ip: \"192.0.2.1\"\n```\n\n**How it works:**\n\n| Condition | Result |\n|-----------|--------|\n| DNS resolution succeeds | Target marked UP |\n| Resolution fails | Target marked DOWN with error |\n| Resolved IP doesn't match expected | Target marked DOWN with mismatch message |\n\n**Supported record types:**\n\n| Type | Description |\n|------|-------------|\n| `A` | IPv4 address resolution (default) |\n| `AAAA` | IPv6 address resolution |\n\n**Notes:**\n- DNS checks measure resolution time (response_time_ms)\n- Uses OS DNS resolver (no additional dependencies)\n- URL format in API: `dns://hostname`\n\n### SSL Certificate Monitoring\n\nWebStatusπ automatically monitors SSL certificates for all HTTPS URLs. No additional configuration is required.\n\n**What's monitored:**\n\n- Certificate expiration date\n- Days until expiration (negative if expired)\n- Certificate issuer (e.g., \"Let's Encrypt\")\n- Certificate subject (common name)\n\n**Behavior:**\n\n| Condition | Result |\n|-----------|--------|\n| Certificate valid | Normal monitoring, SSL info in API response |\n| Certificate expires within threshold | Warning logged, URL still marked UP |\n| Certificate expired | URL marked DOWN with error message |\n| SSL extraction fails | URL status unaffected, error stored in `ssl_cert_error` |\n\n**Configuration:**\n\n```yaml\nmonitor:\n  interval: 60\n  ssl_warning_days: 30  # Days before expiration to log warnings (default: 30)\n```\n\n**API Response (SSL fields):**\n\n```json\n{\n  \"name\": \"MY_SITE\",\n  \"ssl_cert_issuer\": \"Let's Encrypt\",\n  \"ssl_cert_subject\": \"example.com\",\n  \"ssl_cert_expires_at\": \"2025-12-31T23:59:59Z\",\n  \"ssl_cert_expires_in_days\": 365,\n  \"ssl_cert_error\": null\n}\n```\n\n**Prometheus metric:**\n\n```prometheus\nwebstatuspi_ssl_cert_expires_in_days{url_name=\"MY_SITE\",url=\"https://example.com\",issuer=\"Let's Encrypt\",subject=\"example.com\"} 365\n```\n\n### Performance Tips\n\nFor Raspberry Pi 1B+:\n\n| Setting | Recommendation |\n|---------|----------------|\n| URLs | 5-10 max |\n| Interval | 30+ seconds |\n| Timeout | 10s or less |\n\n### Environment Variables\n\nOverride any configuration value using environment variables with the `WEBSTATUS_` prefix:\n\n| Variable | Config Path | Example |\n|----------|-------------|---------|\n| `WEBSTATUS_API_PORT` | `api.port` | `8080` |\n| `WEBSTATUS_API_RESET_TOKEN` | `api.reset_token` | `secret123` |\n| `WEBSTATUS_MONITOR_INTERVAL` | `monitor.interval` | `60` |\n| `WEBSTATUS_DATABASE_PATH` | `database.path` | `/data/status.db` |\n| `WEBSTATUS_DATABASE_RETENTION_DAYS` | `database.retention_days` | `7` |\n\nEnvironment variables take precedence over `config.yaml` values.\n\n---\n\n## 🔒 Security Features\n\nWebStatusπ includes several security measures:\n\n| Feature | Description |\n|---------|-------------|\n| **Rate Limiting** | 60 requests/min per IP (localhost exempt) |\n| **SSRF Protection** | Blocks private IPs, localhost, dangerous ports |\n| **CSP Headers** | Content Security Policy with nonces |\n| **Input Validation** | URL names sanitized to prevent injection |\n\n### SSRF Protection\n\nURLs configured for monitoring are validated to prevent Server-Side Request Forgery:\n- Only `http://` and `https://` schemes allowed\n- Private IP ranges blocked (10.x, 172.16.x, 192.168.x, localhost)\n- Internal service ports blocked (22, 3306, 5432, 6379, etc.)\n\n---\n\n## 🛠️ Development\n\n```bash\n# Setup\npython3 -m venv venv\nsource venv/bin/activate\npip install .[dev]\n\n# Run tests\npytest tests/ -v\n\n# Run benchmark (Docker required)\ncd benchmark \u0026\u0026 ./benchmark.sh\n```\n\n### Project Structure\n\n```\nwebstatuspi/\n├── webstatuspi/        # Core package\n│   ├── __init__.py     # CLI entry point\n│   ├── alerter.py      # Webhook alerts\n│   ├── api.py          # HTTP server\n│   ├── config.py       # Configuration\n│   ├── database.py     # SQLite operations\n│   ├── models.py       # Data models\n│   └── monitor.py      # URL checker\n├── tests/              # Test suite\n└── docs/               # Documentation\n```\n\n---\n\n## 🔮 Roadmap\n\n- [ ] 0.96\" OLED display support\n- [ ] Physical button navigation\n- [ ] Buzzer alerts on failures\n- [ ] Status LEDs (green/red)\n\n---\n\n## 🐛 Troubleshooting\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eAPI not accessible from other devices\u003c/strong\u003e\u003c/summary\u003e\n\n1. Check firewall: `sudo ufw allow 8080`\n2. Verify config has `host: 0.0.0.0`\n3. Check Pi IP: `ip addr show`\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eHigh CPU usage\u003c/strong\u003e\u003c/summary\u003e\n\n- Increase polling interval to 60+ seconds\n- Reduce number of monitored URLs\n- Check for slow/timing out URLs\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eConnection timeouts\u003c/strong\u003e\u003c/summary\u003e\n\n- Increase `timeout` value in config\n- Test network: `ping google.com`\n- Test URL manually: `curl -I \u003curl\u003e`\n\n\u003c/details\u003e\n\nSee [Troubleshooting Guide](docs/TROUBLESHOOTING.md) for more.\n\n---\n\n## 📚 Documentation\n\n| Document | Description |\n|----------|-------------|\n| [Architecture](docs/ARCHITECTURE.md) | System design \u0026 database schema |\n| [Hardware](docs/HARDWARE.md) | GPIO pins \u0026 OLED setup |\n| [Telegram Setup](docs/TELEGRAM_SETUP.md) | Push notifications via Telegram |\n| [Contributing](CONTRIBUTING.md) | How to contribute |\n| [Development Rules](AGENTS.md) | Code style \u0026 conventions |\n\n---\n\n## 📄 License\n\nMIT License — see [LICENSE](LICENSE) for details.\n\n---\n\n\u003cp align=\"center\"\u003e\n  \u003cstrong\u003eBuilt with ❤️ for Raspberry Pi enthusiasts\u003c/strong\u003e\u003cbr\u003e\n  \u003cem\u003eLightweight. Reliable. Open Source.\u003c/em\u003e\n\u003c/p\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjmlweb%2Fwebstatuspi","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjmlweb%2Fwebstatuspi","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjmlweb%2Fwebstatuspi/lists"}