feat: Docker, health check, schema migrations y webhook opt-in
- migrations/001_initial_schema.sql: DDL idempotente con CREATE TABLE/INDEX IF NOT EXISTS - database.py: run_migrations() aplica .sql pendientes en orden; init_db() queda como alias - health.py: servidor aiohttp en puerto 8080 — GET /health, GET /health/history, POST /webhook Watcher loop alerta al admin por Telegram si el poller no actualiza en N horas - config.py: webhook_url, webhook_secret, health_port, health_alert_hours - poller.py: on_poll_done callback (conecta con health.record_poll) - main.py: arranca HealthServer, llama run_migrations, modo webhook opt-in via WEBHOOK_URL - Dockerfile + docker-compose.yml + .dockerignore - pyproject.toml: aiohttp>=3.9 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
58
main.py
58
main.py
@@ -1,8 +1,8 @@
|
||||
"""
|
||||
main.py — Entrypoint de Pedrito Hechakuaa.
|
||||
|
||||
Arranca el bot de Telegram y el poller en paralelo.
|
||||
Al arrancar: inicializa la DB y migra el tenant principal del .env si no existe.
|
||||
Arranca el bot de Telegram, el poller y el servidor HTTP de health check en paralelo.
|
||||
Al arrancar: aplica migraciones y migra el tenant principal del .env si no existe.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -13,9 +13,11 @@ import sys
|
||||
import structlog
|
||||
from telegram.ext import Application
|
||||
|
||||
import health as health_mod
|
||||
import vault as vault_mod
|
||||
from config import get_settings
|
||||
from database import get_tenant, init_db, save_tenant
|
||||
from database import get_tenant, run_migrations, save_tenant
|
||||
from health import HealthServer, record_poll
|
||||
from onboarding import OnboardingFlow
|
||||
from poller import Poller
|
||||
from telegram_bot import PedritoBot
|
||||
@@ -77,10 +79,11 @@ async def main() -> None:
|
||||
poll_interval_minutes=settings.poll_interval_minutes,
|
||||
horario=f"{settings.poll_hora_inicio}:00-{settings.poll_hora_fin}:00",
|
||||
chat_id=settings.telegram_chat_id,
|
||||
webhook_mode=bool(settings.webhook_url),
|
||||
)
|
||||
|
||||
# Base de datos
|
||||
await init_db(settings.db_path)
|
||||
# Migraciones y tenant principal
|
||||
await run_migrations(settings.db_path)
|
||||
await _migrar_tenant_principal(settings)
|
||||
|
||||
# Bot de Telegram
|
||||
@@ -92,19 +95,20 @@ async def main() -> None:
|
||||
)
|
||||
app: Application = pedrito_bot.build()
|
||||
|
||||
# Poller (sin snapshot global — maneja uno por tenant internamente)
|
||||
# Poller
|
||||
poller = Poller(
|
||||
settings=settings,
|
||||
bot=pedrito_bot,
|
||||
on_poll_done=record_poll,
|
||||
)
|
||||
|
||||
# El callback de /notif pasa el chat_id al poller
|
||||
pedrito_bot.set_notif_callback(
|
||||
lambda chat_id: poller.revisar_ahora(chat_id=chat_id, es_manual=True)
|
||||
)
|
||||
|
||||
# Manejo de señales para shutdown limpio
|
||||
loop = asyncio.get_running_loop()
|
||||
health_server: HealthServer | None = None
|
||||
|
||||
def handle_shutdown(sig: int) -> None:
|
||||
logger.info("shutdown_recibido", signal=sig)
|
||||
@@ -117,26 +121,52 @@ async def main() -> None:
|
||||
except NotImplementedError:
|
||||
pass # Windows no soporta add_signal_handler
|
||||
|
||||
# Arrancar bot y poller en paralelo
|
||||
async with app:
|
||||
await app.initialize()
|
||||
await app.start()
|
||||
|
||||
logger.info("bot_telegram_iniciado")
|
||||
|
||||
telegram_task = asyncio.create_task(
|
||||
app.updater.start_polling(drop_pending_updates=True)
|
||||
)
|
||||
# Servidor HTTP de health check (y webhook si está configurado)
|
||||
health_server = HealthServer(settings=settings, application=app)
|
||||
await health_server.start()
|
||||
|
||||
if settings.webhook_url:
|
||||
# Registrar el webhook con Telegram
|
||||
webhook_url = f"{settings.webhook_url.rstrip('/')}/webhook"
|
||||
await app.bot.set_webhook(
|
||||
url=webhook_url,
|
||||
secret_token=settings.webhook_secret or None,
|
||||
drop_pending_updates=True,
|
||||
)
|
||||
logger.info("webhook_registrado", url=webhook_url)
|
||||
telegram_task = asyncio.create_task(asyncio.sleep(0)) # placeholder
|
||||
else:
|
||||
# Long-polling
|
||||
telegram_task = asyncio.create_task(
|
||||
app.updater.start_polling(drop_pending_updates=True)
|
||||
)
|
||||
|
||||
poller_task = asyncio.create_task(poller.run_forever())
|
||||
|
||||
logger.info("pedrito_hechakuaa_corriendo")
|
||||
|
||||
try:
|
||||
await asyncio.gather(telegram_task, poller_task)
|
||||
if settings.webhook_url:
|
||||
await poller_task
|
||||
else:
|
||||
await asyncio.gather(telegram_task, poller_task)
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
finally:
|
||||
await app.updater.stop()
|
||||
if health_server:
|
||||
await health_server.stop()
|
||||
if settings.webhook_url:
|
||||
try:
|
||||
await app.bot.delete_webhook()
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
await app.updater.stop()
|
||||
await app.stop()
|
||||
|
||||
logger.info("pedrito_hechakuaa_detenido")
|
||||
|
||||
Reference in New Issue
Block a user