Files
hechakuaa_bot/DEPLOY.md
markosbenitez ec751e5414 docs: DEPLOY.md adaptado al estado real del servidor
Versión para VPS donde Docker, Nginx y usuario pedrito ya existen.
Elimina: instalación de Docker, Nginx, creación de usuario, configuración DNS.
Agrega: tabla de estado inicial, nota sobre docker-compose v1 vs v2.
De 20 secciones (60-90 min) a 17 secciones (30-45 min).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 19:48:29 -03:00

780 lines
21 KiB
Markdown
Raw Permalink 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.
# 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:** 3045 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=<el token que generaste arriba>
```
### 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<TU_BOT_TOKEN>/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<TU_BOT_TOKEN>/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 <HEALTH_AUTH_TOKEN>" \
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<TOKEN>/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.*