- 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
9.1 KiB
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(containsTELEGRAM_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:
- Finds
~/user/notifications/*.py - For each script:
- Checks cooldown state (
~/.notifications/state/{name}.json) - Runs subprocess with 60s timeout
- Parses stdout JSON
- If
notify: trueand cooldown OK: sends via bot socket - Updates cooldown state
- Checks cooldown state (
- 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
- User logs in on dashboard (Google SSO)
- Sees "Telegram Notifications" card
- Messages
/startto @YourBot - Bot replies with 6-digit code (valid 10 min)
- User enters code on dashboard, clicks Verify
- Webapp verifies code against
pending_codes.json, saves mapping totelegram_users.json - Dashboard shows "Linked" status
Notification Script Contract
Scripts output JSON to stdout:
{
"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
- Create bot via @BotFather on Telegram
- Copy the bot token
- Add
TELEGRAM_BOT_TOKENto GitHub repository secrets - Deploy (push to main or manual trigger)
- 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
# 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
/startinvalidates 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
datareadgroup (all analysts are) - Socket ownership:
deploy:data-ops, mode0660 - Verify groups:
groups $(whoami)