post
Una carpeta que se procesa sola
Problema
Los libros que compro o descargo llegan en PDF y en EPUB, y el Kindle no se lleva bien con la mitad. El ritual era siempre el mismo: copiar el archivo al PC, abrir Calibre de escritorio, convertir, añadir a la biblioteca, mover el original a algún sitio y olvidarme de dónde lo dejé.
Quería quitar el ritual entero: dejar el archivo en una carpeta del NAS desde cualquier máquina de casa y que en unos minutos estuviera en la biblioteca, convertido si hacía falta, sin tocar nada más.
Solución
Tres piezas, ninguna nueva:
- el NAS (OpenMediaVault) exporta por NFS dos carpetas:
incoming, donde se deja el archivo, yebooks, donde vive la biblioteca de Calibre; - un contenedor LXC en Proxmox con Calibre headless, que monta esas dos carpetas y publica la biblioteca por HTTP con
calibre-server; - un script de bash que recorre
incoming, y un timer de systemd que lo dispara cada cinco minutos.
El script no vigila la carpeta ni escucha eventos del sistema de archivos. Se despierta, recorre lo que haya, y se va. Es más tonto y se rompe menos.
incoming/ ← aquí se deja el archivo
processed/ ← el original, después de entrar bien
unsupported/ ← lo que no es PDF ni EPUB
error/ ← lo que falló al convertir o al indexar
ebooks/ ← la biblioteca (metadata.db vive aquí)
Lo que importa de esas carpetas: un archivo nunca se queda en
incoming en un estado ambiguo. O entró, o no es del tipo correcto,
o falló, y en los tres casos está en un sitio distinto.
Ejecución
El contenedor
LXC privilegiado (unprivileged: 0) con nesting=1, 2
vCPU y 2 GB de RAM. Privilegiado porque con NFS de por medio la gestión de
permisos se convierte en su propio proyecto, y esto corre en la red de casa.
Los montajes van en /etc/pve/lxc/103.conf:
mp0: /mnt/pve/omv-books/books/incoming,mp=/mnt/incoming
mp1: /mnt/pve/omv-books/books/ebooks,mp=/mnt/ebooks
Calibre se instala con el binario oficial, no con el paquete de la distro:
wget -O- https://download.calibre-ebook.com/linux-installer.sh | sudo bash
El script
Vive en /usr/local/bin/calibre-process.sh. La conversión sale a un
directorio temporal, para que lo que se archiva en processed sea
exactamente lo que llegó y no una versión modificada.
#!/bin/bash
INCOMING="/mnt/incoming"
PROCESSED="/mnt/incoming/processed"
UNSUPPORTED="/mnt/incoming/unsupported"
ERROR_FOLDER="/mnt/incoming/error"
LIBRARY="/mnt/ebooks"
TEMP_DIR="/tmp/ebook_conversion_$$"
mkdir -p "$TEMP_DIR"
trap 'rm -rf "$TEMP_DIR"' EXIT
for FILE in "$INCOMING"/*; do
[ -e "$FILE" ] || continue # no files
[ -d "$FILE" ] && continue # skip directories
EXT="${FILE##*.}"
BASENAME=$(basename "$FILE")
LOWER=$(echo "$EXT" | tr '[:upper:]' '[:lower:]')
case "$LOWER" in
pdf)
echo "Converting PDF -> EPUB: $BASENAME"
TEMP_OUT="$TEMP_DIR/${BASENAME%.pdf}.epub"
if ebook-convert "$FILE" "$TEMP_OUT" 2>/dev/null; then
if calibredb add --with-library "$LIBRARY" "$TEMP_OUT" 2>/dev/null; then
mv "$FILE" "$PROCESSED/$BASENAME"
mv "$TEMP_OUT" "$LIBRARY/${BASENAME%.pdf}.epub"
echo "SUCCESS: $BASENAME processed"
else
echo "ERROR: calibredb failed for $BASENAME"
mv "$FILE" "$ERROR_FOLDER/$BASENAME"
mv "$TEMP_OUT" "$ERROR_FOLDER/${BASENAME%.pdf}.epub" 2>/dev/null
fi
else
echo "ERROR: ebook-convert failed for $BASENAME"
mv "$FILE" "$ERROR_FOLDER/$BASENAME"
fi
;;
epub)
echo "Adding EPUB: $BASENAME"
if calibredb add --with-library "$LIBRARY" "$FILE"; then
mv "$FILE" "$PROCESSED/$BASENAME"
echo "SUCCESS: $BASENAME indexed"
else
echo "ERROR: calibredb failed for $BASENAME"
mv "$FILE" "$ERROR_FOLDER/$BASENAME"
fi
;;
*)
echo "Skipping unsupported file: $BASENAME"
mv "$FILE" "$UNSUPPORTED/$BASENAME" 2>/dev/null || echo "Error: failed to move $BASENAME"
;;
esac
done
Dos detalles que sí fueron decisiones. El trap ... EXIT borra el
temporal pase lo que pase, incluso si el script muere a mitad. Y el EPUB no se
convierte: se indexa tal cual, porque convertir EPUB a EPUB solo sirve para
estropear el formato.
El disparador
Un servicio oneshot más un timer, en vez de cron. La ventaja
práctica es que la salida del script queda en journalctl sin
montar nada más.
# /etc/systemd/system/process-books.service
[Unit]
Description=Process incoming books for Calibre
[Service]
Type=oneshot
ExecStart=/usr/local/bin/calibre-process.sh
# /etc/systemd/system/process-books.timer
[Unit]
Description=Run book processing script every 5 minutes
[Timer]
OnBootSec=2min
OnUnitActiveSec=5min
Unit=process-books.service
[Install]
WantedBy=timers.target
sudo systemctl daemon-reload
sudo systemctl enable --now process-books.timer
journalctl -u process-books -f
Cómo ha funcionado
Lleva meses corriendo y hace lo que tenía que hacer: dejo el archivo, me voy, y el libro acaba en el Kindle. El día a día son cero intervenciones.
Lo que sí dio guerra al montarlo:
- El binario, no el paquete.
ebook-converttiene que ser el mismo Calibre quecalibredb, o la biblioteca se queja de la versión demetadata.db. Database is locked.calibre-serverycalibredbescribiendo a la vez sobre el mismometadata.dben NFS. Se ve confuser -v /mnt/ebooks/metadata.db.- Errores de Qt y
QColoren el log. Son ruido: Calibre intenta generar la portada sin entorno gráfico. El libro entra igual.
Lo que todavía no maneja, escrito aquí para acordarme: un archivo que se está
copiando cuando el timer entra se procesa a medias y acaba en
error; no hay flock, así que dos ejecuciones pueden
solaparse si un lote grande dura más de cinco minutos; y no hay comprobación
de duplicados, el mismo libro dos veces entra dos veces.