- QUICKSTART.md: replace data_description.md.example copy step with note that tables are registered via the admin API or web UI - NOTIFICATIONS.md: replace examples/ section with planned-feature note - telegram_bot.md: remove examples/notifications/ rows from deployment table and example scripts section; note feature is planned - dev_docs/README.md: remove plan-corporate-memory.md entry - duckdb_manager.py: update comment from remote_query.py to query API endpoint
246 lines
9.1 KiB
Markdown
246 lines
9.1 KiB
Markdown
# Telegram Notification Bot
|
|
|
|
Technical documentation for the notification engine (Phase 3, Issue #41).
|
|
|
|
## Architecture
|
|
|
|
```
|
|
┌─ Web Dashboard (Flask) ─────────────────────┐
|
|
│ "Telegram Notifications" section │
|
|
│ POST /api/telegram/verify │
|
|
│ POST /api/telegram/unlink │
|
|
│ GET /api/telegram/status │
|
|
│ Reads/writes: /data/notifications/*.json │
|
|
└──────────────────────────────────────────────┘
|
|
|
|
┌─ Telegram Bot Service (systemd) ────────────┐
|
|
│ Telegram polling (handles /start command) │
|
|
│ HTTP server on unix socket (send API) │
|
|
│ Reads/writes: /data/notifications/*.json │
|
|
└──────────────────────────────────────────────┘
|
|
▲ unix socket
|
|
│ /data/notifications/bot.sock
|
|
┌───────┴──────────────────────────────────────┐
|
|
│ notify-runner (user crontab) │
|
|
│ Runs ~/user/notifications/*.py │
|
|
│ Sends results via socket to bot │
|
|
└──────────────────────────────────────────────┘
|
|
```
|
|
|
|
## Components
|
|
|
|
### 1. Telegram Bot Service
|
|
|
|
**Source:** `services/telegram_bot/`
|
|
|
|
| File | Purpose |
|
|
|------|---------|
|
|
| `bot.py` | Main entry point - asyncio loop running polling + HTTP server |
|
|
| `config.py` | Configuration constants (paths, TTLs, limits) |
|
|
| `storage.py` | JSON file read/write for user mappings and verification codes |
|
|
| `sender.py` | Telegram Bot API calls (sendMessage, sendPhoto, getUpdates) |
|
|
| `status.py` | Script listing via `notify-scripts list` helper |
|
|
| `runner.py` | Script execution via `notify-scripts run` helper |
|
|
| `dispatch.py` | WebSocket gateway dispatch for desktop app notifications |
|
|
| `__main__.py` | Allows `python -m services.telegram_bot` |
|
|
|
|
**Bot behavior (English):**
|
|
- `/start` -> generates 6-digit verification code, valid 10 minutes
|
|
- Unknown commands -> "Use /start to link your account."
|
|
|
|
**Send API (unix socket):**
|
|
- `POST /send` - send text message (`user`, `text`, `parse_mode`)
|
|
- `POST /send_photo` - send photo with caption (`user`, `photo_path`, `caption`)
|
|
- `GET /health` - health check
|
|
|
|
**Systemd service:** `services/telegram_bot/systemd/notify-bot.service`
|
|
- User: `deploy`, Group: `data-ops`
|
|
- EnvironmentFile: `/opt/data-analyst/.env` (contains `TELEGRAM_BOT_TOKEN`)
|
|
- Restarts automatically on failure
|
|
|
|
### 2. Web Dashboard Changes
|
|
|
|
**Modified files:**
|
|
|
|
| File | Changes |
|
|
|------|---------|
|
|
| `webapp/app.py` | Added 3 API endpoints + `telegram_status` in dashboard context |
|
|
| `webapp/telegram_service.py` | New file - verify/unlink/status logic using shared JSON files |
|
|
| `webapp/templates/dashboard.html` | New "Telegram Notifications" card with verify/unlink UI |
|
|
|
|
**API endpoints:**
|
|
|
|
| Endpoint | Method | Description |
|
|
|----------|--------|-------------|
|
|
| `/api/telegram/verify` | POST | Verify code, link Telegram (`{"code": "123456"}`) |
|
|
| `/api/telegram/unlink` | POST | Unlink Telegram account |
|
|
| `/api/telegram/status` | GET | Get link status (`{"linked": true, "linked_at": "..."}`) |
|
|
|
|
All endpoints require login (Google SSO).
|
|
|
|
### 3. Notify Runner
|
|
|
|
**Source:** `server/bin/notify-runner`
|
|
|
|
Installed to `/usr/local/bin/notify-runner`. Users set up their own crontab.
|
|
|
|
**What it does:**
|
|
1. Finds `~/user/notifications/*.py`
|
|
2. For each script:
|
|
- Checks cooldown state (`~/.notifications/state/{name}.json`)
|
|
- Runs subprocess with 60s timeout
|
|
- Parses stdout JSON
|
|
- If `notify: true` and cooldown OK: sends via bot socket
|
|
- Updates cooldown state
|
|
3. Logs to `~/.notifications/logs/runner.log`
|
|
|
|
**Dependencies:** `httpx` (for unix socket HTTP client)
|
|
|
|
### 4. Example Scripts
|
|
|
|
Note: notification example scripts have been removed. This feature is planned for a future release.
|
|
|
|
## Data Storage
|
|
|
|
All notification data lives on the `/data` disk (backupable):
|
|
|
|
```
|
|
/data/notifications/ # owner: deploy, group: data-ops, mode: 770
|
|
├── telegram_users.json # username -> {chat_id, linked_at}
|
|
├── pending_codes.json # code -> {chat_id, created_at}
|
|
└── bot.log # bot service log
|
|
```
|
|
|
|
Per-user state:
|
|
```
|
|
~/.notifications/
|
|
├── state/ # cooldown state per script
|
|
│ └── {script_name}.json # {"last_sent": unix_timestamp}
|
|
└── logs/
|
|
├── runner.log # notify-runner log
|
|
└── cron.log # crontab output
|
|
```
|
|
|
|
## User Flow: Link Telegram
|
|
|
|
1. User logs in on dashboard (Google SSO)
|
|
2. Sees "Telegram Notifications" card
|
|
3. Messages `/start` to @YourBot
|
|
4. Bot replies with 6-digit code (valid 10 min)
|
|
5. User enters code on dashboard, clicks Verify
|
|
6. Webapp verifies code against `pending_codes.json`, saves mapping to `telegram_users.json`
|
|
7. Dashboard shows "Linked" status
|
|
|
|
## Notification Script Contract
|
|
|
|
Scripts output JSON to stdout:
|
|
|
|
```json
|
|
{
|
|
"notify": true,
|
|
"title": "Revenue dropped 25%",
|
|
"message": "Today: $45k | 7d avg: $60k",
|
|
"image_path": "/tmp/chart.png",
|
|
"cooldown": "1h"
|
|
}
|
|
```
|
|
|
|
| Field | Required | Type | Description |
|
|
|-------|----------|------|-------------|
|
|
| `notify` | yes | bool | Send notification? |
|
|
| `title` | no | string | Bold header in Telegram |
|
|
| `message` | if notify=true | string | Message body (Markdown) |
|
|
| `image_path` | no | string | Absolute path to PNG/JPG |
|
|
| `cooldown` | no | string | `30m`, `1h`, `6h`, `1d` (default: `1h`) |
|
|
| `data` | no | object | Structured data (for future use) |
|
|
|
|
## Deployment
|
|
|
|
### Files deployed by `deploy.sh`
|
|
|
|
| Source | Destination |
|
|
|--------|-------------|
|
|
| `server/bin/notify-scripts` | `/usr/local/bin/notify-scripts` |
|
|
| `server/bin/notify-runner` | `/usr/local/bin/notify-runner` |
|
|
| `services/telegram_bot/systemd/notify-bot.service` | `/etc/systemd/system/notify-bot.service` |
|
|
| `docs/notifications.md` | `/data/docs/notifications.md` |
|
|
|
|
### Changes to existing deploy scripts
|
|
|
|
| File | Changes |
|
|
|------|---------|
|
|
| `server/deploy.sh` | Deploys runner, creates `/data/notifications/`, manages notify-bot service, deploys examples, includes `TELEGRAM_BOT_TOKEN` in .env |
|
|
| `server/bin/add-analyst` | Creates `~/.notifications/{state,logs}` for new users |
|
|
| `server/sudoers-deploy` | Added permissions for notify-bot service, notifications dir, examples |
|
|
| `.github/workflows/deploy.yml` | Added `TELEGRAM_BOT_TOKEN` secret to deploy env |
|
|
| `requirements.txt` | Added `httpx>=0.27.0`, `aiohttp>=3.9.0` |
|
|
|
|
### GitHub Secrets
|
|
|
|
New secret required:
|
|
|
|
| Secret | Description |
|
|
|--------|-------------|
|
|
| `TELEGRAM_BOT_TOKEN` | Bot API token from @BotFather |
|
|
|
|
### First-time setup
|
|
|
|
1. Create bot via [@BotFather](https://t.me/BotFather) on Telegram
|
|
2. Copy the bot token
|
|
3. Add `TELEGRAM_BOT_TOKEN` to GitHub repository secrets
|
|
4. Deploy (push to main or manual trigger)
|
|
5. Verify bot service is running: `sudo systemctl status notify-bot`
|
|
|
|
## Security
|
|
|
|
| Aspect | Implementation |
|
|
|--------|---------------|
|
|
| Bot token | Central, stored as GitHub Secret, deployed to server `.env` |
|
|
| User access to token | None - users communicate via unix socket only |
|
|
| Socket permissions | `deploy:data-ops`, mode `0660` |
|
|
| Script execution (crontab) | Runs under user's own account (no sudo) |
|
|
| Script execution (on-demand) | Via `notify-scripts` helper: `sudo -u <user> /usr/local/bin/notify-scripts run` -- services never access user home directories directly |
|
|
| Script timeout | 60 seconds (enforced by `notify-scripts` helper) |
|
|
| Home directory isolation | User homes are `750`, services use `notify-scripts` running as target user |
|
|
| Verification codes | Expire after 10 minutes, single-use |
|
|
| Telegram data | Stored on `/data` disk (backupable) |
|
|
|
|
## Monitoring
|
|
|
|
```bash
|
|
# Bot service status
|
|
sudo systemctl status notify-bot
|
|
|
|
# Bot logs
|
|
tail -f /data/notifications/bot.log
|
|
|
|
# Runner logs (per user)
|
|
tail -f ~/.notifications/logs/runner.log
|
|
tail -f ~/.notifications/logs/cron.log
|
|
|
|
# Linked users
|
|
cat /data/notifications/telegram_users.json | python3 -m json.tool
|
|
```
|
|
|
|
## Troubleshooting
|
|
|
|
**Bot not responding to /start:**
|
|
- Check service: `sudo systemctl status notify-bot`
|
|
- Check token: `grep TELEGRAM_BOT_TOKEN /opt/data-analyst/.env`
|
|
- Check logs: `tail -50 /data/notifications/bot.log`
|
|
|
|
**Verification code not working:**
|
|
- Codes expire after 10 minutes
|
|
- Each `/start` invalidates previous codes for that chat
|
|
- Check `cat /data/notifications/pending_codes.json`
|
|
|
|
**Runner not sending notifications:**
|
|
- Check socket exists: `ls -la /data/notifications/bot.sock`
|
|
- Check user is linked: `cat /data/notifications/telegram_users.json`
|
|
- Check cooldown: `cat ~/.notifications/state/{script_name}.json`
|
|
- Run manually: `/usr/local/bin/notify-runner`
|
|
|
|
**Permission denied on socket:**
|
|
- User must be in `dataread` group (all analysts are)
|
|
- Socket ownership: `deploy:data-ops`, mode `0660`
|
|
- Verify groups: `groups $(whoami)`
|