Topbar (avatar, network indicator) was hidden behind iOS status bar in standalone PWA mode. Added safe-area-inset-top padding to topbar. Disabled user-scalable to prevent accidental zoom. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> |
||
|---|---|---|
| backend | ||
| deploy | ||
| docs | ||
| frontend | ||
| nginx | ||
| tests | ||
| .dockerignore | ||
| .env.example | ||
| .gitignore | ||
| .pre-commit-config.yaml | ||
| =2.0.0 | ||
| ARCHITECTURE.md | ||
| docker-compose.yml | ||
| Dockerfile | ||
| pytest.ini | ||
| README.md | ||
| requirements-dev.txt | ||
| requirements.txt | ||
Baton — Экстренный сигнал
PWA-приложение для отправки экстренных сигналов с геолокацией через Telegram-бота.
Стек
- Backend: Python 3.12+, FastAPI, aiosqlite, httpx
- Frontend: Vanilla JS PWA (Service Worker, Web Push)
- База данных: SQLite (WAL mode)
- Уведомления: Telegram Bot API
Запуск
# Зависимости
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 и подобных) приложение засыпает после периода неактивности (обычно 15–30 минут). Следующий входящий запрос ждёт пока процесс поднимется заново — cold start занимает 3–5 секунд. Для экстренного приложения это критично.
Решения по вариантам хостинга
| Вариант | Стоимость | Cold start | Рекомендация |
|---|---|---|---|
| fly.io Hobby | $5/мес | Нет (всегда активен) | Оптимально для прода |
| fly.io free tier | Бесплатно | 3–5 сек | Только для разработки |
| Render free | Бесплатно | 3–5 сек | Только для разработки |
| Самохостинг (VPS) | От $3–5/мес | Нет | Полный контроль |
Финальный выбор хостинга зависит от решения по OQ-004 (открытый вопрос по бюджету и масштабированию проекта).
Keep-alive механизм (asyncio background task)
Приложение запускает фоновый asyncio-таск, который каждые 10 минут пингует собственный /health endpoint. Это предотвращает засыпание на платформах, которые реагируют на активность процесса.
Активация: установите переменную APP_URL:
APP_URL=https://your-app.fly.dev
Без APP_URL таск не запускается (keep-alive отключён).
Ограничение: self-ping работает пока процесс жив. Если платформа убивает процесс при нулевом трафике — нужен внешний пингер (см. ниже).
Keep-alive для самохостинга (cron / systemd timer)
Если приложение на VPS и нужен мониторинг извне:
Вариант 1 — crontab:
# Редактируем cron
crontab -e
# Добавляем запись (каждые 10 минут):
*/10 * * * * curl -sf https://your-app.example.com/health > /dev/null
Вариант 2 — systemd timer:
Создайте два файла:
/etc/systemd/system/baton-keepalive.service:
[Unit]
Description=Baton keep-alive ping
[Service]
Type=oneshot
ExecStart=curl -sf https://your-app.example.com/health
/etc/systemd/system/baton-keepalive.timer:
[Unit]
Description=Run Baton keep-alive every 10 minutes
[Timer]
OnBootSec=1min
OnUnitActiveSec=10min
[Install]
WantedBy=timers.target
Готовые файлы находятся в deploy/:
# 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 — бесплатный сервис мониторинга, который пингует ваш /health снаружи каждые 5 минут. В отличие от self-ping, он работает даже если платформа убила процесс.
Настройка (бесплатно, без регистрации кредитной карты):
- Зарегистрируйтесь на uptimerobot.com
- Add New Monitor → тип HTTP(s)
- Заполните:
- Friendly Name:
Baton Health - URL:
https://your-app.example.com/health - Monitoring Interval:
5 minutes
- Friendly Name:
- Сохраните. UptimeRobot начнёт пинговать каждые 5 минут и пришлёт email при падении.
Плюсы: работает независимо от хостинга, бесплатно до 50 мониторов, email/Telegram-уведомления. Минусы: требует публичный URL (для локальной разработки не подходит).
Рекомендация: для прода используйте UptimeRobot как внешний watchdog + self-ping (APP_URL) как запасной вариант.
Nginx deployment
Для проксирования через nginx используйте готовый шаблон nginx/baton.conf.
Применение
# 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.
Тесты
pip install -r requirements-dev.txt
pytest