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

21 KiB
Raw Permalink Blame History

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
  2. Conectarse al VPS
  3. Agregar pedrito al grupo docker

PARTE B — Código y configuración 4. Clonar el repositorio 5. Configurar el archivo .env 6. Cifrar las credenciales del PJ

PARTE C — HTTPS y producción 7. Obtener certificado SSL con Certbot 8. Configurar Nginx como reverse proxy 9. Configurar el firewall 10. Iniciar el bot con Docker 11. Activar el modo webhook

PARTE D — Operación 12. Verificación final 13. Monitoreo y logs 14. Health check HTTP 15. Cómo actualizar el bot 16. Troubleshooting 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

ssh root@93.127.136.210

Si tenés clave SSH configurada en Hostinger:

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.

# 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

# 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

mkdir -p /home/pedrito/hechakuaa_bot/data

Salir a root para el siguiente paso:

exit

Ajustar permisos del directorio data para Docker

El contenedor corre con UID 1000. El directorio data/ necesita ser escribible por ese UID:

# 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

# 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:

# ── 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+OEnterCtrl+X.

Generar FERNET_KEY (si Marcos no te la dio)

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)

curl -LsSf https://astral.sh/uv/install.sh | sh
source $HOME/.local/bin/env
uv --version

Restringir permisos del .env

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

# 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:

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).

# 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

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.

# Como root:
nano /etc/nginx/sites-available/pedrito

Pegar exactamente este contenido:

# /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+OEnterCtrl+X.

# 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

# Como root — verificar estado actual
ufw status

Si dice Status: inactive o no tiene las reglas necesarias:

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

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:

nano docker-compose.yml
# Cambiar "8080:8080" por "127.0.0.1:8080:8080"

Construir la imagen

docker compose build
# Tarda 1-3 minutos la primera vez

Primera prueba: correr en primer plano

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.

Correr en background (modo producción)

docker compose up -d

# Verificar que está corriendo:
docker compose ps
# STATUS debe decir "running"

11. Activar el modo webhook

Generar el WEBHOOK_SECRET

python3 -c "import secrets; print(secrets.token_hex(32))"
# Salida: 64 caracteres hexadecimales — copiarlos

Actualizar .env

nano /home/pedrito/hechakuaa_bot/.env

Completar estas dos líneas:

WEBHOOK_URL=https://pedrito.inqualityhq.com
WEBHOOK_SECRET=<el token que generaste arriba>

Reiniciar el bot

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

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

# 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

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

# 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.

# 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

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:

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:

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

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:

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

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ó

certbot renew
systemctl reload nginx

El VPS se reinició y el bot no volvió

# 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/

# 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)

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)

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

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.