Agrega nota en sección 18 de DEPLOY.md sobre correr migrar_vault_v3.py al actualizar desde versiones anteriores a v0.4.0 con tenants existentes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
38 KiB
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)
PARTE B — Servidor: base 3. Conectarse al VPS 4. Actualizar el sistema y crear usuario 5. Instalar Docker 6. Instalar Nginx 7. Clonar el repositorio 8. Configurar el archivo .env 9. Cifrar las credenciales del PJ
PARTE C — HTTPS y producción 10. Obtener certificado SSL con Certbot 11. Configurar Nginx como reverse proxy 12. Configurar el firewall 13. Iniciar el bot con Docker 14. Activar el modo webhook
PARTE D — Operación 15. Verificación final 16. Monitoreo y logs 17. Health check HTTP 18. Cómo actualizar el bot 19. Troubleshooting 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
-
Entrá a bluehost.com con las credenciales de InQuality
-
En el panel, buscá "cPanel" y hacé click
-
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")
-
Buscá
inqualityhq.comen la lista y hacé click en "Manage" -
Hacé click en "Add Record"
-
Completá el formulario así:
Campo Valor Name (o Host) pedritoType ATTL 300(o el mínimo disponible)Value (o Points To) <IP_DE_TU_VPS> -
Hacé click en "Save" o "Add Record"
-
Para verificar que se propagó (puede tardar de 5 minutos a 2 horas):
# 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
nslookupaú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.comdomain 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:
<IP_DEL_VPS>(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á <IP_DEL_VPS> con la IP real del VPS antes de enviar el mensaje.
3. Conectarse al VPS
Desde tu computadora local, abrí una terminal:
ssh root@<IP_DE_LA_VPS>
Si tenés clave SSH configurada en Hostinger (recomendado):
ssh -i ~/.ssh/id_rsa root@<IP_DE_LA_VPS>
Si te pide contraseña, es la del panel de Hostinger.
Tip para Windows: Usá Windows Terminal o PuTTY. 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í
yesy Enter.
4. Actualizar el sistema y crear usuario
Todos estos comandos se ejecutan como root.
4.1 Actualizar paquetes
apt update && apt upgrade -y
Esto puede tardar 2-5 minutos. Esperá que termine.
4.2 Verificar la versión de Ubuntu
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:
# 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
pedritotenga su propio UID.
4.4 Permitir que pedrito use Docker (sin sudo)
# 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)
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@<IP>. 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
apt install -y \
ca-certificates \
curl \
gnupg \
lsb-release
5.2 Agregar el repositorio oficial de Docker
# 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
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
# 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
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 upabre la caja,docker compose downla 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.
# 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:
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:
# 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
# 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:
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:
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.
# 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:
# ── 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
# 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)
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
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.
# 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:
- Cédula de identidad — el número con el que Marcos entra a
apps.csj.gov.py - 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:
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:
nslookup pedrito.inqualityhq.com # Debe retornar la IP de este VPSSi no retorna la IP correcta, esperá y volvé después.
10.1 Instalar Certbot
# Como root:
exit # si estabas como pedrito, volver a root
apt install -y certbot python3-certbot-nginx
10.2 Obtener el certificado
certbot --nginx -d pedrito.inqualityhq.com
Certbot te va a preguntar:
- Tu dirección de email (para avisos de vencimiento) — ponés la de Marcos o la del equipo
- Aceptar los términos de servicio →
Y - 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:
# 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
# Como root:
nano /etc/nginx/sites-available/pedrito
Pegar exactamente este contenido (reemplazá el nombre de dominio si usás uno diferente):
# /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
# 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
systemctl reload nginx
systemctl status nginx
# Debe estar active (running)
11.4 Verificar que Nginx responde
# 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
# 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:
# Como pedrito:
su - pedrito
cd /home/pedrito/hechakuaa_bot
# Editar docker-compose.yml
nano docker-compose.yml
Buscá la línea:
- "8080:8080"
Cambiala por:
- "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
# 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:
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.
13.4 Correr en background (modo producción)
# 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
# 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:
# 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
su - pedrito
nano /home/pedrito/hechakuaa_bot/.env
Completar estas dos líneas:
WEBHOOK_URL=https://pedrito.inqualityhq.com
WEBHOOK_SECRET=<el token que generaste arriba>
Guardar y salir.
14.3 Reiniciar el bot para aplicar el cambio
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
# Reemplazá TU_BOT_TOKEN con el valor real del .env
curl -s "https://api.telegram.org/bot<TU_BOT_TOKEN>/getWebhookInfo" | python3 -m json.tool
Salida esperada:
{
"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:
# 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<TU_BOT_TOKEN>/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
# 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
# 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
# Ú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)
{
"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:
{
"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):
# 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
.envy el directoriodata/no se tocan congit 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.envexistente.# Ver diferencias entre el template nuevo y el .env actual: diff .env.example .env
Actualización especial: migración de credenciales a vault v3 (v0.4.0+)
Si el servidor tenía abogados registrados antes de la versión v0.4.0, sus credenciales en la DB están en formato v1 (cifrado simple). Hay que migrarlas a v3 (PBKDF2 + envelope encryption) una sola vez.
Hacerlo después del git pull y antes del docker compose up -d:
# Dentro del contenedor (o con uv si no usás Docker):
docker compose run --rm pedrito uv run python scripts/migrar_vault_v3.py --dry-run
# Verificar que lista los tenants esperados, luego migrar de verdad:
docker compose run --rm pedrito uv run python scripts/migrar_vault_v3.py
El script:
- Verifica que puede descifrar todos los tenants antes de tocar la DB
- Hace backup automático de la DB (
data/pasante.db.bak) - Si algo falla, no escribe nada — la DB original queda intacta
Si todos los tenants registrados son nuevos (post v0.4.0), este paso no es necesario.
19. Troubleshooting
El contenedor no arranca
# 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:
docker compose build --no-cache
docker compose up -d
Error response from daemon: Cannot connect to the Docker daemon
Docker no está corriendo:
systemctl start docker
docker compose up -d
El bot arranca pero no responde en Telegram
- Verificar que el contenedor está corriendo:
docker compose ps - Verificar que el webhook está registrado:
curl -s "https://api.telegram.org/bot<TOKEN>/getWebhookInfo" - Verificar que Nginx está ok:
systemctl status nginx - Verificar que el health check responde:
curl -s https://pedrito.inqualityhq.com/health - Revisar logs de Nginx:
tail -20 /var/log/nginx/error.log
Error de credenciales del PJ
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
# 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)
# 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<TOKEN>/setWebhook?url=https://pedrito.inqualityhq.com/webhook&secret_token=<WEBHOOK_SECRET>"
El VPS se reinició y el bot no volvió
El contenedor tiene restart: unless-stopped, así que debería volver solo. Si no:
su - pedrito
cd /home/pedrito/hechakuaa_bot
docker compose up -d
Si Docker en sí no arrancó:
systemctl start docker
su - pedrito
cd /home/pedrito/hechakuaa_bot
docker compose up -d
Ver dos instancias corriendo (causa conflictos de Telegram)
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/
# 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)
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)
# 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
# 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.