# 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:** 30–45 minutos > **Acceso requerido:** SSH root al VPS + datos de configuración provistos por Marcos > **Subdominio objetivo:** `pedrito.inqualityhq.com` ### Estado del servidor al momento de esta guía | Componente | Estado | |---|---| | DNS `pedrito.inqualityhq.com` | ✅ ya apunta al VPS (93.127.136.210) | | Docker v29.2.1 + compose v2 plugin | ✅ instalados | | Nginx v1.24.0 | ✅ instalado | | Usuario `pedrito` (UID 1003) | ✅ existe | | `pedrito` en grupo docker | ❌ paso 3 | | Repositorio clonado | ❌ paso 4 | | `.env` configurado | ❌ paso 5 | | Credenciales PJ cifradas | ❌ paso 6 | | Certificado SSL | ❌ paso 7 | | Nginx configurado para el bot | ❌ paso 8 | | Firewall | ❓ paso 9 | | Bot corriendo | ❌ paso 10 | > **Nota sobre docker-compose:** el servidor tiene `docker-compose` v1.29.2 (standalone) instalado. > Esta guía usa `docker compose` (con espacio, plugin v2) que también está disponible. No mezclar los dos. --- ## Índice **PARTE A — Preparación** 1. [Datos que necesitás antes de empezar](#1-datos-que-necesitás-antes-de-empezar) 2. [Conectarse al VPS](#2-conectarse-al-vps) 3. [Agregar `pedrito` al grupo docker](#3-agregar-pedrito-al-grupo-docker) **PARTE B — Código y configuración** 4. [Clonar el repositorio](#4-clonar-el-repositorio) 5. [Configurar el archivo .env](#5-configurar-el-archivo-env) 6. [Cifrar las credenciales del PJ](#6-cifrar-las-credenciales-del-pj) **PARTE C — HTTPS y producción** 7. [Obtener certificado SSL con Certbot](#7-obtener-certificado-ssl-con-certbot) 8. [Configurar Nginx como reverse proxy](#8-configurar-nginx-como-reverse-proxy) 9. [Configurar el firewall](#9-configurar-el-firewall) 10. [Iniciar el bot con Docker](#10-iniciar-el-bot-con-docker) 11. [Activar el modo webhook](#11-activar-el-modo-webhook) **PARTE D — Operación** 12. [Verificación final](#12-verificación-final) 13. [Monitoreo y logs](#13-monitoreo-y-logs) 14. [Health check HTTP](#14-health-check-http) 15. [Cómo actualizar el bot](#15-cómo-actualizar-el-bot) 16. [Troubleshooting](#16-troubleshooting) 17. [Referencia rápida de comandos](#17-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) | `.env` | | 5 | `INVITE_CODE` | Marcos lo decide | `.env` | | 6 | Cédula del PJ | Marcos | Script de cifrado (paso 6) | | 7 | Contraseña del PJ | Marcos | Script de cifrado (paso 6) | > **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. > El script del paso 6 la pide en pantalla y guarda solo el resultado cifrado. --- ## 2. Conectarse al VPS ```bash ssh root@93.127.136.210 ``` Si tenés clave SSH configurada en Hostinger: ```bash ssh -i ~/.ssh/id_rsa root@93.127.136.210 ``` > Si es la primera conexión, escribí `yes` cuando pregunte por el fingerprint. --- ## 3. Agregar `pedrito` al grupo docker El usuario `pedrito` existe pero no está en el grupo `docker`. Sin esto no puede correr contenedores. ```bash # Como root: usermod -aG docker pedrito # Verificar que se agregó: groups pedrito # Debe incluir "docker" en la lista ``` > **Importante:** Este cambio toma efecto en la **próxima sesión SSH** de `pedrito`. > Si ya tenés una sesión abierta como `pedrito`, cerrala y volvé a abrir. --- ## 4. Clonar el repositorio ```bash # Cambiar al usuario pedrito su - pedrito # Verificar que estamos en su home 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: `main.py`, `config.py`, `Dockerfile`, `docker-compose.yml`, `migrations/`, etc. ### Crear el directorio de datos ```bash mkdir -p /home/pedrito/hechakuaa_bot/data ``` Salir a root para el siguiente paso: ```bash exit ``` ### Ajustar permisos del directorio data para Docker El contenedor corre con UID 1000. El directorio `data/` necesita ser escribible por ese UID: ```bash # Como root: 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 ``` --- ## 5. Configurar el archivo .env ```bash # Como pedrito: su - pedrito cd /home/pedrito/hechakuaa_bot # Crear .env a partir del template cp .env.example .env # Editar nano .env ``` ### Guía de campos del .env Completá los campos marcados con `← COMPLETAR`: ```bash # ── CIFRADO ────────────────────────────────────────────────────────────────── FERNET_KEY= # ← COMPLETAR (Marcos te la da o la generás abajo) # ── CREDENCIALES PJ (se completan en el paso 6) ─────────────────────────────── PJ_USUARIO_ENC= # ← se completa automáticamente con el script PJ_PASSWORD_ENC= # ← se completa automáticamente con el script # ── 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 11) ─────────────────────────────────── WEBHOOK_URL= # ← dejar vacío por ahora WEBHOOK_SECRET= # ← dejar vacío por ahora # ── HEALTH CHECK ───────────────────────────────────────────────────────────── HEALTH_PORT=8080 # dejar así HEALTH_ALERT_HOURS=2 HEALTH_AUTH_TOKEN= # ← COMPLETAR: python3 -c "import secrets; print(secrets.token_hex(32))" # ── POLLING ────────────────────────────────────────────────────────────────── POLL_INTERVAL_MINUTES=60 POLL_HORA_INICIO=7 POLL_HORA_FIN=18 ``` Guardar y salir: `Ctrl+O` → `Enter` → `Ctrl+X`. ### Generar FERNET_KEY (si Marcos no te la dio) ```bash python3 -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())" ``` > ⚠️ **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) ```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 ``` --- ## 6. Cifrar las credenciales del PJ ```bash # Como pedrito, en el directorio del proyecto: cd /home/pedrito/hechakuaa_bot uv run python scripts/cifrar_credenciales.py ``` El script pide: 1. **Cédula de identidad** — el número con el que Marcos entra a `apps.csj.gov.py` 2. **Contraseña** — no se muestra mientras escribís Salida esperada: ``` ✅ Credenciales cifradas y guardadas en .env PJ_USUARIO_ENC y PJ_PASSWORD_ENC actualizadas. ``` Verificar: ```bash grep "PJ_USUARIO_ENC" /home/pedrito/hechakuaa_bot/.env # Debe mostrar un token cifrado largo, NUNCA la cédula en texto plano ``` --- ## 7. Obtener certificado SSL con Certbot HTTPS es obligatorio para el modo webhook de Telegram. > **Prerequisito:** el DNS ya está propagado (verificado: `pedrito.inqualityhq.com` → 93.127.136.210). ```bash # Como root: exit # salir de pedrito si estabas ahí # Instalar Certbot con plugin de Nginx apt install -y certbot python3-certbot-nginx # Obtener el certificado certbot --nginx -d pedrito.inqualityhq.com ``` Certbot te va a preguntar: 1. Tu dirección de email (para avisos de vencimiento) 2. Aceptar los términos de servicio → `Y` 3. Compartir email con EFF → `N` 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 ``` ### Verificar renovación automática ```bash systemctl status certbot.timer # Debe estar "active (waiting)" certbot renew --dry-run # Debe decir: "Simulating renewal of an existing certificate..." ``` --- ## 8. Configurar Nginx como reverse proxy Nginx recibe las conexiones HTTPS y las redirige al bot en el puerto 8080. ```bash # Como root: nano /etc/nginx/sites-available/pedrito ``` Pegar **exactamente** este contenido: ```nginx # /etc/nginx/sites-available/pedrito 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; return 301 https://$server_name$request_uri; } server { listen 443 ssl; server_name pedrito.inqualityhq.com; 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; server_tokens off; add_header X-Content-Type-Options nosniff; add_header X-Frame-Options DENY; client_max_body_size 20M; 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; proxy_set_header Authorization $http_authorization; proxy_read_timeout 10s; } 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; } location / { return 404; } } ``` Guardar: `Ctrl+O` → `Enter` → `Ctrl+X`. ```bash # Activar el sitio ln -s /etc/nginx/sites-available/pedrito /etc/nginx/sites-enabled/pedrito # Verificar sintaxis nginx -t # Debe decir: syntax is ok / test is successful # Recargar Nginx systemctl reload nginx ``` > **Si `nginx -t` falla:** revisá que las comillas y los `;` estén correctos en el archivo. --- ## 9. Configurar el firewall ```bash # Como root — verificar estado actual ufw status ``` Si dice `Status: inactive` o no tiene las reglas necesarias: ```bash ufw allow OpenSSH ufw allow 'Nginx Full' # puertos 80 y 443 ufw --force enable ufw status ``` Salida esperada: ``` Status: active To Action From -- ------ ---- OpenSSH ALLOW Anywhere Nginx Full ALLOW Anywhere ``` > **Importante:** El puerto 8080 del bot **no** se abre al exterior. Solo Nginx accede a él > internamente desde el mismo servidor. --- ## 10. Iniciar el bot con Docker ### Verificar que docker-compose.yml usa binding correcto ```bash su - pedrito cd /home/pedrito/hechakuaa_bot grep "8080" docker-compose.yml ``` Debe mostrar `127.0.0.1:8080:8080`. Si muestra solo `8080:8080`: ```bash nano docker-compose.yml # Cambiar "8080:8080" por "127.0.0.1:8080:8080" ``` ### Construir la imagen ```bash docker compose build # Tarda 1-3 minutos la primera vez ``` ### Primera prueba: correr en primer plano ```bash docker compose up ``` Deberías ver: ``` 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": "login_exitoso", ...} ``` Si aparece `login_exitoso`, el bot se conectó al PJ correctamente. Detenerlo con `Ctrl+C`. > **Si no aparece `login_exitoso`:** ver [Troubleshooting](#16-troubleshooting). ### Correr en background (modo producción) ```bash docker compose up -d # Verificar que está corriendo: docker compose ps # STATUS debe decir "running" ``` --- ## 11. Activar el modo webhook ### Generar el WEBHOOK_SECRET ```bash python3 -c "import secrets; print(secrets.token_hex(32))" # Salida: 64 caracteres hexadecimales — copiarlos ``` ### Actualizar .env ```bash nano /home/pedrito/hechakuaa_bot/.env ``` Completar estas dos líneas: ```bash WEBHOOK_URL=https://pedrito.inqualityhq.com WEBHOOK_SECRET= ``` ### Reiniciar el bot ```bash cd /home/pedrito/hechakuaa_bot docker compose restart docker compose logs --tail=20 # Buscar: {"event": "webhook_registrado", "url": "https://pedrito.inqualityhq.com/webhook", ...} ``` ### Verificar que el webhook quedó registrado en Telegram ```bash curl -s "https://api.telegram.org/bot/getWebhookInfo" | python3 -m json.tool ``` El campo `url` debe mostrar `https://pedrito.inqualityhq.com/webhook`. --- ## 12. Verificación final ```bash # 1. El contenedor está corriendo docker compose ps # STATUS: running # 2. Logs sin errores docker compose logs --tail=30 # Buscar "login_exitoso" — NO debe aparecer: ERROR, exception, Traceback # 3. Health check responde curl -s https://pedrito.inqualityhq.com/health | python3 -m json.tool # {"status": "ok", ...} # 4. Webhook registrado curl -s "https://api.telegram.org/bot/getWebhookInfo" | python3 -m json.tool # url: https://pedrito.inqualityhq.com/webhook # 5. Permisos del .env ls -la /home/pedrito/hechakuaa_bot/.env # -rw------- 1 pedrito pedrito # 6. Directorio data existe ls -la /home/pedrito/hechakuaa_bot/data/ # pasante.db y snapshot_*.json # 7. HTTP redirige a HTTPS curl -I http://pedrito.inqualityhq.com/health # HTTP/1.1 301 Moved Permanently ``` Si todos pasan, avisarle a Marcos para que pruebe con `/start` en Telegram. --- ## 13. Monitoreo y logs ```bash su - pedrito cd /home/pedrito/hechakuaa_bot # Logs en tiempo real docker compose logs -f # Últimos 50 registros docker compose logs --tail=50 ``` ### Logs de Nginx ```bash # Como root: tail -f /var/log/nginx/access.log tail -f /var/log/nginx/error.log ``` ### Eventos clave en los logs del bot | Evento | Qué significa | |--------|---------------| | `login_exitoso` | Conectó al PJ correctamente | | `polling_tenant_ok` | Revisión completada sin errores | | `polling_tenant_error` | Error al revisar notificaciones | | `bot_notificacion_enviada` | Notificación enviada a Telegram | | `onboarding_completado` | Un abogado completó el registro | | `health_alerta_enviada` | El poller está detenido — alerta al admin | | `webhook_registrado` | Webhook activo con Telegram | --- ## 14. Health check HTTP El bot expone `https://pedrito.inqualityhq.com/health`. ```bash # Sin auth — respuesta mínima pública: curl -s https://pedrito.inqualityhq.com/health | python3 -m json.tool # {"status": "ok", "timestamp": "..."} # Con auth — respuesta completa (reemplazá TOKEN): curl -s -H "Authorization: Bearer " \ https://pedrito.inqualityhq.com/health/history | python3 -m json.tool ``` Si el poller no actualizó en más de `HEALTH_ALERT_HOURS` horas, retorna `"status": "degraded"` y el bot manda una alerta automática al Telegram de Marcos. ### Monitor externo (opcional) - URL: `https://pedrito.inqualityhq.com/health` - Método: `GET` — código esperado: `200` — intervalo: 5 minutos --- ## 15. Cómo actualizar el bot ```bash su - pedrito cd /home/pedrito/hechakuaa_bot git pull docker compose build docker compose up -d docker compose ps docker compose logs --tail=20 ``` > `.env` y `data/` no se tocan con `git pull` — están en `.gitignore`. > **Si hubo cambios en `.env.example`:** > ```bash > diff .env.example .env > ``` ### Migración de credenciales vault v3 (solo al actualizar desde versiones < v0.4.0) Si había abogados registrados antes de v0.4.0, migrar sus credenciales **antes** del restart: ```bash docker compose run --rm pedrito uv run python scripts/migrar_vault_v3.py --dry-run # Verificar output, luego: docker compose run --rm pedrito uv run python scripts/migrar_vault_v3.py ``` --- ## 16. Troubleshooting ### El contenedor no arranca ```bash docker compose logs --tail=50 docker compose ps ``` **`FERNET_KEY inválida`** — la clave en `.env` tiene espacios o comillas. Verificar el valor. **`telegram.error.InvalidToken`** — el `TELEGRAM_BOT_TOKEN` tiene un error. Recopiarlo de @BotFather. **`ModuleNotFoundError`** — reconstruir la imagen: ```bash docker compose build --no-cache && docker compose up -d ``` --- ### El bot arranca pero no responde en Telegram 1. `docker compose ps` — ¿está corriendo? 2. `curl -s "https://api.telegram.org/bot/getWebhookInfo"` — ¿el webhook está registrado? 3. `systemctl status nginx` — ¿Nginx está activo? 4. `curl -s https://pedrito.inqualityhq.com/health` — ¿el health responde? 5. `tail -20 /var/log/nginx/error.log` — ¿hay errores en Nginx? --- ### Error de credenciales del PJ ```bash su - pedrito cd /home/pedrito/hechakuaa_bot uv run python scripts/cifrar_credenciales.py docker compose restart docker compose logs --tail=10 ``` --- ### El certificado SSL venció ```bash certbot renew systemctl reload nginx ``` --- ### El VPS se reinició y el bot no volvió ```bash # El contenedor tiene restart: unless-stopped — debería volver solo. # Si no: su - pedrito cd /home/pedrito/hechakuaa_bot docker compose up -d ``` --- ### Error de permisos en data/ ```bash # Como root: chown -R 1000:1000 /home/pedrito/hechakuaa_bot/data su - pedrito cd /home/pedrito/hechakuaa_bot docker compose restart ``` --- ## 17. Referencia rápida de comandos ### Gestión del bot (como `pedrito`) ```bash cd /home/pedrito/hechakuaa_bot docker compose ps # estado docker compose up -d # iniciar docker compose down # detener docker compose restart # reiniciar docker compose logs -f # logs en tiempo real docker compose logs --tail=50 # últimos 50 registros # Actualización rápida: git pull && docker compose build && docker compose up -d ``` ### Nginx (como `root`) ```bash nginx -t # verificar configuración systemctl reload nginx # recargar sin cortar conexiones systemctl restart nginx # reiniciar completamente tail -f /var/log/nginx/error.log ``` ### SSL ```bash certbot renew # renovar manualmente certbot certificates # ver fecha de vencimiento ``` ### 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 | `/home/pedrito/hechakuaa_bot/data/pasante.db` | | Snapshots | `/home/pedrito/hechakuaa_bot/data/snapshot_*.json` | | Docker Compose | `/home/pedrito/hechakuaa_bot/docker-compose.yml` | | Config Nginx | `/etc/nginx/sites-available/pedrito` | | Logs Nginx | `/var/log/nginx/` | | Certificado SSL | `/etc/letsencrypt/live/pedrito.inqualityhq.com/` | --- *Ante dudas técnicas, contactar a Marcos Benítez.*