Servidor VPN en Raspberry Pi

Un uso muy clásico de usar una raspberry es configurarla como VPN, prácticamente cualquier modelo da un rendimiento aceptable, siempre y cuando sea para uso personal y no requiera una gran transferencia de datos, en este caso se va a dar un ejemplo con una Raspberry Pi Model B, una de las primeras que salió al mercado que tiene solo 256MB (sí, con M) de RAM y una tarjeta SD (sí, no es micro) de 8GB.

Primero como siempre actualizar el sistema, en este caso como la fundación Raspberry son muy retrocompatibles (algo que no se agradece lo suficiente) vamos a usar la última distribución disponible Bookworm.

Se recomienda esta optimización.

NoIP

Primero vamos a crear un DNS dinámico de esta forma aunque nuestra dirección IP pública cambie siempre podremos alcanzar al servidor VPN si nos estamos conectando desde el exterior, hay varios proveedores y en este caso vamos a usar NoIP.

Lo primero será crear una cuenta, gratuitamente se dan hasta 3 dominios, cada mes hay que confirmar que estamos usando el servicio, poca molestia por el servicio gratuito.

Después de crear una cuenta tendremos que usar el cliente NoIP este cliente sirve para actualizar dinámicamente la dirección IP con el servidor NoIP, esto es necesario porque si el ISP cambia la IP del router NoIP no lo sabrá a menos que se lo notifiquemos.

NoIP tiene una guia detallada de como instalar el cliente aquí.

En este caso vamos a usar la compilación manual por el simple hecho que en ciertas Raspberry podemos obtener el error Segmentation fault un equivalente al BSOD de Windows.

Las instrucciones para compilar desde fuente también se comentan en el propio documento, esta es una guía de ejemplo:

Vamos a crear una carpeta para almacenar el código y los binarios:

mkdir noip
cd noip

Ahora descargar los fuentes (revisar esta dirección si no descarga puede haber una versión nueva):

wget https://dmej8g5cpdyqd.cloudfront.net/downloads/noip-duc_3.1.0.tar.gz

Extraer los fuentes y posicionarnos en la carpeta:

tar xf noip-duc_3.1.0.tar.gz
cd noip-duc_3.1.0

Necesitamos Rust para poder compilar así que lo instalamos, la instalación dura un poco pero la compilación es muy muy larga, así que usamos screen para que no se pare:

Instalamos Rust primero:

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

Se recomienda aumentar la memoria swap, al menos a 512MB, esto está explicado aquí, sino se corre el riesgo (altamente probable) de que la Raspberry se quede colgada en el último paso, también se recomienda configurar zswap.

Y ahora lanzamos la compilación, primero recargamos el shell como nos indica Rust y luego el comando propio de Rust para la compilación.

. "$HOME/.cargo/env"
cargo build --release

Ahora solo queda configurar el binario que tendremos en /target/release para que se inicie al arrancar, así que lo configuramos como un servicio usando systemd.

Primero copiamos el binario que tenemos en /target/release a la carpeta /usr/bin

sudo cp target/release/noip-duc /usr/bin/

Ahora creamos un fichero en /etc/default/

sudo nano /etc/default/noip-duc

Vamos a añadir en este fichero la configuración, -la configuración completa está en la ayuda-, estamos usando el usuario y grupo de pi para arrancar el servicio por comodidad (que no seguridad).

NOIP_USERNAME=Raspberry
NOIP_PASSWORD=drN01p
NOIP_DAEMON_USER=pi
NOIP_DAEMON_GROUP=pi
NOIP_DAEMON_PID_FILE=/var/run/noip.pid
NOIP_HOSTNAMES=uno.ddns.net,dos.ddns.net

El parámetro NOIP_HOSTNAMES puede ser tanto una lista de hosts como un grupo que se puede crear para actualizar todos los hosts en bloque, la creación de un grupo se hace desde el portal de noip.

Ahora creamos un fichero noip.service in /etc/systemd/system:

sudo nano /etc/systemd/system/noip.service

y vamos a poner lo siguiente (el contenido también está en la carpeta debian):

[Unit]
Description=No-IP Dynamic Update Client
After=network.target auditd.service

[Service]
EnvironmentFile=/etc/default/noip-duc
ExecStart=/usr/bin/noip-duc
Restart=on-failure
Type=simple

[Install]
WantedBy=multi-user.target

Ahora recargamos los servicios:

sudo systemctl daemon-reload

Ahora vamos a hacer que el servicio se inicie al arrancar:

sudo systemctl enable noip.service

Finalmente vamos a arrancarlo manualmente a ver si funciona todo bien.

sudo systemctl start noip.service

Y si queremos saber el estado en el que está usamos:

sudo systemctl status noip.service

PiVPN

Vamos a usar PiVPN como herramienta software para la instalación, bastante sencilla de usar, primero la instalación:

curl -L https://install.pivpn.io | bash

Aparecerá un instalador que hará algunas preguntas:

PiVPN Automated Installer es un mensaje informativo, OK para continuar.

Static IP Needed otro mensaje informativo, OK para continuar.

IPv6 leak elegimos OK, es para que el cliente use el tunel y evitar que su dirección IPv6 se filtre a los servidores, esto no implica que hay que realizar ninguna configuración adicional relacionada con IPv6.

DHCP Reservation para usar la dirección IP del servidor DHCP que tengamos en la red, es verdad que esto es poco habitual, generalmente los servidores suelen tener una IP estática, así que indicamos No.

Static IP Address: si ya tenemos una IP asignada por un servidor DHCP podemos usar la que indica sino pulsamos No y en la siguiente pantalla IPv4 address indicamos la IP y máscara de red, después en la pantalla IPv4 gateway (router) indicamos la IP del router.

Local Users es un mensaje informativo, OK para continuar.

Choose A User sirve para indicar que usuario ejecutará el servicio, el usuario por defecto pi es una buena opción, lo normal es que en un servidor VPN no existan cuentas de usuario.

Installation Mode (1) seleccionamos OpenVPN, la siguiente opción marcamos No para poder usar el protocolo UDP, más rápido en este caso.

Installation Mode (2) usamos UDP por defecto, que en este caso da mejor rendimiento. La opción custom search domain es para indicar si en las peticiones DNS del cliente hay que adjuntar este nombre de dominio, lo normal es que no sea el caso. Seleccionamos No en esta pantalla.

Default openvpn Port el puerto que usará OpenVPN, por defecto es el 1194 (¡UDP!) importante recordarlo para configurarlo en un firewall o router.

Confirm Custom Port Number la simple confirmación de que estamos de acuerdo con el número de puerto.

DNS Provider aquí podemos elegir uno externo como Google o CloudFare, o podemos usar el propio servidor PiVPN-is-local-DNS si también es servidor DNS (algo poco habitual y recomendable), con la opción Custom podemos elegir otro, esto lo veremos más adelante pero para una configuración estándar las dos primeras opciones son correctas.

Public IP or DNS aquí a no ser que tengamos una IP pública que no cambie (no suele ser así) elegiremos DNS Entry e indicaremos un nombre de dominio como el que hemos configurado anteriormente.

Confirm DNS Name simple confirmación que el nombre elegido es correcto.

Server information seleccionamos OK para poder generar la clave HMAC, necesaria para la autenticación TLS de OpenVPN.

Unattended Upgrades seleccionamos OK de esta forma OpenVPN se actualizará automáticamente con parches, seleccionamos Yes.

Installation Complete! si todo ha ido bien llegaremos aquí, presionamos OK para poder reiniciar y confirmamos el reinicio.

Después de este paso podemos actualizar la Raspberry y después reiniciar.

sudo apt-get -y update && sudo apt-get -y upgrade

Optimizaciones

A continuación algunas mejoras de rendimiento para una Raspberry Pi 1.

Deshabilitamos algunos servicios del arranque:

# Deshabilitamos el servicio de modem
sudo systemctl disable ModemManager.service
# Deshabilitamos el servicio Avahi
sudo systemctl disable avahi-daemon.service
# Deshabilitamos triggerhappy
sudo systemctl disable triggerhappy.service

Ajustamos el tamaño de los buffers de red en /etc/sysctl.conf añadimos estas líneas

net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 87380 16777216
net.core.netdev_max_backlog = 2000

Y recargamos los parámetros con:

sudo sysctl -p

Ahora vamos a cambiar algunos valores de OpenVPN, para esto editamos el fichero /etc/openvpn/server.conf

#Bajamos el cifrado a 128 para reducir carga en CPU
cipher AES-128-CBC
#Optimizar TUN/TAP/UDP y escrituras I/O 
fast-io
#Cambiamos tamaño de buffer en cliente y servidor
sndbuf 524288
rcvbuf 524288
#Tamaño de la longitud de la cola, mismo valor que net.core.netdev_max_backlog
txqueuelen 2000

Crear clientes

Ahora solo queda crear los clientes, OpenVPN tiene aplicación cliente para prácticamente todos los sistemas, IOS, Android, Windows, Linux…. así que la opción preferida es crear una por dispositivo que vayamos a usar.

Para crear un cliente solamente tenemos que usar este comando:

pivpn add

Simplemente rellenamos las preguntas con nombre del certificado, contraseña y duración que tiene que estar entre 1 y 3650 días.

Por defecto el certificado lo creará en la carpeta /home/pi/ovpns lo recomendable es sacarlo de aquí y llevarlo al dispositivo destino usando el comando scp, por ejemplo:

scp pi@192.168.1.2:/home/pi/ovpns/client.ovpn .

Donde 192.168.128.1 es la ip del servidor y /home/pi/ovpns/client.ovpn es la ruta al certificado.

Si quisiéramos revocar un certificado, ya sea para que ese cliente no se pueda conectar o para cambiar la contraseña tendríamos que hacer.

pivpn -r client

Done client es el nombre del cliente que queremos revocar, es decir el mismo nombre que usamos al crearlo. Después de hacer esto solo tenemos que volver a crearlo usando pivpn add. Y si queremos saber que clientes están instalados podemos usar pivpn -l

Referencias

https://www.industrialshields.com/es_ES/blog/raspberry-pi-para-la-industria-26/configurar-un-servidor-vpn-en-una-raspberry-pi-566
https://linuxblog.io/improving-openvpn-performance-and-throughput

Servidor VPN en Raspberry Pi

Optimizaciones Raspberry Pi

Recopilación de algunas mejoras de rendimiento para diferentes escenarios. Incluye todas las versiones de Debian hasta Bookworm y para cualquier Raspberry que use alguna de estas versiones.

Hasta la versión de Debian Bookworm el archivo de configuración config.txt estaba en /boot/config.txt ahora lo podéis encontrar en /boot/firmware/config.txt. Lo mismo sucede con el fichero de configuración del kernel cmdline.txt pasa de estar en /boot/cmdline.txt a estar en /boot/firmware/cmdline.txt.

También se incluyen algunas recomendaciones, sobre todo para procesos de larga duración.

Rendimiento

En esta sección incluyo algunas mejoras que se pueden usar para mejorar el rendimiento. Hay que tener cuidado con algunas de ellas así que es bueno evaluar cada una para ver si proporciona el rendimiento esperado.

Reducir memoria dedicada a la gráfica

Para aumentar la memoria disponible del sistema se puede reducir la cantidad de memoria dedicada a la parte gráfica, esto no suele ser un problema ya que muchas veces Raspberry no se usa para aplicaciones gráficas.

Esto lo hacemos a través de la utilidad raspi-config, vamos a la opción Performance options -> GPU Memory e indicamos como valor 16 que es el mínimo permitido.

En Bookworm esto ha cambiado y ahora tenemos que editar manualmente el fichero config.txt. Añadimos esta línea en el fichero, por ejemplo antes de las opciones de audio (buscar dtparam), aquí podemos indicar el valor que queramos, no se recomiendan valores muy bajos (menores a 64MB), por ejemplo el mínimo para una Raspberry Pi 1 sería 16MB. Después de aplicar este cambio reiniciamos:

# Set memory split (add this line to set GPU memory allocation)
gpu_mem=16

Overclock

Existe también la opción de hacer overclocking a la Raspberry Pi a través de la herramienta raspi-config, vamos a la opción Performance options -> Overclock y elegimos una de las opciones para aumentar la velocidad del procesador, estas opciones son delicadas y no es recomendable usarla sobre todo porque Raspberry no tiene ningún sistema de refrigeración adicional.

Aumentar archivo swap

En general el tamaño del archivo swap es adecuado pero para ciertos procesos muy intensivos o Raspberry limitadas se puede aumentar para evitar que se quede colgada, la contrapartida es que esto producirá un mayor aumento de E/S por lo que el tiempo de vida de la tarjeta SD se puede ver reducida. Se puede aumentar de la siguiente forma:

Primero paramos el uso del archivo swap.

sudo dphys-swapfile swapoff

Ahora modificamos el tamaño del fichero dphys-swapfile, por ejemplo:

sudo nano /etc/dphys-swapfile

Vamos a elegir un tamaño de 256MB así que modificamos la siguiente línea.

CONF_SWAPSIZE=256

Y ahora podemos volver a habilitar el uso del fichero swap, primero lo inicializamos y luego lo arrancamos.

sudo dphys-swapfile setup
sudo dphys-swapfile swapon

Si queremos ver la memoria swap asignada así como la que está en uso podemos usar el comando top o htop o free -h.

Usando top la línea que tiene MiB Swap se refiere a la memoria swap, total es toda la que disponemos, free es la que actualmente no está en uso y used es la que está en uso, en avail Mem podemos ver la suma tanto de la memoria RAM como de la swap, haciendo un seguimiento de estos valores podemos tunear para escenarios concretos.

Usando zswap

zswap es un sistema de compresión de memoria RAM esto proporciona más memoria pero también un aumento del uso del procesador en el proceso de compresión, descompresión. Es útil en algunos escenarios donde los procesos consumen mucha memoria. Diría por las pruebas que he hecho que permite que ciertos procesos terminen y no se cuelguen pero no aumenta esencialmente la velocidad de los mismos. Los pasos serían:

Tenemos que añadir zswap.enabled = 1 al archivo /boot/cmdline.txt (/boot/firmware/cmdline.txt en bookworm) para habilitar zswap.

Otra opción sería usar zram, aquí y aquí un listado de ventajas e inconvenientes.

Habilitando z3fold & lz4 para zswap

Instalamos primero el paquete lz4, que es el compresor de menoría y z3fold es la forma en que se guardan estas páginas de memoria.

sudo apt-get install lz4

Después tenemos que comprobar si está habilitado initramfs, para eso editamos este fichero /etc/default/raspberrypi-kernel y comprobamos que está descomentada esta línea (No necesario para la version bookworm).

INITRD=Yes

En caso de que estuviese comentada posiblemente no tendríamos creado initramfs si quisiéramos verificarlo ejecutamos.

sudo update-initramfs -uv

Y la propia salida nos indicará si existe o no, en caso de que no exista ejecutamos el siguiente comando:

sudo update-initramfs -c -k $(uname -r)

Editamos el fichero /etc/initramfs-tools/modules y añadimos las siguientes líneas:

lz4
lz4_compress
z3fold

Y ejecutamos el siguiente comando para actualizar con los nuevos módulos:

update-initramfs -uv

Si da un error de hard link realmente es un aviso y no es un problema. Esta salida nos debería indicar también el nombre de la imagen, algo así como -este fichero se puede encontrar en /boot-:
‘/boot/initrd.img-6.6.20+rpt-rpi-v6‘ -> ‘/boot/firmware/initramfs’

(Este paso lo podemos omitir en bookworm) Después editamos config.txt y añadimos al final del fichero dentro de la sección general, es decir, ninguna sección que contenga un hardware concreto como [pi4] [pi3] [all]... siendo el valor el nombre de la imagen que nos ha sacado el comando anterior (no es necesario poner un «=»).

initramfs initrd.img-6.6.20+rpt-rpi-v6

Ahora editamos cmdline.txt y añadimos lo siguiente:

zswap.compressor=lz4 zswap.zpool=z3fold

Y reiniciamos, después para verificar que todo ha ido correcto ejecutamos:

grep -R . /sys/module/zswap/parameters

Que nos debería sacar como salida…

/sys/module/zswap/parameters/same_filled_pages_enabled:Y
/sys/module/zswap/parameters/enabled:Y
/sys/module/zswap/parameters/max_pool_percent:20
/sys/module/zswap/parameters/compressor:lz4
/sys/module/zswap/parameters/zpool:z3fold
/sys/module/zswap/parameters/accept_threshold_percent:90

En este punto la configuración del archivo config.txt auto_initramfs=1 se puede quitar por redundante pero tampoco afectará si se deja, esto asegura que al menos un initramfs se cargará al inicio, de hecho en Bookworm es la configuración recomendada y no usar la mencionada anteriormente.

Más información sobre las ventajas aquí y detalles de configuración aquí:

Usando ZRAM

A veces puede suceder que zswap no nos de el resultado esperado, podemos probar en ese caso con zram, para eso seguimos estos pasos:

sudo wget -O /usr/bin/zram.sh https://raw.githubusercontent.com/Bash-Projects/rpi_zram/master/zram.sh

Damos permisos para ejecutar el script que nos acabamos de descargar:

sudo chmod +x /usr/bin/zram.sh

Ahora lo vamos a programar para que se ejecute 50 segundos después del arranque, así que lanzamos crontab.

sudo crontab -e

y programamos la ejecución del script:

@reboot ( sleep 50 ; sudo /usr/bin/zram.sh &)

El comando lo podemos lanzar directamente para ver el resultado o podemos reiniciar y esperar 50 segundos para ver el resultado, podemos verificar el aumento de memoria usando:

free -h

para ver como ha aumentado la RAM y para poder ver el aumento en la swap usamos:

swapon -s

Recomendaciones

Recomendaciones para ciertas tareas que se suelen hacer con Raspberry de forma habitual.

Procesos de larga duración

Muchas veces tenemos que lanzar procesos en Raspberry que pueden tardar mucho tiempo, sobre todo en un sistema como Raspberry de capacidades limitadas, y queremos asegurarnos que el proceso termine aunque no vayamos a estar delante del terminal, aquí indico algunas formas de abordar este problema.

Usando comandos

A veces hay procesos que llevan mucho tiempo -por ejemplo compilar un contenedor-, la mejor opción es dejar ejecutando el comando build en el terminal pero a veces esta compilación se lanza desde una conexión ssh y al cerrar se puede terminar el proceso, para evitar esto se puede usar el comando nohup, por ejemplo:

nohup docker image build --tag user/imagen -f Dockerfile .

También se puede ejecutar en segundo plano añadiendo & al final del comando.

Si el comando ya se estaba ejecutando podemos usar el comando disown para conseguir el mismo efecto, en este caso primero pasamos el comando a segundo plano usando Ctrl+Z y usando el comando jobs veremos como el comando está detenido, ahora lo volvemos a arrancar pero esta vez en segundo plano usando:

bg %1

siendo 1 el número del trabajo y a continuación lo desasociamos del terminal usando:

disown -h %1

Si solo tenemos un trabajo podemos omitir %1 en los comandos ya que actuarán sobre el último proceso que se haya lanzado.

Con cualquiera de estos dos métodos el proceso seguirá ejecutándose aunque salgamos del terminal.

Un problema adicional es no saber que está haciendo el proceso, podemos guardar un log redireccionando a texto con > pero en ese caso no veremos por pantalla lo que está pasando para esto tenemos el comando tee que nos permite ver por pantalla y a la vez emitir a fichero lo que está pasando.

Este comando es un resumen de lo explicado, lanza el proceso en segundo plano y permite ver la salida por pantalla a la vez que se guarda en un fichero.

nohup docker image build --tag user/image -f Dockerfile . | tee out.txt &

Si cerramos la sesión y volvemos a conectar más adelante la forma cómoda de ver que está haciendo el trabajo sería usando el comando tail con el fichero que hemos indicado, usando el parámetro -f podemos ir viendo las actualizaciones a medida que el proceso escriba.

tail -f out.txt

Usando screen

Otra opción posiblemente más cómoda sea usando screen, permite tener una sesión de la que se pueda salir sin cerrar el proceso, tiene el problema que no es un paquete instalado por defecto así que primero lo instalamos.

sudo apt-get install screen

Para lanzarlo simplemente usamos el comando screen

screen

Aceptamos el mensaje y veremos un shell en el que podemos trabajar, algunos comandos útiles de screen.

# Listado de las sesiones activas
screen -ls
# Reanudar sesión, si solo hay una activa
screen -r
# Reanudar sesión, donde ID es el número que aparece antes del punto
screen -r ID

También tenemos unos atajos útiles:

Ctrl+A,D Desconectamos temporalmente de la sesión (esto NO interrumpe los procesos activos).

Ctrl+A,A Permite cambiar entre diferentes sesiones activas.

Capturar errores en comandos encadenados

Muchas veces en encadenamos comandos con && y puede ser que fallé alguno de esos comandos pero realmente no se dará la ejecución como fallida a no ser que falle el último, si queremos que cualquier comando produzca un error en toda la ejecución podemos usar pipefail:

set -eo pipefail

Referencias

Enabling & Increasing Raspberry Pi Swap – Neblio
ZRAM

Optimizaciones Raspberry Pi

Kodi y plataformas de streaming (Netflix, HBO, Amazon, Disney+)

Actualizado a 13 de Octubre de 2022

Es habitual ver centros multimedia en Raspberry Pi, lo que no es tan habitual es montarlos en una imagen Docker, con las mejoras que ha habido en hardware en Raspberri ya es posible tener varios servicios a la vez en una sola Rpi, por ejemplo servidores de vídeo, audio etc..

En este caso se van a detallar los pasos para tener una imagen Docker con Kodi (v19.x) en la última versión Debian disponible en este momento (Bullseye).

Esta imagen Docker contendrá los plugins de HBO, Netflix, Amazon y Disney+ (aunque es posible que se puedan configurar otros).

Configuración Host

Antes de empezar con la imagen Docker necesitaremos una Raspberry operativa, como versión de Raspberry Pi OS podemos usar cualquiera (incluso Lite) una vez instalado Raspberry Pi OS actualizamos el sistema con:

sudo apt-get update
sudo apt-get upgrade

También hace falta instalar el paquete lirc.

apt-get install lirc

Y tendremos también que instalar Docker siguiendo estos pasos sino está ya instalado, en ese mismo enlace se dan algunas recomendaciones sobre rendimiento que pueden ser útiles.

Vídeo

Hay que hacer una pequeña modificación en el fichero /boot/config.txt para que el vídeo se reproduzca correctamente, tenemos que tener esto en el fichero:

[all]
dtoverlay=vc4-fkms-v3d

Y después solo queda reiniciar. A partir de aquí es posible que queramos hacer algunas tareas adicionales, por ejemplo igual queremos añadir como biblioteca de vídeo o audio un USB conectado o crear carpetas de unidad de red para acceder a ficheros remotos.

Docker

Aquí tenemos la imagen Docker, solo hay un parámetro para build que indica que salida de audio se va a utilizar, 1 para HDMI y 0 para la salida Jack.

FROM balenalib/raspberrypi3-debian:bullseye

ARG DEBIAN_FRONTEND=noninteractive
# 1 for HDMI, 0 for headphones
ARG     AUDIO=1

RUN	apt-get update				&& \
	apt-get -y purge openssl		&& \
	apt-get -y --purge autoremove		&& \
	apt-get dist-upgrade			&& \
	apt-get install	-y			\			
	uuid-dev		\
	upower			\
	alsa-base		\
	alsa-utils		\
	alsa-tools		\
	dbus-x11		\
	libraspberrypi0		\
	xterm			\
	xinput			\
	xinput-calibrator	\
	evemu-tools		\	
	libnspr4		\
	libwidevinecdm0		\
	libc6			\
	avahi-daemon		\
	libnss-mdns		\
	nano			\
	lirc			\
	lirc-compat-remotes	\
	bluez			\
	dumb-init		\
# Required for AirPlay Mirroring
	cmake					\
        libavahi-compat-libdnssd-dev		\
        libplist-dev				\
        libgstreamer1.0-dev			\
        libx264-dev				\
        libjpeg-dev				\
        libgstreamer-plugins-base1.0-dev	\
        libgstreamer-plugins-bad1.0-dev		\
        gstreamer1.0-plugins-ugly		\
        gstreamer1.0-tools			\
        gstreamer1.0-gl				\
        gstreamer1.0-gtk3			\
        git					\
        git-svn					\
        libssl-dev				\
# Python required for Netflix, Amazon...
	python3-pip		\
	build-essential		&& \
# Install some Python packages
	pip install setuptools pycryptodome pycryptodomex wheel pycrypto				&& \
	apt-get -y --purge autoremove									&& \
	rm -rf /var/lib/apt/lists/*

# besides kodi, we will install a few extra packages:
#  - ca-certificates              allows Kodi to properly establish HTTPS connections
#  - kodi-eventclients-kodi-send  allows us to shut down Kodi gracefully upon container termination
#  - kodi-game-libretro           allows Kodi to utilize Libretro cores as game add-ons
#  - kodi-game-libretro-*         Libretro cores
#  - kodi-inputstream-*           input stream add-ons
#  - kodi-peripheral-*            enables the use of gamepads, joysticks, game controllers, etc.
#  - kodi-pvr-*                   PVR add-ons
#  - kodi-screensaver-*           additional screensavers
#  - lirc,lirc-compat-remotes     enables the use of IR Remotes
#  - locales                      additional spoken language support (via x11docker --lang option)
#  - pulseaudio                   in case the user prefers PulseAudio instead of ALSA
#  - tzdata                       necessary for timezone selection
RUN packages="                                               \
    fbset                                                    \
    ca-certificates                                          \
    kodi                                                     \
    kodi-eventclients-kodi-send                              \
    kodi-inputstream-adaptive                                \
    kodi-inputstream-rtmp                                    \
    kodi-peripheral-joystick                                 \
    kodi-pvr-argustv                                         \
    kodi-pvr-dvblink                                         \
    kodi-pvr-dvbviewer                                       \
    kodi-pvr-filmon                                          \
    kodi-pvr-hdhomerun                                       \
    kodi-pvr-hts                                             \
    kodi-pvr-iptvsimple                                      \
    kodi-pvr-mediaportal-tvserver                            \
    kodi-pvr-mythtv                                          \
    kodi-pvr-nextpvr                                         \
    kodi-pvr-njoy                                            \
    kodi-pvr-pctv                                            \
    kodi-pvr-sledovanitv-cz                                  \
    kodi-pvr-stalker                                         \
    kodi-pvr-teleboy                                         \
    kodi-pvr-vbox                                            \
    kodi-pvr-vdr-vnsi                                        \
#    kodi-pvr-vuplus                                          \ # Not yet available
    kodi-pvr-wmc                                             \
    kodi-pvr-zattoo                                          \
    kodi-screensaver-biogenesis                              \
    kodi-screensaver-matrixtrails                            \
    kodi-screensaver-pyro                                    \
    kodi-screensaver-stars                                   \
    lirc                                                     \
    lirc-compat-remotes                                      \
    locales                                                  \
    libnss3                                                  \
    tzdata"                                               && \
                                                             \
    apt-get update                                        && \
    apt-get install -y $packages                          


# Audio settings
RUN     echo            \
"pcm.!default {         \n\
  type asym             \n\
  playback.pcm {        \n\
  type plug             \n\
  slave.pcm "output"    \n\
}                       \n\
capture.pcm {           \n\
  type plug             \n\
  slave.pcm "input"     \n\
  }                     \n\
}                       \n\
pcm.output {            \n\
  type hw               \n\
  card $AUDIO           \n\
}                       \n\
ctl.!default {          \n\
  type hw               \n\
  card $AUDIO           \n\
}" >> /etc/asound.conf

# Adding user
ENV	KODI_ID=1000
RUN     adduser \
	--uid $KODI_ID \
	--disabled-password \
	--gecos '' \
	--ingroup sudo \
	kodi 
RUN     echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers
RUN	usermod -a -G audio,video,input,dialout,plugdev,netdev,users,cdrom,tty kodi

# Install RpiPlay
WORKDIR /opt/vc
RUN     git svn clone https://github.com/raspberrypi/firmware/trunk/opt/vc/include
RUN     git svn clone https://github.com/raspberrypi/firmware/trunk/opt/vc/src
RUN	git svn clone https://github.com/raspberrypi/firmware/trunk/opt/vc/lib

WORKDIR /repos
COPY    rpiplay.tar.gz .
RUN     tar -zxvf rpiplay.tar.gz
RUN	ls -la
WORKDIR /repos/rpiplay/build
RUN	ls -la
RUN	cmake ..
RUN	make -j 4
WORKDIR /
RUN	rm -rf /repos
RUN	rm -rf /rpiplay.tar.gz

# Plugins

WORKDIR	/plugins
ADD	--chown=kodi:sudo https://github.com/castagnait/repository.castagnait/raw/kodi/repository.castagnait-2.0.0.zip .
ADD	--chown=kodi:sudo https://k.slyguy.xyz/repository.slyguy.zip .
ADD	--chown=kodi:sudo https://github.com/Sandmann79/xbmc/releases/download/Repository/repository.sandmann79.plugins-1.0.4.zip .

# Configuration
EXPOSE	8080 9777/udp

VOLUME	/home/kodi
USER	kodi

COPY entrypoint.sh /usr/local/bin
CMD ["/usr/local/bin/entrypoint.sh"]

También usaremos el siguiente fichero Docker compose:

version: "3.7"
services:
  rpi-kodi:
    image: joursain/rpi-kodi:buster
    build:
      context: .
      dockerfile: Dockerfile
    container_name: "kodi"
    user: kodi
    network_mode: host
    restart: "no"
    privileged: true
    devices:
      - /dev/fb0:/dev/fb0
      - /dev/vchiq:/dev/vchiq
      - /dev/tty0:/dev/tty0
      - /dev/tty2:/dev/tty2
      - /dev/input:/dev/input
      - /dev/snd:/dev/snd
      - /dev/shm:/dev/shm
    volumes:
      - /var/run/dbus:/var/run/dbus
      - /mnt/media/kodi:/home/kodi
      - "/etc/timezone:/etc/timezone:ro"
      - "/etc/localtime:/etc/localtime:ro"
      - "/usr/bin/tvservice:/usr/bin/tvservice:ro"
    tmpfs:
      - /tmp
    environment:
      - DBUS_SYSTEM_BUS_ADDRESS=unix:path=/var/run/dbus/system_bus_socket

Para arrancar lo haremos de la forma habitual (opcionalmente podemos añadir -d si queremos que arranque en segundo plano)

docker-compose up [-d]

Veremos la salida (si no hemos seleccionado -d) de la ejecución y en cuanto haya terminado aparecerá Kodi en pantalla. Esta imagen permite el uso de teclado así que en cuanto esté operativa podremos usar el teclado y escuchar sonidos a medida que se seleccionen diferentes opciones, esto verificará que el sonido está bien configurado.

La carpeta home del usuario kodi (/home/kodi) que contiene la configuración de Kodi está compartida en un volumen por si hiciera falta hacer una copia de seguridad de la configuración o para añadir algún plugin externo se puede dejar en esta carpeta para poder instalarlo desde Kodi.

Al arrancar posiblemente saldrán varias ventanas relacionadas con plugins pidiendo activación, no hace falta hacerlo en un primer momento y se puede hacer más adelante.

Servicios

Es posible configurar algunos servicios de Kodi como por ejemplo UPnP o AirPlay de la siguiente forma:

  1. Vamos a configuración (el icono de la rueda)
  2. Icono System
  3. Services
  4. Aquí podemos habilitar UPnP/DLNA o AirPlay

En la parte de abajo hay una opción para poder ampliar las opciones de configuración (el nivel máximo es Expert)

Sonido

La imagen Docker por defecto al arrancar ya tiene el sonido habilitado, la configuración que deberíamos ver es la que está en:

  1. Vamos a configuración (el icono de la rueda)
  2. Icono System
  3. Opción audio
  4. Audio output device
  5. Marcamos Default (bcm2835 Headphones bcm2835 Headphones) ()

Como detalle si se están compartiendo los altavoces entre Raspberry y otro dispositivo es posible que la imagen arranque sin sonido, solo un dispositivo puede estar habilitado a la vez.

Plugins

Ahora vamos a configurar los diferentes plugins, la imagen Docker los coloca en la carpeta /plugins, antes de instalarlos hay que realizar algunas acciones en la imagen Docker.

  1. Vamos a configuración (el icono de la rueda).
  2. Add-ons.
  3. My add-ons.
  4. VideoPlayer InputStream.
  5. InputStream Adaptive.
  6. Enable.

Ahora tenemos que habilitar la opción de orígenes desconocidos para poder instalar los plugins

  1. Vamos a configuración (el icono de la rueda).
  2. System.
  3. Add-ons.
  4. Habilitamos Unknown sources y confirmamos el mensaje.

Para instalar los diferentes plugins tendremos que seguir estos pasos.

  1. Vamos a configuración (el icono de la rueda).
  2. Add-ons.
  3. Install from zip file (igual tenemos que usar los .. para subir a un nivel superior y poder ver la opción) y confirmamos el mensaje que saldrá, este mensaje solo nos indica que estos plugins no se actualizan automáticamente y habrá que hacerlo manualmente.
  4. Nos movemos a la carpeta /plugins (Root filesystem/plugins).
  5. Aquí se encuentran todos los plugins que iremos instalando.

Instalación

Todos los plugins se instalan de la misma forma, el procedimiento es:

  1. Desde la carpeta plugins del paso anterior buscamos el plugin que queramos instalar, habrá los siguientes:
    1. CastagnaIT para Netflix
    2. SlyGuy para HBO y Disney+
    3. Sandman para Amazon
  2. Una vez seleccionado e instalado, vamos a la opción Install From Repository (al mismo nivel que Install from zip File) y veremos varios repositorios, seleccionamos por el mismo nombre que antes el que interese.
  3. Si solicita componentes adicionales los instalamos también.

Al ejecutar algunos plugins es posible que solicite instalar Widevine CDM, es necesario instalarlo para que funcione el vídeo. Este proceso puede llevar un rato. Es posible también que falle la instalación o de un error al abrir el vídeo, en ese caso hay que apagar el contenedor y volverlo a levantar.

Configuración

Ahora que están los plugins instalados vamos a configurarlos, simplemente hay que acceder a ellos a través de:

  1. Vamos a configuración (el icono de la rueda)
  2. Add-ons
  3. My addons / Video add ons / (plug-in de vídeo)

Básicamente todos los plugins tienen el mismo sistema, algunos pedirán los datos de la cuenta al principio y otros tendrán una sección especial. Cada plugin tiene varias opciones de configuración, por ejemplo idioma o subtítulos, merece la pena dedicarle un rato.

Televisión

Para poder ver la televisión tenemos que ir al apartado de Add-Ons en Sistema y luego en My-AddOns y finalmente instalamos PVR IPTV Simple Client, en la parte de Configure donde dice M3U playlist URL ponemos https://www.tdtchannels.com/lists/tv.m3u8 o https://www.tdtchannels.com/lists/tvradio.m3u8 si también queremos radio.

Ahora solo tenemos que ir al icono de TV que tenemos en la página principal en un lateral y esperar a que cargue los canales, saldrán muchos así que usar el buscador es una buena opción.

Notas

El código fuente aquí.

Como dato adicional se puede usar este mismo docker compose para cambiar Plex por Kodi.

No parece que de buen resultado usar zswap aquí, produce algunos errores al menos en Rpi3, es mejor usar zram.

De igual forma no podemos tener la memoría gráfica por debajo de 160, así que lo dejamos en 256GB.

Bonus

Esta imagen Docker admite AirPlay y Bluetooth, también el uso de infrarrojos si hay uno conectado aunque habría que incluirlo en docker-compose añadiendo – dev/lirc:/dev/lirc o – dev/lirc0:/dev/lirc0

Referencias

https://forum.kodi.tv/showthread.php?tid=365714
https://mundokodi.com/addon-hbo-max-en-kodi/
https://support.zoom.us/hc/en-us/articles/203680359-Protocols-for-Apple-screen-mirroring-AirPlay-
https://forums.raspberrypi.com/viewtopic.php?t=295008
https://github.com/CodaFog/kodi-rpi
https://github.com/rimago/rpi-kodi
https://kodi.wiki/view/AirPlay
https://www.genbeta.com/multimedia/como-ver-netflix-kodi-que-puede-ser-mejor-que-hacerlo-sus-aplicaciones-oficiales
https://www-genbeta-com.cdn.ampproject.org/v/s/www.genbeta.com/multimedia/netflix-hbo-pluto-tv-twitch-youtube-todas-grandes-plataformas-que-puedes-ver-kodi-como-hacerlo/amp?amp_gsa=1&amp_js_v=a6&usqp=mq331AQIKAGwASCAAgM%3D#amp_tf=De%20%251%24s&aoh=16301408042642&csi=0&referrer=https%3A%2F%2Fwww.google.com&ampshare=https%3A%2F%2Fwww.genbeta.com%2Fmultimedia%2Fnetflix-hbo-pluto-tv-twitch-youtube-todas-grandes-plataformas-que-puedes-ver-kodi-como-hacerlo
https://www.balena.io/docs/learn/more/masterclasses/services-masterclass/#5-running-systemd-in-a-service
https://wiki.ubuntu.com/LircHowto
https://www.linuxquestions.org/questions/linux-from-scratch-13/blfs-svn-dbus-won%27t-start-886338/
https://forums.balena.io/t/dbus-failed-to-connect-to-socket-host-run-dbus-system/3107
https://www.balena.io/docs/learn/develop/runtime/#dbus-communication-with-host-os

Kodi y plataformas de streaming (Netflix, HBO, Amazon, Disney+)

Servidor DHCP y DNS

(Actualizado a 13 de Febrero de 2024)

En algunos casos puede resultar interesante tener configurado un servidor DNS en una red local especialmente si tenemos servicios montados en una Raspberry y queremos acceder a ellos por nombre en lugar de aprender la IP y el puerto.

Un servidor DHCP también nos permite personalizar el rango de direcciones y poder asignar direcciones específicas basadas en MAC, útiles para servidores o sistemas NAS.

Existen paquetes que agrupan estos dos servicios y permiten usarse con una configuración mínima pero en este caso vamos a usar dos paquetes diferentes basado en dos clásicos isc dhcp server y bind9.

DHCP y DNS

Servidor DHCP

Primero instalamos el servidor DHCP

sudo apt-get install isc-dhcp-server

Es posible que al instalar este paquete aparezcan algunos errores, no hay que preocuparse porque es debido a que el servicio todavía no está configurado.
Ahora vamos a editar algunos valores el fichero /etc/dhcpcd.conf, en la parte de Example static IP configuration ponemos la siguiente configuración, vamos a suponer que el servidor tiene de IP 192.168.1.2 que el router de la red tiene de IP 192.168.1.1 y que el servidor DNS es este mismo (192.168.1.2) así como uno adicional 8.8.8.8

# Example static IP configuration:
interface eth0
static ip_address=192.168.1.2/24
#static ip6_address=fd51:42f8:caae:d92e::ff/64
static routers=192.168.1.1
static domain_name_servers=192.168.1.2 8.8.8.8 fd51:42f8:caae:d92e::1

Ahora editamos el fichero /etc/dhcp/dhcpd.conf quitamos el comentario de la opción authoritative y también de la opción log-facility local7; indicamos opciones para el nombre de la zona y aumentamos el tiempo de concesión (lease time), en una red doméstica es más práctico para no perder la IP en caso de que el servidor DHCP se caiga.

Al final del fichero agregamos la red que en nuestro caso es 192.168.1.0 y también creamos el rango en el que se entregarán IPs vamos a usar el rango 192.168.1.100-192.168.1.254.

En esta misma sección vamos a poder asignar las IP estáticas, por ejemplo máquinas que siempre tienen que recibir la misma IP, vamos a suponer por ejemplo que tenemos un servidor NAS en la IP 192.168.1.10, y por si acaso añadimos también el router aunque posiblemente ya tenga IP fija.

La configuración con todos estos cambios sería:

option domain-name "acme.local";
option domain-name-servers 192.168.1.2, 8.8.8.8;

default-lease-time 259200;
max-lease-time 604800;

authoritative;
log-facility local7;

subnet 192.168.1.0 netmask 255.255.255.0 {
range 192.168.1.100 192.168.1.254;
  option routers 192.168.1.1;
  option subnet-mask 255.255.255.0;
  option broadcast-address 192.168.1.255;
}

host router {
  hardware ethernet 38:72:c0:30:26:6e;
  fixed-address 192.168.1.1;
}

host NAS {
  hardware ethernet 00:d0:4b:95:06:c0;
  fixed-address 192.168.1.10;
}

Finalmente indicamos que solo procesaremos peticiones IPv4, para esto editamos el fichero /etc/default/isc-dhcp-server y al final dejamos solo lo siguiente comentando el resto:

INTERFACESv4="eth0"

Para poder empezar igual necesitamos establecer la dirección IP manualmente. Esto no interrumpirá la conexión existente.

sudo ifconfig eth0 192.168.1.1

También habilitamos algunos servicios para que el arranque sea correcto.

sudo systemctl enable dhcpcd
sudo systemctl enable isc-dhcp-server

Al terminar es aconsejable reiniciar los servicios para detectar cualquier posible error en la configuración:

sudo service dhcpcd restart
sudo service isc-dhcp-server restart

Servidor DNS

En este caso vamos a usar el servidor bind9, vamos a crear las zonas que servirá para identificar nuestra red y las maquinas que se tendrán que resolver cuando se acceda a ellas por nombre. Primero instalamos el servidor:

sudo apt-get install bind9

Una opción de configuración para habilitar bind9 solo para direcciones IPV4, editamos el fichero /etc/default/named y añadimos la opción -4 al final.

OPTIONS="-u bind -4"

Vamos a definir estas zonas en el fichero /etc/bind/named.conf.local a la zona la llamaremos acme

zone "acme.local" {
  type master;
  file "/etc/bind/db.acme.local";
};

zone "1.168.192.in-addr.arpa" {
  type master;
  file "/etc/bind/db.192.168.1";
};

En la primera sección creamos la zona acme que definiremos en la ruta indicada /etc/bind/db.acme.local en la segunda sección definiremos la zona inversa que permite hacer consultas en base a la IP para obtener el nombre, esta zona se suele nombrar usando la IP de red en sentido inverso.

Ahora editamos la opciones a través del fichero /etc/bind/named.conf.options

options {
  directory "/var/cache/bind";
  auth-nxdomain no;    # conform to RFC1035

# Reenviadores
  forwarders { # Reenviamos las consultas a:
    8.8.8.8; # DNS de Google.
    8.8.4.4; # DNS de Google.
    192.168.128.1; # IP del router.
  };

# Opciones de seguridad
  listen-on port 53 { # Escuchamos en el puerto 53 (Puerto por defecto)...
    127.0.0.1; # ... por la interfaz de loopback...
    192.168.1.2; # ... y por nuestra IP de red.
  };

# Otras opciones
  listen-on-v6 { none; }; # No escuchamos tráfico IPv6;

  allow-query { # Permitimos consultas DNS desde...
    127.0.0.1; # ... la interfaz de loopback ...
    192.168.1.0/24; # ... y nuestra red interna.
  };

  allow-transfer { none; }; # Prohibimos la transferencia de zonas puesto que este   es nuestro único servidor DNS de la LAN

  allow-recursion { # Permitimos consultas recursivas desde...
    127.0.0.1; # ... la interfaz de loopback...
    192.168.1.0/24; # ... y la red interna.
  };
};

Ahora creamos las zonas usando la configuración indicada al principio así que usaremos la carpeta /etc/bind, es importante respetar el formato del fichero con el punto al final (.) y los tabuladores.

  • db.acme.local
    • Contiene los nombres de la zona, aquí indicamos el nombre y su IP, al servidor DNS lo vamos a llamar server
$ORIGIN acme.local.
$TTL 86400 ; 1 dia
@       IN      SOA     server.acme.local        admin.acme.local (
        2    ; serie
        6H   ; refresco (6 horas)
        1H   ; reintentos (1 hora)
        2W   ; expira (2 semanas)
        3H   ; mínimo (3 horas)
)
; name servers - NS records
                        IN      NS      server.acme.local.

; name servers - A records
router.etxe.local.              IN      A       192.168.1.2
nas.etxe.local.                 IN      A       192.168.1.3
  • db.192.168.128
    • Este fichero contiene la zona inversa que se ha comentado antes
$ORIGIN 1.168.192.in-addr.arpa.
$TTL 86400     ; 1 dia
@       IN      SOA     server.acme.local        admin.acme.local (
        2      ; serie
        6H     ; refresco (6 horas)
        1H     ; reintentos (1 hora)
        2W     ; expire (2 semanas)
        3H     ; mínimo (3 horas)
)
; name servers - NS records
      IN      NS      server.acme.local.

; PTR Records
1.128   IN      PTR     router.etxe.local.       ; 192.168.128.1
2.128   IN      PTR     nas.etxe.local.          ; 192.168.128.2

Finalmente establecemos el servicio para que arranque automáticamente

sudo systemctl enable named

Para verificar que la configuración es correcta:

sudo named-checkconf

También podemos verificar la configuración de las zonas usando este comando donde el primer parámetro es el nombre de la zona y el segundo el nombre del fichero.

sudo named-checkzone acme.local db.acme.local

Y comprobamos que todo es correcto con:

sudo service named restart
Servidor DHCP y DNS

Docker Raspberry: Servidor multimedia con Compose

Actualizado el 19 de Octubre de 2021

Con la llegada de Raspberry surgieron muchas distribuciones multimedia como Kodi, OSMC, OpenELEC,… todas tienen en común que vienen preparadas para ser utilizadas sin ninguna instalación y con su propia interfaz gráfica y proporcionan plugins para ampliar la funcionalidad, por ejemplo emisoras de radio o clientes Torrent.

Estas distribuciones al estar dedicadas en exclusiva a funciones multimedia no permiten realizar otras acciones. Puede ser que no estemos todo el tiempo usándola de este modo y queramos darle más usos, sobre todo con los últimos modelos que son más potentes así que una alternativa es usar contenedores Docker, en este caso se van a crear tres contenedores:

  • Transmission: Un cliente torrent personalizable.
  • Mopidy: Servidor de audio compatible con Spotify.
  • Plex: Un servidor de streaming de video.
Docker

Dockerfile

Todos los contenedores se pueden ejecutar por separado usando los siguientes ficheros Dockerfile o usando el fichero compose que se indica al final. El código fuente está aquí. Vamos a suponer que las carpetas locales están bajo el raíz /mnt/media

Docker Mopidy

El fichero Docker para Mopidy está basado en este artículo, tiene consejos y recomendaciones sobre la configuración de sonido, la variable de entorno AUDIO maneja la salida de audio. Hay que tener en cuenta que si los altavoces están compartidos, por ejemplo tanto un ordenador como una Raspberry están conectados solo uno puede estar usándolo a la vez y el otro dispositivo tendrá que cambiar su salida de audio para liberar el canal.

# 0. Official armv32 image
FROM debian:latest

LABEL "guru.raraavis.creator"="blog@raraavis.guru"
LABEL "guru.raraavis.version"="1.0.0"
LABEL "guru.raraavis.release-date"="20/03/2021"
LABEL "guru.raraavis.description"="Raspberry Pi 3 image with Mopidy"

# 1. Environment vars
ARG DEBIAN_FRONTEND=noninteractive
ARG TZ=Europe/Madrid
ENV TZ=$TZ
        # Use 1 for headphones, 0 for HDMI
ARG AUDIO=1

# 2. Update system and install necessary packages
RUN     apt-get update && \
        apt-get install -y \
                wget \
                lsb-release \
                gnupg2 \
                gnupg \
                gnupg1 \
                python3-dev \
                python3-pip \
                libffi-dev \
                sudo \
                gosu \
                dumb-init && \
                cron && \
                rm -rf /var/lib/apt/lists/* && \
                rm -rf /tmp/*

# 3. Add mopidy repository
RUN     wget -q -O - https://apt.mopidy.com/mopidy.gpg | apt-key add -
RUN     DEBIAN_CODENAME=`lsb_release -sc` && \
        wget -vO /etc/apt/sources.list.d/mopidy.list "https://apt.mopidy.com/$DEBIAN_CODENAME.list"

# 4. Install pip packages and Mopidy
RUN     apt-get update && \
        apt-get install -y \
                mopidy \
                libspotify-dev && \
        rm -rf /var/lib/apt/lists/* && \
        rm -rf /tmp/*
RUN     python3 -m pip install \
                pyspotify \
                Mopidy-Local \
                Mopidy-Iris \
                Mopidy-Mobile \
                Mopidy-Spotify \
                Mopidy-Mopify

# 5. Configure user
RUN     usermod -d /mnt/mopidy mopidy
RUN     echo "mopidy ALL=NOPASSWD: /usr/local/lib/python3.7/dist-packages/mopidy_iris/system.sh" >> /etc/sudoers

# 6. Create folders
WORKDIR /mnt/mopidy
RUN     mkdir /mnt/mopidy/data
RUN     mkdir /mnt/mopidy/cache
COPY    scripts/ scripts/

# 7. Configuration local scan
RUN     (crontab -l 2>/dev/null || true; echo "0 */1 * * * mopidy --config  /mnt/mopidy/config/mopidy.conf local scan") | crontab -
RUN     ln -sf /mnt/mopidy/config/mopidy.conf /etc/mopidy/mopidy.conf

# 8. Audio settings
RUN     echo            \
"pcm.!default {         \n\
  type asym             \n\
  playback.pcm {        \n\
  type plug             \n\
  slave.pcm "output"    \n\
}                       \n\
capture.pcm {           \n\
  type plug             \n\
  slave.pcm "input"     \n\
  }                     \n\
}                       \n\
pcm.output {            \n\
  type hw               \n\
  card $AUDIO           \n\
}                       \n\
ctl.!default {          \n\
  type hw               \n\
  card $AUDIO           \n\
}" >> /etc/asound.conf


# 9. Container
EXPOSE  6680
VOLUME  /mnt/mopidy/config
VOLUME  /mnt/mopidy/local

ENTRYPOINT ["/usr/bin/dumb-init","--"]
CMD ["/bin/sh", "scripts/entry_point.sh", "mopidy", "--config", "/mnt/mopidy/config/mopidy.conf"]

Instalamos algunos paquetes de sistema necesarios para poder instalar Mopidy, el paquete curl se instala para poder usar el comando HEALTHCHECK en el fichero docker-compose.yml

Instalamos Mopidy usando el repositorio oficial, en este caso usamos Debian porque el repositorio está orientado a esta distribución de tal forma que al usar el comando lsb_release podemos parametrizar el acceso al repositorio.

Una vez instalado Mopidy instalamos algunos plugins (se podrían instalar más). Usamos el usuario creado mopidy para arrancar el servicio y configuramos las carpetas donde guardar la configuración (/config) y la carpeta de archivos locales (/local) para el plugin local.

Como el plugin local tiene que escanear si hay cambios en las carpetas añadimos una tarea cron que se ejecutará cada hora para refrescar carpetas. Este mismo comando se puede ejecutar desde la opción Start local scan para eso añadimos al usuario mopidy al fichero sudoers para que pueda lanzar el comando. Este comando lanzado a través de la web utilizar el fichero de configuración por defecto así que creamos un enlace simbólico al que vamos a usar.

En este caso el usuario mopidy no hace falta que tenga derechos de root por seguridad, aunque es verdad que sería necesario para actualizar algunos plugins en este caso es más práctico actualizar el contenedor.

También se usa el paquete dump-init, el proceso mopidy no está orientado a ser usado como proceso padre así que es difícil gestionar su ciclo de vida si sucede algo y es necesario reiniciarlo, por eso gestionamos el contenedor a través de dumb-init que es el proceso padre del contenedor y el que arranca el proceso de mopidy.

Para obtener un id de cliente y un secret de Spotify podemos ir aquí.

Compilación y ejecución

Para compilar podemos usar:
docker build –tag user/mopidy -f Dockerfile.mopidy .

Para ejecutar podemos usar:
docker container run \
–init \
–publish 6680:6680 \
–device /dev/snd \
–volume /mnt/media/mopidy/config:/mnt/mopidy/config \
–volume /mnt/media/mopidy/local:/mnt/mopidy/local \
–env RA_UUID=1000 \
–env RA_GUID=1000 \
–env RA_SRVC=mopidy \
–env RA_FLDR=/mnt/mopidy \
–detach \
user/mopidy

Un detalle importante es usar –device /dev/snd para que el dispositivo de sonido sea utilizado dentro del contenedor. Una vez que arranquemos el contenedor tendremos que hacer login en Spotify y puede que también refrescar el token y la lista de reproducción.

Migración

Si ya tenemos una instalación de Mopidy podemos coger el fichero de configuración buscándolo con:
sudo find / -name mopidy*
Normalmente estará en el home del usuario en la carpeta .config, por ejemplo /home/pi/.config/mopidy/mopidy.conf

Docker Transmission

El fichero Docker para Transmission está basado en este artículo aunque también existe una variante de linuxserver.

# 0. Official armv32 image
FROM ubuntu:latest

LABEL "guru.raraavis.creator"="blog@raraavis.guru"
LABEL "guru.raraavis.version"="1.0.0"
LABEL "guru.raraavis.release-date"="20/03/2021"
LABEL "guru.raraavis.description"="Raspberry Pi 3 image with Transmission"

# 1. Environment vars
ARG DEBIAN_FRONTEND=noninteractive
ARG TZ=Europe/Madrid
ENV TZ=$TZ

# 2. Update system and install necessary packages
RUN     apt-get update && \
        apt-get install -y \
                gosu \
                software-properties-common \
                gnupg2 \
                gnupg \
                gnupg1 && \
                rm -rf /var/lib/apt/lists/* && \
                rm -rf /tmp/*

# 3. Add transmission repository and install Transmission
RUN     add-apt-repository ppa:transmissionbt/ppa && \
        apt-get update && \
        apt-get install -y \
                transmission-cli \
                transmission-common \
                transmission-daemon && \
                rm -rf /var/lib/apt/lists/* && \
                rm -rf /tmp/*

# 4. Configure user
RUN     usermod -d /mnt/transmission debian-transmission

# 5. Create folder
WORKDIR /mnt/transmission
COPY    scripts/ scripts/

# 6. Container
EXPOSE  9091
VOLUME  /mnt/transmission/config
VOLUME  /mnt/transmission/watch
VOLUME  /mnt/transmission/download
VOLUME  /mnt/transmission/temp

ENTRYPOINT ["/bin/sh", "scripts/entry_point.sh"]
CMD ["/usr/bin/transmission-daemon", "-g", "/mnt/transmission/config", "-f", "-x", "/mnt/transmission/config/trans.PID"]

Para evitar un error parecido a este:
UDP Failed to set receive buffer: requested 4194304, got 425984 (tr-udp.c:84)

Tenemos que editar algunos valores del kernel en el host estos valores no se pueden modificar en el contenedor al ser valores compartidos. Hay varias formas de editar estos valores, una forma sería la siguiente:

Primero editamos el fichero sysctl.conf y añadimos lo siguiente (hay varias formas de editar sysctl).

net.core.rmem_max = 16777216
net.core.wmem_max = 4194304

Y refrescamos los valores con sysctl -p

Tenemos también varios volúmenes configurados:

  • /mnt/transmission/config para la configuración
  • /mnt/transmission/watch la carpeta watch donde buscar nuevos ficheros torrent para descargar
  • /mnt/transmission/download carpeta de descargas
  • /mnt/transmission/temp la carpeta temporal

Se usa un script especial para arrancar el contenedor y configurar los permisos, los detalles aquí.

Compilación y ejecución

Para compilar podemos usar:
docker build –tag user/transmission -f Dockerfile.transmission .

Para ejecutar podemos usar:
docker container run –init -p 9091:9091 -p 51413:51413 \
–volume /mnt/media/transmission/config:/mnt/transmission/config \
–volume /mnt/media/transmission/download:/mnt/transmission/download \
–volume /mnt/media/transmission/temp:/mnt/transmission/temp \
–volume /mnt/media/transmission/watch:/mnt/transmission/watch \
–env RA_UUID=1000 \
–env RA_GUID=1000 \
–env RA_SRVC=debian-transmission \
–env RA_FLDR=/mnt/transmission
\
–detach user/transmission

Migración

La carpeta de Transmission tiene no solo la configuración en el fichero settings.json también otras carpetas donde se guardan los ficheros torrent descargados y las estadísticas, se puede encontrar con sudo find / -name settings.json y normalmente estará ubicada en /var/lib/transmission-daemon
Hay que copiar todo este contenido dentro de la carpeta /mnt/transmission/config y actualizar el fichero settings.json para que apunte a las carpetas del contenedor (/mnt/transmission/download, ….)

Docker Plex

En este caso se ha usado un contenedor ya existente de Plex suministrado por linuxserver.

Compilación y ejecución

En este caso no hace falta compilar al ser una imagen sacada del registro de contenedores. Para ejecutar podemos ver los detalles aquí.

Migración

Si tenemos cuenta en Plex con hacer login se puede recuperar gran parte de la configuración, la carpeta con el resto de la información se puede sacar de aquí /var/lib/plexmediaserver/Library/Application Support/Plex Media Server tal y como se cuenta aquí.

Docker Compose

Ahora todos los contenedores se arrancan a la vez usando este fichero:

version: "3"
services:  
  mopidy:    
    container_name: mopidy    
    image: raraavis/mopidy    
    build:      
      context: ..
      dockerfile: media/mopidy/Dockerfile.mopidy
    ports:      
      - 6680:6680    
    volumes:      
      - /mnt/media/mopidy/config:/mnt/mopidy/config
      - /mnt/media/mopidy/local:/mnt/mopidy/local
    devices:
      - /dev/snd
    environment:      
      - RA_UUID=1000      
      - RA_GUID=1000      
      - RA_SRVC=mopidy
      - RA_FLDR=/mnt/mopidy
    healthcheck:
      test: ["CMD-SHELL", "curl -f http://localhost:6680 || bash -c 'kill -s 15 -1 && (sleep 10; kill -s 9 -1)'"]      
      interval: 30m
      timeout: 30s
      retries: 3
      start_period: 30s
    restart: unless-stopped
  transmission:    
    container_name: transmission    
    image: raraavis/transmission    
    build:      
      context: ..
      dockerfile: media/transmission/Dockerfile.transmission
    ports:      
      - 9091:9091      
      - 51413:51413    
    volumes:      
      - /mnt/media/transmission/config:/mnt/transmission/config
      - /mnt/media/temp:/mnt/transmission/temp
      - /mnt/media/download:/mnt/transmission/download
      - /mnt/media/watch:/mnt/transmission/watch
    environment:      
      - RA_UUID=1000      
      - RA_GUID=1000      
      - RA_SRVC=debian-transmission
      - RA_FLDR=/mnt/transmission
  plex:    
    container_name: plex    
    image: ghcr.io/linuxserver/plex    
    container_name: plex    
    network_mode: host    
    environment:      
      - PUID=105      
      - PGID=106      
      - VERSION=docker      
    #- PLEX_CLAIM= see .env file
    volumes:      
      - /mnt/media/plex/config:/config      
     #- /mnt/media/plex/tv:/tv      
      - /mnt/media/movies:/movies    
  restart: unless-stopped

Algunos aspectos a destacar son que el servidor Mopidy usa un health check para verificar si sigue activo, esto es debido a un bug reportado a Mopidy, el comando lo que hace es verificar si el servidor sigue activo y sino lo está mata el contenedor para que se reinicie usando el comando restart, más información aquí.

Para rellenar PLEX_CLAIM puede usarse esta página, esta variable está en un fichero de configuración separado .env otra opción sería usar secrets pero esto condiciona a la creación de un swarm.

Para docker transmission se pueden configurar varias carpetas pero si algunas de ellas está anidadas dentro de otras pueden ocultarse entre ellas de tal forma que no verá el contenedor los ficheros que dejemos, esto no es algo que suceda usando la línea de comandos de docker. La forma de evitarlo es indicar solo la carpeta raíz.

La estructura de carpetas es la misma que aparece en el código fuente.

Una vez que las imágenes han sido creadas y subidas al repositorio se puede comentar la sección de build.

Compose: compilación y ejecución

El fichero está configurado para compilar los ficheros dockerfile indicados usando docker-compose build el nombre de la imagen será el indicado en image.

Para ejecutar simplemente usamos docker-compose up y podremos ir viendo los mensajes o si lo queremos en segundo plano agregamos la opción –detach y quedaría docker-compose up –detach

Puede pasar que algunos valores del contenedor se mantengan entre ejecuciones y veamos mensajes como:
WARNING: Service «transmission» is using volume «/watch» from the previous container. Host mapping «/watch» has no effect. Remove the existing containers (with docker-compose rm transmission) to use the host volume mapping.

Solo hay que fijarse en el mensaje y ejecutar la instrucción que indica docker-compose rm transmission

Posible arquitectura

Se puede plantear lo siguiente:

  • Los contenedores Docker se colocan en la misma Rpi usando Docker compose.
  • Las carpetas de descarga apuntan a un almacenamiento externo, para saber como hacerlo se puede ver aquí.
  • La carpeta watch se puede crear como un recurso compartido vía Samba para poder dejar los ficheros torrent desde cualquier otra máquina y que se vayan descargando sin tener que hacerlo a través del cliente web.
  • Haría falta un cliente Plex que puede ser una SmartTv, u otra Raspberry Pi con esta distribución.
  • Opcionalmente se puede colocar por delante el proxy Apache como se indica más abajo.

(Opcional) Proxy Apache

Es posible usar un servidor Apache que sirva de proxy para poder acceder a los servicios de forma más cómoda usando una URL con nombre en lugar de a través de la IP y del puerto, en cualquier caso tiene que haber un DNS que pueda resolver los nombres.

Tenemos que habilitar algunos módulos, para permitir que funcionen los websockets cors.

a2enmod headers
a2enmod proxy
a2enmod proxy_http
a2enmod proxy_wstunnel
a2enmod headers

Vamos a suponer que la IP del contenedor es 192.168.1.2, y que el dominio es rpi.local, hay que crear los siguientes ficheros de configuración de apache y colocarlos en la carpeta /etc/apache2/sites-available

001-transmission.conf

<VirtualHost *:80>
      ServerName torrent.rpi.local
      ServerAlias torrent
      ProxyPreserveHost On
      ProxyPass / http://192.168.1.2:9091/
      ProxyPassReverse / http://192.168.1.2:9091/
      CustomLog /var/log/apache2/transmission.log combined
      ErrorLog /var/log/apache2/transmission.error.log
</VirtualHost>

Para habilitar el sitio ejecutamos:

sudo a2ensite 001-transmission.conf

002-mopidy.conf

<VirtualHost *:80>
      ServerName spotify.rpi.local
      ServerAlias spotify
      ProxyPreserveHost On
      ProxyPass / http://192.168.1.2:6680/
      ProxyPassReverse / http://192.168.1.2:6680/
      CustomLog /var/log/apache2/mopidy.log combined
      ErrorLog /var/log/apache2/mopidy.error.log
</VirtualHost>

Para habilitar el sitio ejecutamos:

sudo a2ensite 002-mopidy.conf

003-plex.conf

<VirtualHost *:80>
      ServerName plex.rpi.local
      ServerAlias plex
      ProxyPreserveHost On
      ProxyPass / http://192.168.1.2:32400/
      ProxyPassReverse / http://192.168.1.2:32400/
      CustomLog /var/log/apache2/plex.log combined
      ErrorLog /var/log/apache2/plex.error.log
</VirtualHost>

Para habilitar el sitio ejecutamos este comando para los diferentes virtual host, por ejemplo:

sudo a2ensite 003-plex.conf

Docker Raspberry: Servidor multimedia con Compose

USB externo en Raspberry Pi

Actualizado el 22 de Noviembre de 2020

Una forma rápida y sencilla de añadir un disco duro externo por USB y asociarlo a una carpeta, primero como siempre actualizar:

sudo apt-get update
sudo apt-get upgrade
sudo apt-get dist-upgrade

Antes de empezar tenemos que tener en cuenta que hay unidades USB con alimentación externa y otros sin ella, con alimentación externa no habrá problemas pero debido a las limitaciones de la RaspberryPi en términos de alimentación es posible que no pueda arrancar un disco que requiera alimentación por USB, así que lo primero será encontrar las unidades que tenemos conectadas.

sudo blkid

Si no vemos nuestra unidad y no tiene alimentación externa podemos probar a hacer los siguientes pasos:

Archivos sin fuente de alimentación externa

Editamos el fichero config.txt

sudo nano /boot/config.txt

Agregamos esta línea al final del fichero

max_usb_current=1

Reiniciamos

sudo reboot

Ahora volveríamos a probar el comando blkid y ver si aparece nuestra unidad, de no ser así podemos probar lo siguiente.

Instalamos wringPi desde aquí y comprobamos si tenemos el pin GPIO 38 activo que es el que nos permitirá doblar la potencia de la RaspberryPi y alimentar dispositivos externos:

gpio -g read 38

Si devuelve 1 lo tenemos habilitado, si vemos un 0 lo habilitamos con:

gpio -g write 38 1

En caso de no funcionar hay que usar una fuente de alimentación de más calidad, para verificar si el problema es la fuente de alimentación al arrancar la RaspberryPi tenemos que fijarnos en la cantidad de iconos de frambuesa que veamos, cuantos más mejor y sobre todo fijarnos de no ver ningún símbolo de un rayo lo que querría decir que no hay potencia suficiente.

Configurando unidad externa

Cuando veamos nuestra unidad usamos el siguiente comando:

sudo lsblk -o UUID,NAME,FSTYPE,SIZE,MOUNTPOINT,LABEL,MODEL

Veremos un listado de unidades conectadas, una buena pista suele ser mirar la columna LABEL y MODEL también por el tamaño podemos averiguar cual es nuestra unidad.

La columna FSTYPE muestra el tipo del sistema de archivos, hay que tener instalado el driver adecuado para que se pueda leer, por ejemplo para exFAT:

sudo apt install exfat-fuse

O para NTFS (poder leer y escribir):

sudo apt install ntfs-3g

Tenemos que crear la carpeta en la que queramos mapear la unidad de red, por ejemplo:

mkdir /media/usb/acme

Ahora localizamos el valor UUID (la primera columna) lo guardamos y editamos el fichero /etc/fstab, incluyendo esta línea:

UUID=12345678ABCDEFGH /media/usb/acme ntfs-3g default,uid=pi,gid=pi,nofail 0 0

Donde UUID es el valor que teníamos guardado, luego tenemos la ruta a la carpeta que hemos creado /media/usb/acme, ntfs-3g es el driver necesario para leer la unidad, en este caso ntfs-3g porque es una unidad con sistema de archivos ntfs, uid es el nombre de usuario que se usará para acceder a la unidad (en este ejemplo el usuario pi) y gid es el nombre del grupo con permiso para acceder (en este caso el grupo pi), nofail sirve para indicar que si la unidad no se puede montar en el arranque no se produzca un error.

En este caso hemos usado el usuario pi que viene por defecto en la instalación, si quisiéramos usar otro haríamos:

sudo adduser acme

Igual puede interesar crear un usuario solo para validarse por red pero no para poder iniciar sesión como un usuario normal, algo parecido a una cuenta de servicio.

sudo adduser -shell /bin/false --no-create-home acme

Ahora solo nos queda montar la unidad USB con:

sudo mount -a

Si accedemos a la carpeta que hemos creado veremos el contenido del disco, también podemos compartirla por unidad de red siguiendo los detalles de este post.

Referencias

https://www.htpcguides.com/power-2-5-hard-drive-with-raspberry-pi-b/
https://www.raspberrypi.org/documentation/configuration/external-storage.md
https://www.raspberrypi.org/forums/viewtopic.php?t=238095

USB externo en Raspberry Pi