{"id":47699143,"url":"https://github.com/davebugg/alertpilot","last_synced_at":"2026-04-02T17:02:06.313Z","repository":{"id":347650185,"uuid":"1194770119","full_name":"DaveBugg/AlertPilot","owner":"DaveBugg","description":"Self-hosted action-on-alert framework — receive events via HTTP, push to any device, execute actions from notification","archived":false,"fork":false,"pushed_at":"2026-03-28T20:12:17.000Z","size":103,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-03-28T21:26:20.950Z","etag":null,"topics":["alerting","devops","docker","fastapi","notification-center","notification-service","notification-system","notifications","ntfy","pwa","self-hosted","self-service"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","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/DaveBugg.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"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":null,"dco":null,"cla":null}},"created_at":"2026-03-28T19:40:54.000Z","updated_at":"2026-03-28T20:12:20.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/DaveBugg/AlertPilot","commit_stats":null,"previous_names":["davebugg/alertpilot"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/DaveBugg/AlertPilot","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DaveBugg%2FAlertPilot","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DaveBugg%2FAlertPilot/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DaveBugg%2FAlertPilot/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DaveBugg%2FAlertPilot/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/DaveBugg","download_url":"https://codeload.github.com/DaveBugg/AlertPilot/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DaveBugg%2FAlertPilot/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31311024,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-02T12:59:32.332Z","status":"ssl_error","status_checked_at":"2026-04-02T12:54:48.875Z","response_time":89,"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","devops","docker","fastapi","notification-center","notification-service","notification-system","notifications","ntfy","pwa","self-hosted","self-service"],"created_at":"2026-04-02T17:01:03.798Z","updated_at":"2026-04-02T17:02:06.301Z","avatar_url":"https://github.com/DaveBugg.png","language":"TypeScript","readme":"# AlertPilot\n\nSelf-hosted action-on-alert framework. Receive events from anything via HTTP, get them on your phone as push notifications, and execute actions directly from the notification — restart a service, scale a container, block an IP, run a runbook — all without opening a terminal.\n\n```\n[Any Source] → HTTP POST → [ntfy] → WebSocket → [PWA]\n                                                    ↓ (tap action button)\n                                              [Runner] → runbook / command → result → [ntfy]\n```\n\n## Why\n\nMost alerting tools are either too heavy (PagerDuty, OpsGenie) or too dumb (just send a message). AlertPilot sits in the middle: lightweight enough to self-host on a $5 VPS, smart enough to let you act on alerts without SSH-ing into anything.\n\n## What it does\n\n- **Receives alerts** from any source that can make an HTTP POST (Grafana, Prometheus, GitHub Actions, cron jobs, your own scripts)\n- **Pushes them** to your phone/desktop as native notifications via a PWA — even when the browser is closed\n- **Lets you act** on alerts with one tap: action buttons appear automatically based on keywords in the alert text\n- **Persists state** — ack, silence, delete alerts. State synced per-user across devices via SQLite on the runner\n- **Replays missed alerts** — ntfy caches messages for 12h, PWA fetches them on reconnect via `?since=`\n\n## Stack\n\n| Component | Tech |\n|-----------|------|\n| **PWA** | React 19, TypeScript, Vite, Tailwind CSS |\n| **Runner** | Python 3.12, FastAPI, SQLite |\n| **Transport** | [ntfy](https://ntfy.sh) (self-hosted) |\n| **Proxy** | Caddy (auto TLS via Let's Encrypt) |\n| **Infra** | Docker Compose |\n\n## Quick start\n\n```bash\ngit clone https://github.com/YOUR_USER/alertpilot.git\ncd alertpilot\ncp .env.example .env\n```\n\nEdit `.env` — at minimum set:\n```env\nDOMAIN=alerts.your.host\nRUNNER_SECRET=your-secret\nNTFY_BOT_TOKEN=tk_...        # generate after first start\nVITE_NTFY_URL=https://alerts.your.host/ntfy\nVITE_RUNNER_URL=https://alerts.your.host/runner\n```\n\n```bash\ndocker compose up -d\n\n# One-time: create ntfy users\ndocker compose exec ntfy ntfy user add --role=admin admin\ndocker compose exec ntfy ntfy user add ops-user\ndocker compose exec ntfy ntfy user add dev-user\ndocker compose exec ntfy ntfy user add bot\n\ndocker compose exec ntfy ntfy access ops-user ops-alerts rw\ndocker compose exec ntfy ntfy access ops-user dev-alerts ro\ndocker compose exec ntfy ntfy access dev-user dev-alerts ro\ndocker compose exec ntfy ntfy access bot ops-alerts wo\ndocker compose exec ntfy ntfy access bot dev-alerts wo\n\n# Generate bot token → paste as NTFY_BOT_TOKEN in .env → docker compose up -d\ndocker compose exec ntfy ntfy token add bot\n```\n\nOpen `https://alerts.your.host` — create your admin account on first visit.\n\n## Local testing (no domain, no TLS)\n\n```bash\ncd test-build\ndocker compose -f docker-compose.test.yml up --build\n```\n\nOpen `http://localhost:8080`. Everything pre-configured, auth open (no tokens needed).\n\n## Sending alerts\n\n```bash\n# Minimal\ncurl -d \"Disk full on db-1\" https://alerts.your.host/ntfy/ops-alerts\n\n# With title and priority (5 = urgent)\ncurl \\\n  -H \"Title: nginx returned 502\" \\\n  -H \"Priority: 5\" \\\n  -H \"Tags: rotating_light\" \\\n  -d \"Backend not responding on prod-1\" \\\n  https://alerts.your.host/ntfy/ops-alerts\n\n# Webhook (Stripe, 3DS, generic)\ncurl -X POST https://alerts.your.host/runner/api/webhook/stripe \\\n  -H \"Content-Type: application/json\" \\\n  -H \"X-Webhook-Secret: your-secret\" \\\n  -d '{\"type\":\"payment_intent.succeeded\",\"data\":{\"object\":{\"amount\":5000}}}'\n```\n\n## Built-in actions (17)\n\n| Category | Actions |\n|----------|---------|\n| **DevOps** | `restart` — restart service, `scale` — set replica count, `silence` — mute alerts |\n| **CI/CD** | `rollback` — revert deployment, `approve_deploy` — approve pipeline, `rerun_pipeline` — retry failed run |\n| **Security** | `block_ip` — add IP to blocklist, `revoke_token` — invalidate API token |\n| **Database** | `db_failover` — promote replica, `db_backup` — trigger backup |\n| **Business** | `notify_oncall` — page on-call person, `pause_campaign` — pause ad/email campaign |\n| **Smart Home** | `lights` — control lights, `thermostat` — set temperature |\n| **Webhooks** | `stripe` — payment events, `tds_code` — 3DS auth codes, `generic_webhook` — catch-all |\n\nActions are **auto-matched** to alerts by keyword triggers. Send an alert containing \"nginx\" and the restart/scale buttons appear automatically.\n\n## Adding your own action\n\nCreate `runner/actions/{category}/{name}.py`:\n\n```python\nfrom runner.core import BaseRunnerAction, ActionContext, ActionResult\n\nclass MyAction(BaseRunnerAction):\n    name = \"my_action\"\n    label = \"Do something to {service}\"\n    category = \"custom\"\n    description = \"One-line description\"\n    triggers = [\"keyword1\", \"keyword2\"]  # auto-attach to matching alerts\n    confirm = True                        # ask user before executing\n    params_schema = {\n        \"service\": {\"type\": \"string\", \"required\": True, \"whitelist\": True},\n    }\n\n    async def execute(self, ctx: ActionContext) -\u003e ActionResult:\n        result = await self.run_runbook_or_default(\n            f\"my_action_{ctx.params['service']}\",\n            fallback=f\"echo 'done'\",\n        )\n        return result\n```\n\nThen `POST /runner/api/reload` or restart the container. No rebuild needed.\n\nOptionally create `runner/runbooks/my_action_nginx.sh` — the framework will find and execute it automatically.\n\n## Architecture\n\n```\ninternet\n   │\n   ▼\n[Caddy :443]\n   ├── /ntfy/   → ntfy:80        (pub/sub, WebSocket, Web Push)\n   ├── /runner/ → runner:8000    (FastAPI actions + auth)\n   └── /        → pwa:80         (React PWA, served by nginx)\n\n[Runner]\n   ├── /api/auth/*     JWT login, setup, TOTP, user management\n   ├── /api/schema     action metadata (PWA loads on startup)\n   ├── /api/action/*   execute action (Bearer JWT required)\n   ├── /api/alerts/*   ack/silence/delete per-user (SQLite)\n   └── /api/webhook/*  receive external webhooks\n```\n\n## Auth \u0026 security\n\n- **JWT** (HS256) with bcrypt passwords. Auto-generated secret on first run.\n- **TOTP / 2FA** — optional per-user, verified at login.\n- **Role-based**: `ops` sees everything + can manage users + run actions. `dev` sees dev-alerts only.\n- **ntfy auth**: `deny-all` by default in production — each user needs their own ntfy token.\n- **Webhook secret**: optional `X-Webhook-Secret` header check via `WEBHOOK_SECRET` env var.\n- **CORS**: configurable via `CORS_ORIGINS` env var (defaults to same-origin).\n\n## Web Push (background notifications)\n\nWhen the app is closed, ntfy can still push alerts to your device via the browser's native push service (no app install needed — PWA + Service Worker).\n\n```bash\n# Generate VAPID keys (one-time)\ndocker compose exec ntfy ntfy webpush keys\n\n# Add to .env and rebuild\nNTFY_WEB_PUSH_PUBLIC_KEY=...\nNTFY_WEB_PUSH_PRIVATE_KEY=...\ndocker compose up -d --build pwa\n```\n\nThen open Settings in the PWA → Enable push notifications.\n\n## Custom port\n\n```env\n# .env\nDOMAIN=alerts.your.host\nWEB_PORT=50443\n```\n\nCaddy will listen on `50443` instead of `443`. Port `80` is always bound for ACME challenges.\n\n## Environment variables\n\n| Variable | Default | Description |\n|----------|---------|-------------|\n| `DOMAIN` | `localhost` | Your domain. Caddy auto-obtains TLS cert. |\n| `WEB_PORT` | `443` | HTTPS port. Leave empty for standard 443. |\n| `RUNNER_SECRET` | `changeme` | Bearer token for machine-to-machine access. |\n| `WEBHOOK_SECRET` | _(empty)_ | Optional secret for webhook endpoint. |\n| `NTFY_BOT_TOKEN` | _(empty)_ | ntfy token for runner → ntfy publishing. |\n| `NTFY_OPS_TOPIC` | `ops-alerts` | Ops team alert topic. |\n| `NTFY_DEV_TOPIC` | `dev-alerts` | Dev team alert topic. |\n| `SERVICE_WHITELIST` | `nginx,api,...` | Services restart/scale can touch. |\n| `ACTION_TIMEOUT` | `30` | Max seconds for action execution. |\n| `VITE_NTFY_URL` | _(empty)_ | Baked into PWA — ntfy public URL. |\n| `VITE_RUNNER_URL` | _(empty)_ | Baked into PWA — runner public URL. |\n| `JWT_SECRET` | _(auto)_ | JWT signing key. Auto-generated if empty. |\n| `JWT_EXPIRY_DAYS` | `30` | Session duration. |\n| `CORS_ORIGINS` | `*` | Comma-separated allowed origins. |\n\n## Development with Claude Code\n\nThe repo includes a `CLAUDE.md` file — a persistent instruction set for [Claude Code](https://claude.ai/code) (the AI coding assistant). It describes the full project architecture, conventions, and step-by-step guides for common tasks.\n\nIf you use Claude Code, it will automatically load `CLAUDE.md` at the start of every session and understand:\n- How to add a new action (template + validation rules)\n- How to add a PWA feature (folder structure, patterns)\n- All API endpoints and their auth requirements\n- Docker / Caddy / ntfy configuration decisions\n\nUseful when you want to customize AlertPilot for your own setup — add actions for your specific services, integrate with your own tools, or extend the PWA — without needing to re-explain the codebase each time.\n\n## License\n\nMIT\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdavebugg%2Falertpilot","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdavebugg%2Falertpilot","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdavebugg%2Falertpilot/lists"}