Problema

En entornos de auto‑hosting es frecuente querer ofrecer a los usuarios una forma sencilla de archivar páginas web sin salir de Discord. El patrón típico consiste en:

  1. Un bot que recibe una URL a través de un mensaje.
  2. El bot abre la página con un motor sin cabeza, genera un archivo MHTML y lo guarda en un almacenamiento permanente.

Los obstáculos aparecen cuando el bot necesita escribir en un NAS o en otro recurso compartido, cuando las dependencias del motor sin cabeza son incompatibles con la distribución del servidor, o cuando el proceso se cae al cerrar la sesión SSH. En muchos casos el bot funciona en pruebas locales pero falla en producción por problemas de montaje, versiones de Node, o gestión de procesos.

Causa

Los fallos más habituales provienen de tres áreas:

  • Montaje del share CIFS/SMB – Si el punto de montaje no está persistente, el bot no encuentra la ruta de destino y aborta. Permisos incorrectos en el archivo de credenciales o en el punto de montaje también provocan “permission denied”.
  • Dependencias de Chromium – Puppeteer necesita bibliotecas del sistema (libnss3, libatk‑bridge2.0, etc.). En distribuciones minimalistas esas librerías faltan y el lanzamiento del navegador termina con errores de “cannot open shared object file”.
  • Gestión del proceso – Ejecutar node discord-agent.js directamente funciona mientras la sesión SSH está activa. Al cerrar la terminal el proceso se mata, y al reiniciar el host el bot no vuelve a iniciarse.

A menudo aparecen combinaciones: un NAS montado con la versión 2.0 de SMB que requiere la opción vers=3.0, o una instalación de Node vía apt que deja paquetes rotos y obliga a forzar la instalación.

Solución

Una estrategia reutilizable cubre los tres puntos críticos:

  1. Montar el share de forma persistente y segura

    • Instalar cifs-utils.
    • Crear un archivo de credenciales con permisos 600.
    • Añadir la línea adecuada a /etc/fstab usando la versión SMB que soporte el NAS (generalmente 3.0).
    • Probar el montaje con mount -a y validar la escritura con touch.
  2. Instalar Node.js y Chromium con sus dependencias

    • Usar el script oficial de NodeSource para obtener la última versión LTS (v20 al momento).
    • Evitar el paquete npm de apt; el instalador de Node incluye su propio npm.
    • Instalar las bibliotecas de Chromium listadas en la documentación de Puppeteer. En Debian/Ubuntu basta con un único apt-get install que incluye todas.
  3. Ejecutar el bot bajo PM2

    • PM2 mantiene el proceso activo, lo reinicia tras caídas y genera scripts de inicio automático (pm2 startup).
    • Guardar el estado con pm2 save para que se recupere tras reboot.

Con este enfoque el bot puede recibir URLs, generar MHTML y depositar los archivos directamente en el NAS sin intervención manual. Además, el uso de PM2 permite actualizar el código y aplicar pm2 restart sin perder disponibilidad.

Alternativas prácticas

  • Docker – Empaquetar el bot y sus dependencias en un contenedor simplifica la gestión de versiones, pero requiere montar el share dentro del contenedor (-v /mnt/mhtml_archive:/archive).
  • VPN/Tunnel – Si el NAS está detrás de una red aislada, crear un túnel SSH o usar WireGuard permite montar el share como si estuviera local.

Cuándo aplicar esta solución

Aplica cuando:

  • Necesitas archivar páginas web de forma automática desde Discord o cualquier otro chat bot.
  • El destino de los archivos es un NAS, un disco compartido o un volumen montado vía CIFS/SMB.
  • El servidor corre una distribución basada en Debian/Ubuntu y se prefiere una instalación nativa de Node.js.

No es la mejor opción si:

  • El entorno está completamente contenerizado y prefieres orquestación con Kubernetes.
  • El NAS solo soporta NFS y no CIFS; en ese caso se cambia la configuración de montaje.

Código

# 1. Instalación de utilidades CIFS y creación del punto de montaje
sudo apt-get update
sudo apt-get install -y cifs-utils

sudo mkdir -p /mnt/mhtml_archive
sudo nano ~/.smbcredentials   # agrega: username=USUARIO  password=CONTRASEÑA
chmod 600 ~/.smbcredentials

# 2. Añadir entrada a /etc/fstab (reemplaza los placeholders)
echo "//NAS_IP/ShareName /mnt/mhtml_archive cifs credentials=/home/$USER/.smbcredentials,uid=$(id -u),gid=$(id -g),iocharset=utf8,vers=3.0,noperm 0 0" | sudo tee -a /etc/fstab

# 3. Montar y validar
sudo mount -a
touch /mnt/mhtml_archive/prueba.txt && echo "ok" > /mnt/mhtml_archive/prueba.txt

# 4. Instalar Node.js v20 (NodeSource)
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt-get install -y nodejs

# 5. Instalar dependencias de Chromium
sudo apt-get install -y libnss3 libnspr4 libatk1.0-0 libatk-bridge2.0-0 libcups2 \
    libdrm2 libxkbcommon0 libxcomposite1 libxdamage1 libxfixes3 libxrandr2 \
    libgbm1 libasound2

# 6. Preparar proyecto
mkdir -p ~/mhtml-discord-bot && cd ~/mhtml-discord-bot
npm init -y
npm install discord.js puppeteer

# 7. Guardar script (discord-agent.js) – copia el contenido del post original y ajusta el token

# 8. Instalar y lanzar PM2
sudo npm install -g pm2
pm2 start discord-agent.js --name "mhtml-bot"
pm2 save
pm2 startup   # sigue las instrucciones que PM2 muestra

Verificación

  1. Montaje – Ejecuta df -h | grep mhtml_archive. La salida debe mostrar el share montado y espacio disponible.
  2. Permisosls -l /mnt/mhtml_archive debe listar el archivo prueba.txt con tu UID/GID.
  3. Bot activopm2 status muestra mhtml-bot en estado online.
  4. Prueba funcional – Envía !backup https://example.com al canal de Discord. El bot debe responder con una reacción y, tras unos segundos, publicar el archivo MHTML o indicar que se guardó en el NAS.
  5. Persistencia – Reinicia el servidor (sudo reboot). Tras el arranque, verifica nuevamente pm2 status y que el share sigue montado.

Notas adicionales

  • Captchas y Cloudflare – Algunos sitios lanzan desafíos que Puppeteer no puede resolver sin intervención humana. En esos casos, considera usar un proxy rotativo o ejecutar el bot detrás de una VPN que ya tenga la sesión autenticada.
  • Tamaño de archivo – Discord impone un límite de 25 MiB por archivo. El script ya incluye una comprobación; si el MHTML supera ese umbral, el bot solo guarda en el NAS y notifica al usuario.
  • Actualizaciones de Puppeteer – Cada versión mayor de Chromium puede requerir nuevas librerías. Si el bot deja de lanzar, revisa los cambios de dependencias en la página de releases de Puppeteer.
  • Seguridad – Mantén el archivo .smbcredentials fuera del repositorio y usa permisos restrictivos. Si el bot necesita acceso a varios shares, crea un usuario de solo lectura/escritura dedicado en el NAS.
  • Logs – PM2 guarda logs en ~/.pm2/logs. Configura una rotación con logrotate si el bot genera mucho tráfico.