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