# Guía de despliegue — Pedrito Hechakuaa en VPS Ubuntu > **Destinatario:** equipo técnico de InQuality > **Entorno objetivo:** VPS Ubuntu 22.04 en Hostinger — producción > **Tiempo estimado:** 60–90 minutos (primera vez) > **Acceso requerido:** SSH root al VPS + datos de configuración provistos por Marcos > **Subdominio objetivo:** `pedrito.inqualityhq.com` --- ## Índice **PARTE A — Preparación (antes de tocar el servidor)** 1. [Datos que necesitás antes de empezar](#1-datos-que-necesitás-antes-de-empezar) 2. [Configurar DNS en Bluehost](#2-configurar-dns-en-bluehost) **PARTE B — Servidor: base** 3. [Conectarse al VPS](#3-conectarse-al-vps) 4. [Actualizar el sistema y crear usuario](#4-actualizar-el-sistema-y-crear-usuario) 5. [Instalar Docker](#5-instalar-docker) 6. [Instalar Nginx](#6-instalar-nginx) 7. [Clonar el repositorio](#7-clonar-el-repositorio) 8. [Configurar el archivo .env](#8-configurar-el-archivo-env) 9. [Cifrar las credenciales del PJ](#9-cifrar-las-credenciales-del-pj) **PARTE C — HTTPS y producción** 10. [Obtener certificado SSL con Certbot](#10-obtener-certificado-ssl-con-certbot) 11. [Configurar Nginx como reverse proxy](#11-configurar-nginx-como-reverse-proxy) 12. [Configurar el firewall](#12-configurar-el-firewall) 13. [Iniciar el bot con Docker](#13-iniciar-el-bot-con-docker) 14. [Activar el modo webhook](#14-activar-el-modo-webhook) **PARTE D — Operación** 15. [Verificación final](#15-verificación-final) 16. [Monitoreo y logs](#16-monitoreo-y-logs) 17. [Health check HTTP](#17-health-check-http) 18. [Cómo actualizar el bot](#18-cómo-actualizar-el-bot) 19. [Troubleshooting](#19-troubleshooting) 20. [Referencia rápida de comandos](#20-referencia-rápida-de-comandos) --- ## 1. Datos que necesitás antes de empezar Antes de conectarte al VPS, juntá estos datos. Sin alguno de ellos el deploy no puede completarse. | # | Dato | Quién lo tiene | Dónde va | |---|------|---------------|----------| | 1 | `FERNET_KEY` (clave de cifrado) | Marcos la genera | `.env` | | 2 | `TELEGRAM_BOT_TOKEN` | Marcos (de @BotFather) | `.env` | | 3 | `TELEGRAM_CHAT_ID` | Marcos (de @userinfobot) | `.env` | | 4 | `TELEGRAM_EXTRA_IDS` | Marcos (chat IDs de otros abogados, separados por coma) | `.env` | | 5 | `INVITE_CODE` | Marcos lo decide | `.env` | | 6 | Cédula del PJ | Marcos | Script de cifrado | | 7 | Contraseña del PJ | Marcos | Script de cifrado | | 8 | IP pública del VPS | Panel de Hostinger | DNS + SSH | | 9 | Credenciales SSH del VPS | Panel de Hostinger | Terminal | > **Tip:** Pedile a Marcos que te envíe los datos 1-5 por un canal seguro (Signal, Bitwarden Send, etc.). > Nunca por email ni WhatsApp. > ⚠️ **Seguridad crítica:** Nunca guardés la contraseña del PJ (dato 7) en ningún archivo, chat, > nota, ni Post-it. El script del paso 9 la pide en pantalla, la descarta de memoria y guarda > solo el resultado cifrado. Una vez que el script termina exitosamente, podés olvidarla. --- ## 2. Configurar DNS en Bluehost El bot va a ser accesible en `pedrito.inqualityhq.com`. Para eso necesitamos apuntar ese subdominio a la IP del VPS con un registro DNS tipo A. **Necesitás:** La IP pública del VPS (Panel de Hostinger → tu VPS → ver IP). --- ### Opción A: Hacerlo vos mismo en Bluehost cPanel 1. Entrá a [bluehost.com](https://www.bluehost.com) con las credenciales de InQuality 2. En el panel, buscá **"cPanel"** y hacé click 3. Dentro de cPanel, en la sección **"Domains"**, hacé click en **"Zone Editor"** (también puede aparecer como **"DNS Zone Editor"** o **"Advanced DNS Editor"**) 4. Buscá `inqualityhq.com` en la lista y hacé click en **"Manage"** 5. Hacé click en **"Add Record"** 6. Completá el formulario así: | Campo | Valor | |-------|-------| | **Name** (o Host) | `pedrito` | | **Type** | `A` | | **TTL** | `300` (o el mínimo disponible) | | **Value** (o Points To) | `` | 7. Hacé click en **"Save"** o **"Add Record"** 8. Para verificar que se propagó (puede tardar de 5 minutos a 2 horas): ```bash # Desde tu computadora local nslookup pedrito.inqualityhq.com # Debe mostrar la IP del VPS en la respuesta # O también: dig pedrito.inqualityhq.com A +short # Debe retornar solo la IP del VPS ``` > **Tip:** Si `nslookup` aún no muestra la IP, esperá 10-15 minutos y volvé a intentar. > La propagación en Bluehost suele ser rápida, pero a veces tarda más. --- ### Opción B: Mensaje exacto para pedirle a un asistente de Bluehost (en inglés) Si necesitás que un asistente del equipo (que tenga acceso al cPanel de Bluehost) lo haga por vos, podés enviarle exactamente este mensaje: --- > Hi, > > I need to add a DNS A record to the `inqualityhq.com` domain managed through Bluehost cPanel. > Could you please add the following record? > > - **Domain:** inqualityhq.com > - **Record type:** A > - **Host/Name:** pedrito *(this will create pedrito.inqualityhq.com)* > - **Value/Points to:** `` *(replace with the actual VPS IP)* > - **TTL:** 300 seconds (minimum available) > > Once added, please confirm so I can verify propagation. > If you need me to confirm with the account holder, let me know. > > Thank you! --- Reemplazá `` con la IP real del VPS antes de enviar el mensaje. --- ## 3. Conectarse al VPS Desde tu computadora local, abrí una terminal: ```bash ssh root@ ``` Si tenés clave SSH configurada en Hostinger (recomendado): ```bash ssh -i ~/.ssh/id_rsa root@ ``` Si te pide contraseña, es la del panel de Hostinger. > **Tip para Windows:** Usá [Windows Terminal](https://apps.microsoft.com/detail/9n0dx20hk701) o > [PuTTY](https://www.putty.org/). En Windows Terminal el comando SSH es el mismo. > **Tip:** Si es la primera conexión, va a preguntar: > ``` > Are you sure you want to continue connecting (yes/no/[fingerprint])? > ``` > Escribí `yes` y Enter. --- ## 4. Actualizar el sistema y crear usuario Todos estos comandos se ejecutan como **root**. ### 4.1 Actualizar paquetes ```bash apt update && apt upgrade -y ``` Esto puede tardar 2-5 minutos. Esperá que termine. ### 4.2 Verificar la versión de Ubuntu ```bash lsb_release -a ``` Salida esperada: `Ubuntu 22.04.x LTS`. Si es otra versión, avisale a Marcos antes de continuar. ### 4.3 Crear el usuario `pedrito` El bot no debe correr como root. Le creamos un usuario propio: ```bash # Crear el usuario (sin contraseña de login interactivo) adduser pedrito --disabled-password --gecos "" # Verificar que se creó id pedrito ``` Salida esperada: ``` uid=1001(pedrito) gid=1001(pedrito) groups=1001(pedrito) ``` > **¿Por qué UID 1001 y no 1000?** En Ubuntu el usuario 1000 suele ser el primer usuario > creado durante la instalación. En un VPS de Hostinger puede o no existir. > Lo importante es que `pedrito` tenga su propio UID. ### 4.4 Permitir que `pedrito` use Docker (sin sudo) ```bash # Esto lo hacemos después de instalar Docker (paso 5), pero lo dejamos acá como referencia # usermod -aG docker pedrito ← lo corremos en el paso 5.3 ``` ### 4.5 Configurar acceso SSH para `pedrito` (opcional pero recomendado) ```bash mkdir -p /home/pedrito/.ssh cp /root/.ssh/authorized_keys /home/pedrito/.ssh/ chown -R pedrito:pedrito /home/pedrito/.ssh chmod 700 /home/pedrito/.ssh chmod 600 /home/pedrito/.ssh/authorized_keys ``` Para verificar: en una segunda terminal, intentá `ssh pedrito@`. Si entra sin contraseña, perfecto. --- ## 5. Instalar Docker Docker es el motor de contenedores que va a correr el bot de forma aislada y reproducible. ### 5.1 Instalar dependencias previas ```bash apt install -y \ ca-certificates \ curl \ gnupg \ lsb-release ``` ### 5.2 Agregar el repositorio oficial de Docker ```bash # Crear directorio para las claves GPG install -m 0755 -d /etc/apt/keyrings # Descargar e instalar la clave GPG de Docker curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \ gpg --dearmor -o /etc/apt/keyrings/docker.gpg chmod a+r /etc/apt/keyrings/docker.gpg # Agregar el repositorio echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \ https://download.docker.com/linux/ubuntu \ $(lsb_release -cs) stable" | \ tee /etc/apt/sources.list.d/docker.list > /dev/null # Actualizar lista de paquetes apt update ``` ### 5.3 Instalar Docker Engine y docker-compose ```bash apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin # Verificar que Docker se instaló correctamente docker --version # Salida esperada: Docker version 24.x.x o mayor docker compose version # Salida esperada: Docker Compose version v2.x.x ``` ### 5.4 Agregar a `pedrito` al grupo docker ```bash # Esto permite que pedrito corra docker sin sudo usermod -aG docker pedrito # Verificar groups pedrito # Debe incluir "docker" en la lista ``` ### 5.5 Verificar que Docker funciona ```bash docker run --rm hello-world ``` Si imprime `Hello from Docker!`, Docker está funcionando. > **¿Qué es un contenedor?** Pensalo como una "caja" cerrada que incluye el código del bot > y todas sus dependencias. Si algo falla dentro de la caja, no afecta al resto del servidor. > `docker compose up` abre la caja, `docker compose down` la cierra. --- ## 6. Instalar Nginx Nginx actúa como intermediario entre internet y el bot. Recibe las conexiones HTTPS (puerto 443) y las reenvía internamente al bot (puerto 8080). Esto permite tener SSL sin que el bot lo maneje. ```bash # Instalar Nginx apt install -y nginx # Verificar que arrancó systemctl status nginx ``` Salida esperada: ``` ● nginx.service - A high performance web server and reverse proxy server Active: active (running) since ... ``` Si dice `inactive`, arrancalo manualmente: ```bash systemctl start nginx systemctl enable nginx # para que arranque en cada reboot ``` --- ## 7. Clonar el repositorio A partir de acá trabajamos como el usuario `pedrito`: ```bash # Cambiar al usuario pedrito su - pedrito # Verificar que estamos en el home correcto pwd # Debe mostrar: /home/pedrito # Clonar el repositorio git clone https://github.com/InqGit/hechakuaa_bot.git # Entrar al directorio cd hechakuaa_bot # Verificar que los archivos están presentes ls -la ``` Deberías ver archivos como: `main.py`, `config.py`, `telegram_bot.py`, `Dockerfile`, `docker-compose.yml`, `migrations/`, etc. ### Crear el directorio de datos ```bash # Este directorio persiste entre reinicios del bot (DB, snapshots) mkdir -p /home/pedrito/hechakuaa_bot/data ``` Salir de vuelta a root para el siguiente paso: ```bash exit # Ahora estás como root de nuevo ``` ### Ajustar permisos del directorio data para Docker El contenedor Docker corre con usuario UID 1000. El directorio `data/` necesita ser escribible por ese UID: ```bash chown -R 1000:1000 /home/pedrito/hechakuaa_bot/data # Verificar ls -la /home/pedrito/hechakuaa_bot/data # Debe mostrar: drwxr-xr-x 2 1000 1000 ... data ``` > **¿Por qué 1000 y no pedrito?** Dentro del contenedor Docker existe un usuario con UID 1000. > El servidor no sabe que ese usuario se llama "pedrito" — solo entiende el número UID. > En el sistema anfitrión, UID 1000 puede corresponder a un usuario diferente, pero Docker > solo necesita que el directorio sea escribible por ese número. --- ## 8. Configurar el archivo .env El archivo `.env` contiene toda la configuración sensible del bot. Se crea directamente en el VPS y **nunca** se sube al repositorio ni se copia entre máquinas. ```bash # Como pedrito: su - pedrito cd /home/pedrito/hechakuaa_bot # Crear .env a partir del template cp .env.example .env # Editar (nano es el editor más simple) nano .env ``` ### Guía de campos del .env Completá los campos marcados con `← COMPLETAR`. Los demás se pueden dejar con sus valores por defecto: ```bash # ── CIFRADO ────────────────────────────────────────────────────────────────── FERNET_KEY= # ← COMPLETAR (Marcos te la da o la generás abajo) # ── CREDENCIALES PJ (se completan en el paso 9) ─────────────────────────────── PJ_USUARIO_ENC= # ← se completa automáticamente con el script de cifrado PJ_PASSWORD_ENC= # ← se completa automáticamente con el script de cifrado # ── TELEGRAM ───────────────────────────────────────────────────────────────── TELEGRAM_BOT_TOKEN= # ← COMPLETAR (de @BotFather) TELEGRAM_CHAT_ID= # ← COMPLETAR (chat ID de Marcos) TELEGRAM_EXTRA_IDS= # ← COMPLETAR si hay más abogados (ej: 111222333,444555666) # ── INVITACIÓN ──────────────────────────────────────────────────────────────── INVITE_CODE= # ← COMPLETAR (código maestro ilimitado) # ── WEBHOOK (completar después del paso 14) ─────────────────────────────────── WEBHOOK_URL= # ← dejar vacío por ahora, completar en el paso 14 WEBHOOK_SECRET= # ← dejar vacío por ahora, completar en el paso 14 # ── HEALTH CHECK ───────────────────────────────────────────────────────────── HEALTH_PORT=8080 # dejar así HEALTH_ALERT_HOURS=2 # alerta al admin si el poller no actualizó en 2 horas # Token para /health (detallado) y /health/history — generar con: # python3 -c "import secrets; print(secrets.token_hex(32))" HEALTH_AUTH_TOKEN= # ← COMPLETAR (recomendado) # ── POLLING ────────────────────────────────────────────────────────────────── POLL_INTERVAL_MINUTES=60 # revisar el PJ cada 60 minutos POLL_HORA_INICIO=7 # desde las 7am (hora Paraguay) POLL_HORA_FIN=18 # hasta las 6pm (hora Paraguay) ``` Guardar y salir de nano: `Ctrl+O` → `Enter` → `Ctrl+X`. ### Generar FERNET_KEY si Marcos no te la dio ```bash # Generar una clave nueva (solo si no tenés una preexistente) uv run python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())" ``` Salida esperada: una cadena de ~44 caracteres como `gAAAAABx3...`. Copiarla y pegarla en `.env`. > ⚠️ **Guardá la FERNET_KEY en un gestor de contraseñas (Bitwarden, 1Password, etc.).** > Si se pierde, las credenciales cifradas de todos los abogados quedan irrecuperables. ### Instalar uv (si no está disponible como pedrito) ```bash curl -LsSf https://astral.sh/uv/install.sh | sh source $HOME/.local/bin/env uv --version ``` ### Restringir permisos del .env ```bash chmod 600 /home/pedrito/hechakuaa_bot/.env # Verificar ls -la /home/pedrito/hechakuaa_bot/.env # Debe mostrar: -rw------- 1 pedrito pedrito ``` --- ## 9. Cifrar las credenciales del PJ El bot nunca guarda la cédula ni la contraseña del PJ en texto plano. Este script las cifra con la `FERNET_KEY` y guarda el resultado cifrado en `.env`. ```bash # Como pedrito, dentro del directorio del proyecto: cd /home/pedrito/hechakuaa_bot uv run python scripts/cifrar_credenciales.py ``` El script te va a pedir: 1. **Cédula de identidad** — el número con el que Marcos entra a `apps.csj.gov.py` 2. **Contraseña** — la contraseña de ese sistema (no se muestra mientras escribís) Salida esperada: ``` ✅ Credenciales cifradas y guardadas en .env PJ_USUARIO_ENC y PJ_PASSWORD_ENC actualizadas. ``` Verificar que se guardaron: ```bash grep "PJ_USUARIO_ENC" /home/pedrito/hechakuaa_bot/.env # Debe mostrar un valor cifrado largo, NUNCA la cédula en texto plano ``` --- ## 10. Obtener certificado SSL con Certbot HTTPS es obligatorio para el modo webhook de Telegram y para cualquier servicio expuesto en producción. Certbot obtiene el certificado gratis de Let's Encrypt. > **Prerequisito:** El DNS del paso 2 ya debe estar propagado. Verificalo: > ```bash > nslookup pedrito.inqualityhq.com > # Debe retornar la IP de este VPS > ``` > Si no retorna la IP correcta, esperá y volvé después. ### 10.1 Instalar Certbot ```bash # Como root: exit # si estabas como pedrito, volver a root apt install -y certbot python3-certbot-nginx ``` ### 10.2 Obtener el certificado ```bash certbot --nginx -d pedrito.inqualityhq.com ``` Certbot te va a preguntar: 1. Tu dirección de email (para avisos de vencimiento) — ponés la de Marcos o la del equipo 2. Aceptar los términos de servicio → `Y` 3. Si querés compartir el email con EFF → `N` (opcional) Salida esperada al terminar: ``` Successfully received certificate. Certificate is saved at: /etc/letsencrypt/live/pedrito.inqualityhq.com/fullchain.pem Key is saved at: /etc/letsencrypt/live/pedrito.inqualityhq.com/privkey.pem ... ``` > **¿Por qué usamos --nginx?** Le decimos a Certbot que tenemos Nginx instalado y que puede > configurar automáticamente el sitio para HTTPS. Pero vamos a sobreescribir esa configuración > en el siguiente paso para personalizarla. ### 10.3 Verificar la renovación automática Los certificados de Let's Encrypt duran 90 días y se renuevan automáticamente: ```bash # Verificar que el timer de renovación automática existe systemctl status certbot.timer # Debe estar "active (waiting)" # Simular una renovación para verificar que funciona certbot renew --dry-run # Debe decir: "Simulating renewal of an existing certificate..." ``` --- ## 11. Configurar Nginx como reverse proxy Nginx recibe las requests de internet y las redirige al bot corriendo en el contenedor Docker (puerto 8080). También maneja el SSL automáticamente. ### 11.1 Crear el archivo de configuración ```bash # Como root: nano /etc/nginx/sites-available/pedrito ``` Pegar **exactamente** este contenido (reemplazá el nombre de dominio si usás uno diferente): ```nginx # /etc/nginx/sites-available/pedrito # Reverse proxy para Pedrito Hechakuaa # ── Rate limiting ───────────────────────────────────────────────────────────── # Limita requests por IP para mitigar escaneo automatizado y ataques de fuerza bruta. # La zona se define fuera del bloque server (en el contexto http). # Si tenés otros sites en este Nginx, mover estas líneas a /etc/nginx/nginx.conf # dentro del bloque http { }. limit_req_zone $binary_remote_addr zone=pedrito_webhook:10m rate=30r/s; limit_req_zone $binary_remote_addr zone=pedrito_health:10m rate=10r/m; server { listen 80; server_name pedrito.inqualityhq.com; # Redirigir todo el tráfico HTTP a HTTPS return 301 https://$server_name$request_uri; } server { listen 443 ssl; server_name pedrito.inqualityhq.com; # Certificados SSL de Let's Encrypt ssl_certificate /etc/letsencrypt/live/pedrito.inqualityhq.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/pedrito.inqualityhq.com/privkey.pem; include /etc/letsencrypt/options-ssl-nginx.conf; ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # Seguridad: no exponer versión de Nginx ni headers informativos server_tokens off; add_header X-Content-Type-Options nosniff; add_header X-Frame-Options DENY; # Tamaño máximo de payload (Telegram puede enviar archivos) client_max_body_size 20M; # ── Health check ────────────────────────────────────────────────────────── # /health es público (respuesta mínima sin datos sensibles) # /health/history requiere Bearer token — el bot lo rechaza sin él location /health { limit_req zone=pedrito_health burst=5 nodelay; proxy_pass http://127.0.0.1:8080/health; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_read_timeout 10s; } location /health/history { limit_req zone=pedrito_health burst=3 nodelay; proxy_pass http://127.0.0.1:8080/health/history; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; # Pasar Authorization header para que el bot pueda validar el Bearer token proxy_set_header Authorization $http_authorization; proxy_read_timeout 10s; } # ── Webhook de Telegram ─────────────────────────────────────────────────── # Solo Telegram envía aquí — los IPs de Telegram son bien conocidos (149.154.x, 91.108.x) # El bot valida el X-Telegram-Bot-Api-Secret-Token internamente location /webhook { limit_req zone=pedrito_webhook burst=50 nodelay; proxy_pass http://127.0.0.1:8080/webhook; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Telegram-Bot-Api-Secret-Token $http_x_telegram_bot_api_secret_token; proxy_read_timeout 30s; } # ── Denegar cualquier otra ruta ─────────────────────────────────────────── # Respuesta genérica — no revelar que hay un bot ni su propósito location / { return 404; } } ``` Guardar: `Ctrl+O` → `Enter` → `Ctrl+X`. ### 11.2 Activar el sitio ```bash # Crear el enlace simbólico que activa el sitio ln -s /etc/nginx/sites-available/pedrito /etc/nginx/sites-enabled/pedrito # Verificar que no hay errores de sintaxis en la configuración nginx -t ``` Salida esperada: ``` nginx: the configuration file /etc/nginx/nginx.conf syntax is ok nginx: configuration file /etc/nginx/nginx.conf test is successful ``` Si hay errores, revisá que el archivo se pegó correctamente (especialmente las comillas y los `;`). ### 11.3 Recargar Nginx ```bash systemctl reload nginx systemctl status nginx # Debe estar active (running) ``` ### 11.4 Verificar que Nginx responde ```bash # Desde el propio servidor: curl -sk https://pedrito.inqualityhq.com/health # Debe retornar un error de conexión rechazada (porque el bot no está corriendo aún) # o un JSON si ya está corriendo # Verificar que HTTP redirige a HTTPS: curl -I http://pedrito.inqualityhq.com/health # Debe retornar: HTTP/1.1 301 Moved Permanently ``` --- ## 12. Configurar el firewall ```bash # Como root ufw allow OpenSSH ufw allow 'Nginx Full' # abre puertos 80 (HTTP) y 443 (HTTPS) ufw --force enable # Verificar estado ufw status ``` Salida esperada: ``` Status: active To Action From -- ------ ---- OpenSSH ALLOW Anywhere Nginx Full ALLOW Anywhere OpenSSH (v6) ALLOW Anywhere (v6) Nginx Full (v6) ALLOW Anywhere (v6) ``` > **Importante:** El puerto 8080 del bot **no** se abre al exterior. Solo Nginx (que corre en el > mismo servidor) se comunica con el bot internamente. El bot no es accesible directamente desde > internet, solo a través de Nginx. --- ## 13. Iniciar el bot con Docker ### 13.1 Actualizar docker-compose.yml para producción Por seguridad, el bot solo debe escuchar en localhost (no en todas las interfaces de red). Modificá el puerto en docker-compose.yml: ```bash # Como pedrito: su - pedrito cd /home/pedrito/hechakuaa_bot # Editar docker-compose.yml nano docker-compose.yml ``` Buscá la línea: ```yaml - "8080:8080" ``` Cambiala por: ```yaml - "127.0.0.1:8080:8080" ``` Guardar y salir. Esto hace que el bot solo sea accesible desde el mismo servidor (por Nginx), no desde internet. ### 13.2 Construir la imagen Docker ```bash # Construir la imagen (tarda 1-3 minutos la primera vez) docker compose build # Salida esperada al final: # => exporting to image # => => naming to docker.io/library/hechakuaa_bot-pedrito ``` ### 13.3 Primera prueba: correr en primer plano Antes de dejarlo en background, verificamos que arranca bien: ```bash docker compose up ``` Deberías ver logs como: ``` pedrito-1 | {"event": "migration_aplicada", "version": "001_initial_schema", ...} pedrito-1 | {"event": "database_iniciada", ...} pedrito-1 | {"event": "health_server_iniciado", "port": 8080, ...} pedrito-1 | {"event": "bot_telegram_iniciado", ...} pedrito-1 | {"event": "pedrito_hechakuaa_corriendo", ...} pedrito-1 | {"event": "poller_iniciado", ...} pedrito-1 | {"event": "login_exitoso", ...} ``` Si aparece `login_exitoso`, el bot se conectó al PJ. Detenerlo con `Ctrl+C`. > **Si no aparece `login_exitoso`:** ver la sección [Troubleshooting](#19-troubleshooting). ### 13.4 Correr en background (modo producción) ```bash # Arrancar en modo detached (background) docker compose up -d # Verificar que el contenedor está corriendo docker compose ps ``` Salida esperada: ``` NAME IMAGE COMMAND SERVICE STATUS PORTS hechakuaa_bot-... hechakuaa_bot-pedrito "uv run python main.…" pedrito running 127.0.0.1:8080->8080/tcp ``` El `STATUS` debe decir `running`. ### 13.5 Configurar Docker para que arranque en cada reboot ```bash # Como root: exit # salir de pedrito # El servicio de Docker ya inicia automáticamente # Solo verificar que el contenedor tiene restart policy correcta systemctl enable docker ``` El `docker-compose.yml` ya tiene `restart: unless-stopped`, lo que hace que el contenedor se reinicie automáticamente si el VPS se reinicia. --- ## 14. Activar el modo webhook En modo webhook, Telegram envía los mensajes al bot directamente (HTTPS push), en lugar de que el bot los pida constantemente (polling). Es más eficiente y tiene menor latencia. ### 14.1 Generar el WEBHOOK_SECRET Este token es como una contraseña que Telegram incluye en cada request para probar que es legítimo: ```bash # Generar un token aleatorio seguro: python3 -c "import secrets; print(secrets.token_hex(32))" # Salida: una cadena de 64 caracteres hexadecimales ``` Copiar la salida. La vamos a usar en el siguiente paso. ### 14.2 Actualizar .env con los datos del webhook ```bash su - pedrito nano /home/pedrito/hechakuaa_bot/.env ``` Completar estas dos líneas: ```bash WEBHOOK_URL=https://pedrito.inqualityhq.com WEBHOOK_SECRET= ``` Guardar y salir. ### 14.3 Reiniciar el bot para aplicar el cambio ```bash cd /home/pedrito/hechakuaa_bot docker compose restart # Ver los logs para confirmar que el webhook se registró: docker compose logs --tail=20 ``` Buscar en los logs: ``` {"event": "webhook_registrado", "url": "https://pedrito.inqualityhq.com/webhook", ...} ``` ### 14.4 Verificar que el webhook quedó registrado en Telegram ```bash # Reemplazá TU_BOT_TOKEN con el valor real del .env curl -s "https://api.telegram.org/bot/getWebhookInfo" | python3 -m json.tool ``` Salida esperada: ```json { "ok": true, "result": { "url": "https://pedrito.inqualityhq.com/webhook", "has_custom_certificate": false, "pending_update_count": 0, ... } } ``` El campo `url` debe mostrar la URL correcta. Si está vacío o muestra otra URL, revisá los logs del bot. --- ## 15. Verificación final Correr esta lista de verificaciones antes de dar el deploy por terminado: ```bash # 1. El contenedor Docker está corriendo docker compose ps # STATUS debe ser "running" # 2. Los logs no tienen errores docker compose logs --tail=30 # Buscar "login_exitoso" y "pedrito_hechakuaa_corriendo" # NO debe aparecer: ERROR, exception, Traceback # 3. El health check responde curl -s https://pedrito.inqualityhq.com/health | python3 -m json.tool # Debe retornar JSON con "status": "ok" # 4. El webhook está registrado (si activaste el modo webhook) curl -s "https://api.telegram.org/bot/getWebhookInfo" | python3 -m json.tool # url debe ser: https://pedrito.inqualityhq.com/webhook # 5. El .env tiene los permisos correctos ls -la /home/pedrito/hechakuaa_bot/.env # Debe mostrar: -rw------- 1 pedrito pedrito # 6. El directorio data existe y tiene contenido ls -la /home/pedrito/hechakuaa_bot/data/ # Debe aparecer pasante.db y snapshot_*.json # 7. Nginx redirige HTTP a HTTPS curl -I http://pedrito.inqualityhq.com/health # Debe retornar: HTTP/1.1 301 Moved Permanently ``` Si todos los checks pasan, avisarle a Marcos para que pruebe mandándole `/start` al bot en Telegram. --- ## 16. Monitoreo y logs ### Logs del bot en tiempo real ```bash # Como pedrito: su - pedrito cd /home/pedrito/hechakuaa_bot # Ver logs en tiempo real (Ctrl+C para salir) docker compose logs -f # Ver solo los últimos 50 registros docker compose logs --tail=50 # Ver logs con timestamps más legibles docker compose logs --tail=50 --timestamps ``` ### Logs de Nginx ```bash # Accesos (como root): tail -f /var/log/nginx/access.log # Errores: tail -f /var/log/nginx/error.log # Solo errores del bot (filtra por el path): grep "/webhook\|/health" /var/log/nginx/access.log | tail -20 ``` ### Eventos útiles que buscar en los logs del bot | Evento | Qué significa | |--------|---------------| | `login_exitoso` | Se conectó al PJ correctamente | | `migration_aplicada` | Se aplicó una migración de DB (solo al arrancar) | | `health_server_iniciado` | El server HTTP de health está escuchando | | `webhook_registrado` | El webhook se registró con Telegram correctamente | | `polling_tenant_ok` | Revisión de notificaciones completada sin errores | | `polling_tenant_error` | Error al revisar notificaciones de un abogado | | `pj_error_conexion` | El PJ no respondió (puede ser transitorio) | | `bot_notificacion_enviada` | Se envió una notificación a Telegram | | `onboarding_completado` | Un abogado completó el registro | | `health_alerta_enviada` | El bot detectó que el poller está detenido y avisó al admin | ### Ver historial de eventos vía HTTP ```bash # Últimos 50 eventos del audit log: curl -s https://pedrito.inqualityhq.com/health/history | python3 -m json.tool # Últimos 100 eventos: curl -s "https://pedrito.inqualityhq.com/health/history?limit=100" | python3 -m json.tool ``` --- ## 17. Health check HTTP El bot expone un endpoint HTTP en `https://pedrito.inqualityhq.com/health` que retorna el estado actual. ### Ejemplo de respuesta normal (status 200) ```json { "status": "ok", "timestamp": "2026-05-25T10:00:00+00:00", "tenants_activos": 3, "last_poll": "2026-05-25T09:58:42+00:00", "last_poll_tenants": 3, "seconds_since_last_poll": 78, "poll_interval_minutes": 60, "webhook_mode": true } ``` ### Ejemplo de respuesta degradada (status 503) Si el poller no actualizó en más de `HEALTH_ALERT_HOURS` horas, retorna: ```json { "status": "degraded", ... } ``` En ese caso, el bot también manda un mensaje de alerta automáticamente al Telegram de Marcos. ### Integrar con un monitor externo (opcional) Si tienen un servicio de uptime monitoring (UptimeRobot, BetterUptime, etc.): - URL a monitorear: `https://pedrito.inqualityhq.com/health` - Método: `GET` - Código esperado: `200` - Intervalo: `5 minutos` --- ## 18. Cómo actualizar el bot Cuando hay cambios en el código (nuevo commit en el repositorio): ```bash # Como pedrito: su - pedrito cd /home/pedrito/hechakuaa_bot # 1. Traer los cambios del repositorio git pull # 2. Reconstruir la imagen con el nuevo código docker compose build # 3. Reiniciar el contenedor con la nueva imagen docker compose up -d # 4. Verificar que levantó bien docker compose ps docker compose logs --tail=20 ``` > **Importante:** El archivo `.env` y el directorio `data/` **no se tocan con `git pull`** — > están en `.gitignore`. Las credenciales y el historial de notificaciones persisten > entre actualizaciones. > **Si hubo cambios en `.env.example`:** revisar manualmente si hay nuevos campos que agregar al `.env` existente. > ```bash > # Ver diferencias entre el template nuevo y el .env actual: > diff .env.example .env > ``` --- ## 19. Troubleshooting ### El contenedor no arranca ```bash # Ver el error específico: docker compose logs --tail=50 # O ver el estado del contenedor: docker compose ps ``` **`FERNET_KEY inválida`** La clave en `.env` es incorrecta o tiene espacios/comillas. Verificar que el valor en `.env` no tiene comillas ni espacios al inicio/final. **`telegram.error.InvalidToken`** El `TELEGRAM_BOT_TOKEN` en `.env` tiene un error. Verificar copiando el token directamente desde la conversación con @BotFather en Telegram. **`ModuleNotFoundError: No module named 'X'`** La imagen necesita ser reconstruida: ```bash docker compose build --no-cache docker compose up -d ``` **`Error response from daemon: Cannot connect to the Docker daemon`** Docker no está corriendo: ```bash systemctl start docker docker compose up -d ``` --- ### El bot arranca pero no responde en Telegram 1. Verificar que el contenedor está corriendo: `docker compose ps` 2. Verificar que el webhook está registrado: `curl -s "https://api.telegram.org/bot/getWebhookInfo"` 3. Verificar que Nginx está ok: `systemctl status nginx` 4. Verificar que el health check responde: `curl -s https://pedrito.inqualityhq.com/health` 5. Revisar logs de Nginx: `tail -20 /var/log/nginx/error.log` --- ### Error de credenciales del PJ ```bash su - pedrito cd /home/pedrito/hechakuaa_bot # Repetir el cifrado de credenciales uv run python scripts/cifrar_credenciales.py # Reiniciar el bot docker compose restart docker compose logs --tail=10 ``` --- ### El certificado SSL venció o hay error de SSL ```bash # Renovar manualmente (normalmente es automático): certbot renew # Recargar Nginx para que use el nuevo certificado: systemctl reload nginx ``` --- ### El webhook no recibe mensajes (pero el bot arranca) ```bash # 1. Verificar que Nginx está reenviando correctamente: tail -20 /var/log/nginx/access.log # Debe aparecer peticiones POST a /webhook cuando alguien manda un mensaje # 2. Verificar el health check del bot directamente (sin pasar por Nginx): # Desde el propio servidor: curl -s http://127.0.0.1:8080/health # 3. Volver a registrar el webhook manualmente: curl "https://api.telegram.org/bot/setWebhook?url=https://pedrito.inqualityhq.com/webhook&secret_token=" ``` --- ### El VPS se reinició y el bot no volvió El contenedor tiene `restart: unless-stopped`, así que debería volver solo. Si no: ```bash su - pedrito cd /home/pedrito/hechakuaa_bot docker compose up -d ``` Si Docker en sí no arrancó: ```bash systemctl start docker su - pedrito cd /home/pedrito/hechakuaa_bot docker compose up -d ``` --- ### Ver dos instancias corriendo (causa conflictos de Telegram) ```bash docker compose ps # Si hay más de un contenedor corriendo: docker compose down docker compose up -d ``` --- ### Error de permisos en el directorio data/ ```bash # Como root: chown -R 1000:1000 /home/pedrito/hechakuaa_bot/data # Reiniciar: su - pedrito cd /home/pedrito/hechakuaa_bot docker compose restart ``` --- ## 20. Referencia rápida de comandos ### Gestión del bot (como usuario `pedrito`) ```bash cd /home/pedrito/hechakuaa_bot # Estado docker compose ps # Iniciar / detener / reiniciar docker compose up -d docker compose down docker compose restart # Logs en tiempo real docker compose logs -f # Logs últimos 50 registros docker compose logs --tail=50 # Actualizar (git pull + rebuild + restart) git pull && docker compose build && docker compose up -d ``` ### Nginx (como `root`) ```bash # Verificar configuración nginx -t # Recargar sin cortar conexiones systemctl reload nginx # Reiniciar completamente systemctl restart nginx # Ver logs de error tail -f /var/log/nginx/error.log ``` ### SSL ```bash # Renovar certificado manualmente certbot renew # Ver fecha de vencimiento del certificado actual certbot certificates ``` ### Rutas de archivos importantes | Archivo | Ruta absoluta | |---------|---------------| | Código del bot | `/home/pedrito/hechakuaa_bot/` | | Configuración | `/home/pedrito/hechakuaa_bot/.env` | | Base de datos SQLite | `/home/pedrito/hechakuaa_bot/data/pasante.db` | | Snapshots de notificaciones | `/home/pedrito/hechakuaa_bot/data/snapshot_*.json` | | Docker Compose | `/home/pedrito/hechakuaa_bot/docker-compose.yml` | | Config Nginx del bot | `/etc/nginx/sites-available/pedrito` | | Logs de Nginx | `/var/log/nginx/` | | Certificado SSL (cert) | `/etc/letsencrypt/live/pedrito.inqualityhq.com/fullchain.pem` | | Certificado SSL (clave) | `/etc/letsencrypt/live/pedrito.inqualityhq.com/privkey.pem` | --- *Repositorio: https://github.com/InqGit/hechakuaa_bot* *Ante dudas técnicas no cubiertas acá, contactar a Marcos Benítez.*