baton/README.md

196 lines
8.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Baton — Экстренный сигнал
PWA-приложение для отправки экстренных сигналов с геолокацией через Telegram-бота.
## Стек
- **Backend:** Python 3.12+, FastAPI, aiosqlite, httpx
- **Frontend:** Vanilla JS PWA (Service Worker, Web Push)
- **База данных:** SQLite (WAL mode)
- **Уведомления:** Telegram Bot API
## Запуск
```bash
# Зависимости
pip install -r requirements.txt
# Переменные окружения (см. .env.example)
cp .env.example .env
# Запуск
uvicorn backend.main:app --host 0.0.0.0 --port 8000
```
## Переменные окружения
| Переменная | Обязательна | Описание |
|---|---|---|
| `BOT_TOKEN` | да | Токен Telegram-бота |
| `CHAT_ID` | да | ID чата для уведомлений |
| `WEBHOOK_SECRET` | да | Секрет для верификации Telegram webhook |
| `WEBHOOK_URL` | да | Публичный URL `/api/webhook/telegram` |
| `DB_PATH` | нет | Путь к SQLite-файлу (по умолчанию `baton.db`) |
| `FRONTEND_ORIGIN` | нет | Разрешённый origin для CORS (по умолчанию `http://localhost:3000`) |
| `WEBHOOK_ENABLED` | нет | Регистрировать webhook при старте (по умолчанию `true`) |
| `APP_URL` | нет | Публичный URL приложения для keep-alive (например `https://baton.fly.dev`) |
## API
| Метод | Путь | Описание |
|---|---|---|
| `GET` | `/health` | Health check: `{"status": "ok", "timestamp": <unix_ts>}` |
| `POST` | `/api/register` | Регистрация пользователя |
| `POST` | `/api/signal` | Отправка экстренного сигнала |
| `POST` | `/api/webhook/telegram` | Telegram webhook |
## Hosting & Keep-Alive
### Проблема cold start
На бесплатных хостингах (Render, fly.io free tier, Railway и подобных) приложение **засыпает** после периода неактивности (обычно 1530 минут). Следующий входящий запрос ждёт пока процесс поднимется заново — **cold start занимает 35 секунд**. Для экстренного приложения это критично.
### Решения по вариантам хостинга
| Вариант | Стоимость | Cold start | Рекомендация |
|---|---|---|---|
| **fly.io Hobby** | $5/мес | Нет (всегда активен) | Оптимально для прода |
| **fly.io free tier** | Бесплатно | 35 сек | Только для разработки |
| **Render free** | Бесплатно | 35 сек | Только для разработки |
| **Самохостинг (VPS)** | От $35/мес | Нет | Полный контроль |
> **Финальный выбор хостинга зависит от решения по OQ-004** (открытый вопрос по бюджету и масштабированию проекта).
### Keep-alive механизм (asyncio background task)
Приложение запускает фоновый asyncio-таск, который каждые **10 минут** пингует собственный `/health` endpoint. Это предотвращает засыпание на платформах, которые реагируют на активность процесса.
**Активация:** установите переменную `APP_URL`:
```bash
APP_URL=https://your-app.fly.dev
```
Без `APP_URL` таск не запускается (keep-alive отключён).
**Ограничение:** self-ping работает пока процесс жив. Если платформа убивает процесс при нулевом трафике — нужен внешний пингер (см. ниже).
### Keep-alive для самохостинга (cron / systemd timer)
Если приложение на VPS и нужен мониторинг извне:
**Вариант 1 — crontab:**
```bash
# Редактируем cron
crontab -e
# Добавляем запись (каждые 10 минут):
*/10 * * * * curl -sf https://your-app.example.com/health > /dev/null
```
**Вариант 2 — systemd timer:**
Создайте два файла:
`/etc/systemd/system/baton-keepalive.service`:
```ini
[Unit]
Description=Baton keep-alive ping
[Service]
Type=oneshot
ExecStart=curl -sf https://your-app.example.com/health
```
`/etc/systemd/system/baton-keepalive.timer`:
```ini
[Unit]
Description=Run Baton keep-alive every 10 minutes
[Timer]
OnBootSec=1min
OnUnitActiveSec=10min
[Install]
WantedBy=timers.target
```
Готовые файлы находятся в `deploy/`:
```bash
# 1. Скопировать файлы
sudo cp deploy/baton-keepalive.service /etc/systemd/system/
sudo cp deploy/baton-keepalive.timer /etc/systemd/system/
# 2. Заменить URL на реальный
sudo sed -i 's|https://your-app.example.com|https://YOUR_APP_URL|g' \
/etc/systemd/system/baton-keepalive.service
# 3. Включить и запустить
sudo systemctl daemon-reload
sudo systemctl enable --now baton-keepalive.timer
# 4. Проверить
systemctl list-timers baton-keepalive.timer
```
### Keep-alive через UptimeRobot (внешний сервис, рекомендуется)
[UptimeRobot](https://uptimerobot.com) — бесплатный сервис мониторинга, который пингует ваш `/health` снаружи каждые 5 минут. В отличие от self-ping, он работает даже если платформа убила процесс.
**Настройка (бесплатно, без регистрации кредитной карты):**
1. Зарегистрируйтесь на [uptimerobot.com](https://uptimerobot.com)
2. **Add New Monitor** → тип **HTTP(s)**
3. Заполните:
- **Friendly Name:** `Baton Health`
- **URL:** `https://your-app.example.com/health`
- **Monitoring Interval:** `5 minutes`
4. Сохраните. UptimeRobot начнёт пинговать каждые 5 минут и пришлёт email при падении.
**Плюсы:** работает независимо от хостинга, бесплатно до 50 мониторов, email/Telegram-уведомления.
**Минусы:** требует публичный URL (для локальной разработки не подходит).
> **Рекомендация:** для прода используйте UptimeRobot как внешний watchdog + self-ping (APP_URL) как запасной вариант.
## Nginx deployment
Для проксирования через nginx используйте готовый шаблон `nginx/baton.conf`.
### Применение
```bash
# 1. Скопировать шаблон и заменить домен
sudo cp nginx/baton.conf /etc/nginx/sites-available/baton
sudo sed -i 's/<YOUR_DOMAIN>/baton.example.com/g' /etc/nginx/sites-available/baton
# 2. Получить TLS-сертификат (если ещё нет)
sudo certbot certonly --nginx -d baton.example.com
# 3. Включить конфиг
sudo ln -s /etc/nginx/sites-available/baton /etc/nginx/sites-enabled/baton
# 4. Проверить и применить
sudo nginx -t && sudo systemctl reload nginx
```
### Защита BOT_TOKEN в логах
Конфиг включает `map`-блок, который автоматически маскирует токен бота в `access_log`:
```
# В логе вместо реального токена:
GET /bot<TOKEN>/sendMessage → GET /bot[REDACTED]/sendMessage
```
Это защита по принципу «defence in depth»: текущий webhook-эндпоинт (`/api/webhook/telegram`) токен в URL не содержит, но маскировка сработает, если в будущем появится маршрут вида `/bot<TOKEN>/...`.
Заголовок `X-Telegram-Bot-Api-Secret-Token` не попадает в `access_log` — nginx не логирует заголовки запросов в стандартном `log_format`.
## Тесты
```bash
pip install -r requirements-dev.txt
pytest
```