Compilar wkhtmltopdf para debian bookworm

wkhtmltopdf de momento es la única forma que tengo para generar PDF en python, tras un upgrade de sistema operativo en un servidor tengo la necesidad de actualizar también a la última versión wkhtmltopdf pero al ser un proyecto tan viejo, no han preparado paquetes deb con el qt activado en su repositorio (recuerdo que la versión que viene en los repositorios de debian, no trae qt activado).

No queda otra que compilar los sources y al hacerlo me he encontrado con una gran aventura mañanera (documentación que brilla por su ausencia), así que aquí dejo la solución:

# cd ~
# git clone https://github.com/wkhtmltopdf/wkhtmltopdf.git
# cd wkhtmltopdf
# mkdir build
# cd build

Ahora necesitamos instalar las dependencias necesarias para poder compilar wkhtmltopdf

# apt -y install build-essential xorg libxrender1 libxext6 libfontconfig1 libssl-dev git qtbase5-dev qt5-qmake libqt5webkit5-dev g++ cmake bison flex gperf ruby python3 pkg-config libglu1-mesa-dev libqt5xmlpatterns5-dev libqt5svg5-dev

Y lo compilamos

# qmake ../wkhtmltopdf.pro
# make
# make install

Ahora creo los enlaces simbólicos necesarios para que mi aplicación de django que tiene configurada una ruta específica de donde se encuentran los binarios, los encuentre

# cd /usr/local/bin
# ln -s /usr/bin/wkhtmltopdf .
# ln -s /usr/bin/wkhtmltoimage .

Y listos.

La recomendación del desarrollador es que nos migremos a puppeteer. …

Montar RAID de un NAS con Linux

Hace un par de semanas que estoy trabajando en un caso que tengo varios discos duros y unidades de memoria cifradas con LUKS2 y no es posible conocer la contraseña. Quien la puso no está y el trabajo consiste en tratar de recuperar todos los datos posibles.

Dentro de los dispositivos a localizar datos se encuentra un NAS “NETGEAR ReadyNAS” con 4 discos de 4Tb y del cuál no dispongo del alimentador y el que tengo no funciona. El otro día analicé los discos de forma individual pero evidentemente estaban en RAID y precisaba de una máquina que me permitiese conectar como mínimo 6 discos.

Hoy he puesto los 4 discos en el servidor, con el hd del sistema y otro para almacenar los datos (por esto 6 discos) y he montado el raid usando mdadm, basándome en qué el NAS como es común suelen usar Linux.

Lo primero será ver si ha detectado los discos

root@IAIA-Capa8:~# lsblk
NAME   MAJ:MIN RM   SIZE RO TYPE MOUNTPOINTS
sda      8:0    0 476.9G  0 disk 
├─sda1   8:1    0   512M  0 part /boot/efi
├─sda2   8:2    0 475.5G  0 part /
└─sda3   8:3    0   976M  0 part [SWAP]
sdb      8:16   0 476.9G  0 disk 
└─sdb1   8:17   0 476.9G  0 part /mnt/hd1
sdc      8:32   0   2.7T  0 disk 
├─sdc1   8:33   0     4G  0 part 
├─sdc2   8:34   0   512M  0 part 
└─sdc3   8:35   0   2.7T  0 part 
sdd      8:48   0   3.6T  0 disk 
└─sdd1   8:49   0   3.6T  0 part /mnt/hd2
sde      8:64   0   2.7T  0 disk 
├─sde1   8:65   

Eliminar el fondo de un vídeo

Hoy me ha llegado otro reto que he terminado resolviendo con IA :)

Se trata de un vídeo en el que aparece una persona y detrás una fea y arrugada sábana blanca que en el vídeo se ve amarillenta. El pedido ha sido eliminar el fondo.

Las opciones iniciales eran hacerlo con kdenlive o con blender y muchas horas de marcar puntitos para cada uno de los frames. El vídeo en cuestión tiene un total de 7933 frames y como que no…

He empezado a buscar opciones y he encontrado clipdrop (cerrado aunque tiene opción de API) y he decidido seguir buscando. En la búsqueda he encontrado backgroundremover interesante pero con resultados deplorables y finalmente rembg (con mejores resultados pero enfocado a imágenes).

Así que de nuevo la solución ha sido crear un script. Esta vez me he decantado por bash.

#!/bin/bash

# Fix video's FPS
echo "Converting video to 30 FPS..."
ffmpeg -i video.mp4 -r 30 -c:v libx264 -crf 18 -preset fast -c:a copy video_30.mp4

# Crea temp directories
#mkdir -p original_images
#mkdir -p processed_images

# Generate one image for each frame and save to original_images directory
echo "Extracting frames as images..."
ffmpeg -i video_30.mp4 -vf "fps=30" original_images/frame_%04d.png

# Delete the background of each image of original_images to processed_images
echo "Removing backgrounds from images..."
for img in original_images/frame_*.png; do
    output_name="processed_images/$(basename "$img")"
    rembg i "$img" "$output_name"
done

# Join all the images from processed_images to a video and use audio from video_30.mp4
echo "Combining processed images into a video with 

Servidor RTSP + ffmpeg

Seguimos con lo del proyecto de fin de semana de montar un punto de acceso wifi con un servidor de streaming.
En el post mencionada mostraba como usar icecast2 como servidor de streaming, pero al empezar a conectar dispositivos me he encontrado con una cosa un tanto molesta, que la sincronización era malísima. Icecast2 funciona sobre el protocolo http y no era lo que quería hacer exactamente, sino hacerlo sobre RTSP. Así que en este post veremos como configurar un servidor RTSP y enviaremos el stream de MPD al servidor de streaming RTSP.

Lo primero será descargar el servidor y lo ejecutamos:

root@raspberrypi:~# mkdir mediamtx
root@raspberrypi:~# cd mediamtx
root@raspberrypi:~# wget https://github.com/bluenviron/mediamtx/releases/download/v1.9.0/mediamtx_v1.9.0_linux_armv7.tar.gz
root@raspberrypi:~# tar xvzf mediamtx_v1.9.0_linux_armv7.tar.gz
root@raspberrypi:~# ./mediamtx

Para hacer pruebas de como hacer el stream de un fichero mp3 he usado inicialmente gstreamer (que me ha llevado un buen rato para hacerlo funcionar)

root@raspberrypi:~# apt-get install gstreamer1.0-rtsp gstreamer1.0-tools

root@raspberrypi:~# gst-launch-1.0 filesrc location=/home/laura/Desktop/2024_09-07-Laura_Mora_Aubert-Silent-P9-200.mp3 ! decodebin ! audioresample ! audioconvert ! opusenc ! audio/x-opus, mapping=stream1 ! rtspclientsink location=rtsp://localhost:8554/test 

root@raspberrypi:~# gst-launch-1.0 filesrc location=/home/laura/Desktop/2024_09-07-Laura_Mora_Aubert-Silent-P9-200.mp3 ! decodebin ! audioresample ! audioconvert ! voaacenc ! audio/mpeg, mapping=/stream1 ! rtspclientsink location=rtsp://localhost:8554/test 

Me he encontrado algunos problemas con el códec que necesitaba, pero esta página me ha ayudado.

the stream doesn't contain any supported codec, which are currently H265, H264, Opus, MPEG-4 Audio

Finalmente en MPD modificamos el fichero /etc/mpd.conf y añadimos un nuevo audio_output

root@raspberrypi:~# vi /etc/mpd.conf
audio_output {
     name            "pipe to ffmpeg"
     type            "pipe"
     enabled         "yes"
     format          "48000:16:2"
     command         "ffmpeg -loglevel error -hide_banner 

Montar punto de acceso wifi y servidor de streaming con raspberry pi 5

Hace unos meses que descubrí las Silent Disco, varios auriculares Bluetooth conectados a un sólo emisor. Esta solución tiene un coste un poco elevado así que en otras he visto como se comparte un audio y luego los asistentes se sincronizan como pueden para bailar todos al unísono. En este post traigo una opción intermedia. Un dispositivo raspberry pi 5 reproduce una playlist que la manda a un icecast2 en local. La raspberry Pi 5 al tener wifi, crea un punto de acceso donde los asistentes se conectan al punto de acceso y configuran con VLC el stream de icecast2.

Script para generar el fichero sitemap.xml de una web estática

Hace unos años hice el crawling de la web antigua de kaosenlared. Ahora tiempo mas tarde el analytics de google les estaba dando muchos errores de páginas no encontradas, así que he tenido que generar los ficheros sitemap.xml para que google indexe el sitio.

He hecho un primer script para listar todas las páginas index.html del directorio y generar el fichero sitemap.xml que ocupaba la friolera de 150Mb pero al metérselo a google analytics daba errores de tamaño de archivo. Buscando los límites, nos encontramos que el sitemap.xml no puede ser más grande de 50Mb ni contener más de 50.000 entradas. Así que ha sido necesario crear un índice y separar los sitemap.xml en ficheros más pequeños.

A continuación el script de como lo he hecho

#!/bin/bash

# Directori on es troben les pàgines html
DIRECTORI=/home/virtualmin/archivo.kaosenlared.net/public_html

# URL base del lloc web
URL_BASE=https://archivo.kaosenlared.net

current_date=$(date -u +"%Y-%m-%dT%H:%M:%SZ")

# Funció per generar el sitemap.xml
generar_sitemap() {
  # Initialize the URL count and the file number
  url_count=0
  file_number=1

  # Initialize the sitemap file name
  sitemap_file="sitemap_${file_number}.xml"
  sitemap_index="sitemap.xml"

  # Inicialitza el fitxer sitemap.xml
  echo '<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">' > $sitemap_index

  echo '<?xml version="1.0" encoding="UTF-8"?>' > $sitemap_file
  echo '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">' >> $sitemap_file


  echo "  <sitemap>" >> $sitemap_index
  echo "    <loc>$URL_BASE/$sitemap_file</loc>" >> $sitemap_index
  echo "    <lastmod>$current_date</lastmod>" >> $sitemap_index
  #echo "    <changefreq>monthly</changefreq>" >> $sitemap_index
  echo "  </sitemap>" >> $sitemap_index


  # Recorre els fitxers html del directori i subdirectoris
  find $DIRECTORI -type f -name "*.html" -not -path "*mailto*" -not -path "* *" -not -path "*&*" | while read 

Django: peticiones wsgi y asgi simultaneas (htmx y websockets)

Muy feliz de finalmente tenerlo funcionando! no sé si es la mejor forma, pero la cuestión es que funciona :D

Estos días me estoy peleando en hacer funcionar un chatbot con stream de openai sobre django. Me ha llevado un par de días de hacerlo funcionar pero aquí lo tenemos :D

Básicamente después de perderme entre decenas de páginas de documentación y abrir y cerrar otras decenas más de pestañas del navegador, lo que terminé haciendo fue localizar una aplicación que sí hacía lo que yo quería y luego la he implementado a mi aplicación. La aplicación que funcionaba like a charm en local es Django OpenAI-Powered Chatbot with HTMX Streaming. También recomiendo esta otra documentación (Learn to use Websockets with Django by building your own ChatGPT) que si eres relativamente nuevo con esto de htmx y websockets te va a aclarar mucho el tema :)

Inicié implementando el manual de saaspegasus pero no funcionaba. Así que he decidido directamente integrar el código de django_chatbot a mi aplicación y tampoco funcionaba… hasta que he visto lo de daphne! El python manage.py runserver de toda la vida no es síncrono pero daphne si, y lo que permite es activar las conexiones asíncronas. Al aplicar esto, todo ha empezado a funcionar :D

Después ha venido la parte de ponerlo en producción que de nuevo me he perdido entre múltiples páginas pero finalmente lo que sacado y aquí lo dejo para saber como implementarlo la próxima vez y también para otro …

Ficheros mp3

Este fin de semana en un curso el profesor comentaba que los ficheros mp3 se degradaban con el uso y terminaban dando problemas como cortes y que era mejor usar otros formatos como WAV o FLAC. Me sorprendió lo comentado, ya que según mi entendimiento, cuando abres un fichero mp3, este se carga en memoria (RAM) y desde ahí se realiza todo el proceso de descompresión.

Así que el objetivo de este post es el de comprobar y demostrar lo comentado. Esto también me sirve para corroborar a nivel de perito informático qué ocurre con la reproducción de ficheros de audio, vídeo y fotografías (el concepto es el mismo).

Para ello voy a usar un script que reproduzca 100 veces un clip de audio y compruebe en cada reproducción el hash del fichero.

Antes de empezar voy a instalar sox y libsox-fmt-mp3 para reproducir el mp3 en consola

# apt install sox libsox-fmt-mp3

Y voy a usar el siguiente script

$ vi play_mp3_and_check_hash.sh
#!/bin/bash

AUDIO_FILE=duck_quack.mp3

for ((i=1;i<=100;i++));
do
   SUM=`md5sum ${AUDIO_FILE}`
   echo "Loop number: ${i}, hash is: ${SUM}"
   play -q ${AUDIO_FILE} 2>/dev/null
done

Le doy permisos y lo ejecuto

$ chmod +x play_mp3_and_check_hash.sh
$ ./play_mp3_and_check_hash.sh
Loop number: 1, hash is: 50c8fa8d674e6598951ab69bf2596c5d  duck_quack.mp3
Loop number: 2, hash is: 50c8fa8d674e6598951ab69bf2596c5d  duck_quack.mp3
Loop number: 3, hash is: 50c8fa8d674e6598951ab69bf2596c5d  duck_quack.mp3
Loop number: 4, hash is: 50c8fa8d674e6598951ab69bf2596c5d  duck_quack.mp3
Loop number: 5, hash is: 50c8fa8d674e6598951ab69bf2596c5d  duck_quack.mp3
[...]
Loop number: 97, hash is: 50c8fa8d674e6598951ab69bf2596c5d  duck_quack.mp3
Loop number: 98, hash is: 50c8fa8d674e6598951ab69bf2596c5d  duck_quack.mp3
Loop number: 99, hash is: 50c8fa8d674e6598951ab69bf2596c5d  duck_quack.mp3
Loop number: 100, hash is: 50c8fa8d674e6598951ab69bf2596c5d  duck_quack.mp3

Y la conclusión es que, …

Script para reiniciar nginx cuando han caducado los certificados de letsencrypt

Últimamente estoy de scripts para automatizar mi vida de sysadmin ;) aquí os dejo otro que comprueba si se han renovado los certificados y reinicia nginx en caso de que si haya ocurrido

# vi /root/scripts/check_certificates2.sh

#!/bin/bash

ADMIN_MAIL=""
DOMAINS=($(nginx -T |grep server_name |grep ";" |grep -v "#" |awk '{print $2}' |sed 's/;//' |grep -v "_" | sort -u))
CHECK_DIR="/root/scripts/check_domains"
REBOOT_NGINX=0

mkdir -p ${CHECK_DIR}

for i in "${DOMAINS[@]}"
do
   if [[ -f ${CHECK_DIR}/$i ]]; then
       OLD=`cat ${CHECK_DIR}/$i |awk '{print $1}'`
       CURRENT=`sha1sum /etc/letsencrypt/live/${i}/cert.pem |awk '{print $1}'`
       if [[ ${OLD} != ${CURRENT} ]]; then
           echo "hashes don't match for domain ${i}, please restart nginx"
           REBOOT_NGINX=1
           sha1sum /etc/letsencrypt/live/${i}/cert.pem ${CHECK_DIR}/$i
       fi
   else
       if [[ -f /etc/letsencrypt/live/${i}/cert.pem ]]; then
           sha1sum /etc/letsencrypt/live/${i}/cert.pem ${CHECK_DIR}/$i
       else
           echo "Domain ${i} has no certificate file"
       fi
   fi
done

if [[ ${REBOOT_NGINX} == "1" ]]; then
    echo "I reboot nginx due there are changes on certificates"
    service nginx restart
fi

Luego hago que se ejecute cada 10 minutos y listos

# vi /etc/crontab
*/10 *  * * *   root    /root/scripts/check_certificates2.sh

Script para realizar un sha1sum recursivo de los ficheros un directorio

Volvemos a la carga con mas scripts de bash. Esta vez necesito realizar un sha1sum para obtener el hash sha1 de unos ficheros que están dentro de muchos directorios. Como sha1sum no soporta la opción recursiva (en mi opinión un fallo), no queda otra que recurrir a nuestro amado bash.

Aquí el script para la ocasión

#!/bin/bash

DIRECTORY="material"

find "$DIRECTORY" -type d -print0 -maxdepth 4 | while read -r -d '' d; do
  echo "#### SHA1SUM Directorio: $d #####"
  sha1sum $d/*
done