# Guía de operación — cootrasaravitabotcliente (Podman)

Complemento a `DEPLOY-PODMAN.md`: comandos de monitoreo diario, incidencias resueltas durante el despliegue en este servidor, y procedimiento para conectar el bot a MySQL/MariaDB local del mismo servidor.

---

## 1. Comandos de estado y logs

### Estado general de los contenedores
```bash
podman compose ps
```

### Logs en vivo (streaming)
```bash
podman compose logs -f bot
podman compose logs -f worker
podman compose logs -f redis
```
`Ctrl+C` para salir. Para ver todos los servicios a la vez:
```bash
podman compose logs -f
```

### Últimas N líneas sin quedar en modo streaming
```bash
podman compose logs --tail=100 bot
podman compose logs --tail=100 worker
```

### Uso de recursos en vivo (CPU/RAM), similar a `top`
```bash
podman stats
```

### Inspeccionar un contenedor (red, límites, config aplicada)
```bash
podman inspect cootrasaravitabotcliente_bot_1
```

### Ver variables de entorno reales dentro del contenedor
```bash
podman exec cootrasaravitabotcliente_bot_1 env
```

### Entrar a una shell dentro del contenedor
```bash
podman exec -it cootrasaravitabotcliente_bot_1 sh
```

### Reiniciar un servicio puntual
```bash
podman compose restart bot
podman compose restart worker
```

### Verificar el healthcheck HTTP del bot
```bash
curl -s http://127.0.0.1:3000/health
```

### Verificar si redis responde
```bash
podman exec -it cootrasaravitabotcliente_redis_1 redis-cli ping
```
---

## 2. Incidencias resueltas durante este despliegue

### 2.1 Falta de subuid/subgid (rootless)
**Síntoma:** `no subuid ranges found for user ... in /etc/subuid`

**Solución (como root):**
```bash
sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 cootrasaravitane
podman system migrate
```

### 2.2 Sin proveedor de compose instalado
**Síntoma:** `exec: "docker-compose": executable file not found` / `exec: "podman-compose": executable file not found`

**Solución:** instalar `podman-compose`:
```bash
sudo dnf install -y python3-pip
pip3 install --user podman-compose
```

### 2.3 Dos archivos de compose mezclándose
**Síntoma:** valores duplicados en el JSON "merged" de `podman-compose` (`env_file`, `ports`, `networks` repetidos dos veces).

**Causa:** existían `docker-compose.yml` (de desarrollo) y `compose.yml` (de producción) en el mismo directorio, y se estaban fusionando.

**Solución:** dejar solo el `compose.yml` de producción activo; renombrar/mover el otro:
```bash
mv docker-compose.yml docker-compose.dev.yml.bak
```

### 2.4 Contenedor `bot`/`worker` en `Exited (1)` por variables faltantes
**Síntoma:** el proceso Node sale inmediatamente al arrancar.

**Causa:** `src/config/env.ts` valida variables requeridas (`EMPRESA_ID`, `DB_*`, `META_*`, `CENTRAL_*`, tokens de bridge) y aborta si falta alguna.

**Solución:** completar el `.env` con todas las variables requeridas y recrear:
```bash
podman compose up -d --force-recreate bot worker
```

### 2.5 `RangeError: WebAssembly.instantiate(): Out of memory`
**Síntoma:** el `bot`/`worker` arrancan, conectan a MySQL y Redis, y luego truenan justo al hacer la primera llamada `fetch()` (usada internamente por `undici`).

**Causa:** límite de `ulimit -v` (address space / memoria virtual) demasiado bajo para el usuario rootless, definido en `/etc/security/limits.conf`:
```
cootrasaravitane   hard    as          2194304
```
WebAssembly reserva un bloque grande de memoria virtual al instanciar el parser HTTP de `undici`, y ese límite lo rechaza aunque haya RAM física libre de sobra.

**Solución:**
1. Editar `/etc/security/limits.conf` (como root) y subir el límite:
   ```
   cootrasaravitane   hard    as          8388608
   ```
2. Habilitar `linger` para que el manager de sesión del usuario no dependa de la conexión SSH:
   ```bash
   sudo loginctl enable-linger cootrasaravitane
   ```
3. **Reiniciar completamente el manager de systemd `--user`** del usuario, no solo la sesión SSH — de lo contrario los contenedores heredan el límite viejo aunque la shell interactiva ya muestre el nuevo:
   ```bash
   sudo loginctl terminate-user cootrasaravitane
   ```
   Reconectar por SSH y verificar:
   ```bash
   ulimit -Ha | grep "virtual memory"
   ```
4. Verificar el límite real que ve el proceso del contenedor (no basta con verificarlo en la shell):
   ```bash
   podman inspect --format '{{.State.Pid}}' cootrasaravitabotcliente_bot_1
   cat /proc/<PID>/limits | grep -i "address space"
   ```
5. Recrear los contenedores:
   ```bash
   podman compose up -d --force-recreate bot worker
   ```

### 2.6 Panic de Podman al migrar (`invalid internal status`)
**Síntoma:**
```
ERRO[0000] invalid internal status, try resetting the pause process with "podman system migrate"...
panic: runtime error: invalid memory address or nil pointer dereference
```

**Causa:** al cerrar la sesión SSH sin `linger` habilitado, las network namespaces rootless de los contenedores quedaron huérfanas (el "pause process" murió con la sesión).

**Solución:**
```bash
sudo loginctl enable-linger cootrasaravitane
sudo pkill -u cootrasaravitane -f conmon
sudo pkill -u cootrasaravitane -f pasta
sudo pkill -u cootrasaravitane -f slirp4netns
sudo umount -l /run/user/<UID>/netns/rootless-netns-* 2>/dev/null
sudo umount -l /run/user/<UID>/netns/netns-* 2>/dev/null
sudo rm -rf /run/user/<UID>/netns/*
```
Reconectar por SSH y correr:
```bash
podman system migrate
podman ps -a
```

### 2.7 Puerto 3000 ocupado por `rootlessport` huérfano tras una caída
**Síntoma:**
```
Error: rootlessport listen tcp 0.0.0.0:3000: bind: address already in use
```
seguido de que `worker` también falla porque su contenedor de referencia (`bot`) quedó en estado `Created` y no `Running`:
```
Error: generating dependency graph for container ...: container ... depends on container ... not found in input list: no such container
```

**Causa:** cuando el servicio se interrumpe de forma abrupta (crash, `kill`, reinicio sin bajar antes el compose), el proceso auxiliar `rootlessport` que hace el port-forwarding rootless queda vivo fuera del ciclo de vida del contenedor. Podman no lo rastrea como parte del contenedor, así que al recrear `bot` el puerto ya está tomado por el proceso viejo.

**Diagnóstico:**
```bash
ss -tulpn | grep 3000
```
Si aparece un `rootlessport` con un PID viejo asociado, es un huérfano de una corrida anterior.

**Solución:**
```bash
# 1. Matar el proceso huérfano (el PID lo da el comando anterior)
sudo kill -9 <PID_ROOTLESSPORT>

# 2. Confirmar que el puerto quedó libre
ss -tulpn | grep 3000

# 3. Bajar y limpiar el estado de compose (bot/worker suelen quedar en Created)
podman compose down
# si down falla, forzar:
podman rm -f cootrasaravitabotcliente_bot_1 cootrasaravitabotcliente_worker_1 cootrasaravitabotcliente_redis_1

# 4. Levantar de nuevo
podman compose up -d
podman compose ps
```

### 2.8 `getaddrinfo ENOTFOUND redis` con contenedores `healthy` (DNS interno duplicado)
**Síntoma:** `bot`/`worker` están `Up (healthy)` pero el log muestra en vivo:
```
Error: getaddrinfo ENOTFOUND redis
    at GetAddrInfoReqWrap.onlookup [as oncomplete] (node:dns:111:26) {
  errno: -3008,
  code: 'ENOTFOUND',
  syscall: 'getaddrinfo',
  hostname: 'redis'
}
```
y `podman exec ... getent hosts redis` no devuelve nada.

**Causa:** este backend de Podman usa **CNI** (no Netavark), confirmado con:
```bash
podman info | grep -A3 networkBackend
```
En CNI, el DNS interno de la red (para que `redis`/`bot`/`worker` se resuelvan por nombre) lo sirve el plugin `dnsname` vía un proceso `dnsmasq` por red. Tras una caída/`kill` previo (ver 2.7), puede quedar un `dnsmasq` huérfano de la corrida anterior corriendo en paralelo con el nuevo, sirviendo registros DNS desactualizados o compitiendo por el mismo socket/config:
```bash
ps aux | grep dnsname
# ejemplo de síntoma: dos procesos dnsmasq para la misma red, con fechas distintas
# .../containers/cni/dnsname/cootrasaravitabotcliente_bot-net/dnsmasq.conf
```

**Solución:**
```bash
# 1. Identificar y matar TODOS los dnsmasq de la red afectada
ps aux | grep dnsname
kill -9 <PID_DNSMASQ_1> <PID_DNSMASQ_2>

# 2. Bajar el compose completo
podman compose down

# 3. Verificar que no quede nada huérfano
ps aux | grep -E "dnsmasq|rootlessport" | grep -v grep

# 4. Levantar limpio
podman compose up -d

# 5. Verificar resolución DNS interna
podman exec -it cootrasaravitabotcliente_bot_1 getent hosts redis
```
Debe devolver una IP dentro de la subred de la red (ej. `10.89.0.x`).

**Nota:** esto es la misma familia de problema que 2.6 y 2.7 — procesos auxiliares rootless (`rootlessport`, `dnsmasq`, `pasta`/`slirp4netns`) que no se limpian solos cuando el servicio muere sin pasar por `podman compose down`. El fix estructural es el mismo: `linger` habilitado (ver sección siguiente) y, quitando bandaids, evaluar migrar de CNI a Netavark (`aardvark-dns`), que maneja estos fallos de forma más robusta.

---

## 3. Conexion a MySQL/MariaDB local (mismo servidor)

El bot y los workers corren en contenedores Podman; MySQL/MariaDB de la central corre en el **host**. No uses `localhost` en `.env`: dentro del contenedor apunta al propio contenedor.

### 3.1 Variables a configurar en `.env`

```bash
DB_HOST=host.containers.internal
DB_PORT=3306
DB_USER=usuario_mysql
DB_PASSWORD=contraseña_mysql
DB_NAME=nombre_base_datos
DB_SSL_ENABLED=false
```

`compose.yml` ya define `extra_hosts: host.containers.internal:host-gateway` en `bot` y `worker`.

### 3.2 Requisitos previos en el servidor (MySQL/MariaDB del host)

1. **Usuario y privilegios:** el usuario del bot debe tener SELECT/INSERT/UPDATE sobre las tablas que usa (ver `sql/init/verify-bot-schema.sql` y seccion D del informe).
2. **Escucha local:** MySQL debe aceptar conexiones desde la red de Podman. Opciones habituales (elige una, sin abrir 3306 a Internet):
   - `bind-address = 0.0.0.0` o incluir la IP del gateway de la red `bot-net` (solo si el firewall del host lo restringe).
   - En muchos despliegues rootless, `host.containers.internal` + `host-gateway` basta sin cambiar `bind-address` si el servicio ya escucha en todas las interfaces locales.
3. **Esquema:** ejecutar `sql/init/verify-bot-schema.sql` en la base de la central si aun no existen tablas/columnas del bot (script idempotente, no borra datos).
4. **No crear** un contenedor MySQL adicional ni otra base de datos solo para el bot.

### 3.3 Aplicar el cambio

Tras editar `.env`:

```bash
podman compose up -d --force-recreate bot worker
```

### 3.4 Verificar conexion

```bash
podman compose logs --tail=50 bot
```

Buscar `"Configuracion activa del pool de MySQL"` con `host: host.containers.internal` y `ssl: false`, sin errores:

- `ECONNREFUSED` → MySQL no escucha desde la red del contenedor o `DB_HOST` incorrecto.
- `ER_ACCESS_DENIED_ERROR` → usuario/contraseña o privilegios.
- `ETIMEDOUT` → firewall bloqueando entre red Podman y host.

Desde el contenedor (prueba directa):

```bash
podman exec -it cootrasaravitabotcliente_bot_1 sh -c "node -e \"const mysql=require('mysql2/promise'); mysql.createConnection({host:process.env.DB_HOST,user:process.env.DB_USER,password:process.env.DB_PASSWORD,database:process.env.DB_NAME}).then(c=>c.query('SELECT 1')).then(r=>{console.log('OK',r[0]);process.exit(0)}).catch(e=>{console.error(e);process.exit(1)})\""
```

Confirmar healthcheck:

```bash
curl -s http://127.0.0.1:3000/health
podman compose ps
```

---

## Subir ajustes al repositorio
```bash
git pull
podman compose down
podman compose build --no-cache  #Compilar sin tener encuenta el caché.
podman compose build
podman compose up -d
podman compose ps
podman ps -a
```
## En caso de que los contenedores se queden atascados o no arranque, validar.
```bash 
# Desde root o con sudo, habilitar linger
loginctl enable-linger cootrasaravitane
# Verificar que quede habilitado
loginctl show-user cootrasaravitane | grep Linger

# Script de verificación, si se cae los servicios correr este comando antes de levantar los servicios, se debe primero ejecutar el podman compose down
ps aux | grep -E "rootlessport|dnsmasq" | grep -v grep

# Si aparece algo, matar esos PIDs (ver incidencias 2.7 y 2.8) antes de "podman compose up -d"
# kill -9 <PID>

# Confirmar también que el puerto del bot quedó libre
ss -tulpn | grep 3000
```

Ver detalle completo de estos dos casos (puerto ocupado y DNS interno duplicado) en las secciones **2.7** y **2.8**.