diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml new file mode 100644 index 0000000..0ce7f37 --- /dev/null +++ b/.github/workflows/docker-image.yml @@ -0,0 +1,59 @@ +name: Docker Image CI + +on: + pull_request: + branches: + - "main" + push: + branches: + - "main" + +jobs: + build: + runs-on: ubuntu-24.04 + outputs: + image_date: ${{ steps.date.outputs.image_date }} + steps: + - name: Set build date for image tagging + id: date + run: echo "image_date=$(date +'%Y.%m.%d')" >> "$GITHUB_OUTPUT" + + - name: Checkout repository + uses: actions/checkout@v4.0.0 + + - name: Setup Docker buildx + uses: docker/setup-buildx-action@v3 + with: + driver-opts: image=moby/buildkit:latest + + - name: Docker build + uses: docker/build-push-action@v6 + with: + context: . + file: ./Dockerfile + push: false + tags: | + ${{ secrets.DOCKERHUB_USERNAME }}/roon-server:${{ steps.date.outputs.image_date }} + ${{ secrets.DOCKERHUB_USERNAME }}/roon-server:${{ github.sha }} + + - name: Docker compose build + run: docker compose build + + push: + needs: build + runs-on: ubuntu-24.04 + if: github.event_name == 'push' + steps: + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Docker push + uses: docker/build-push-action@v6 + with: + push: true + tags: | + ${{ secrets.DOCKERHUB_USERNAME }}/roon-server:${{ needs.build.outputs.image_date }} + ${{ secrets.DOCKERHUB_USERNAME }}/roon-server:${{ github.sha }} \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 7d28541..eaa8dca 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,57 +1,56 @@ ################## ## base stage ################## -FROM ubuntu:jammy AS BASE +FROM ubuntu:24.04 AS base USER root # Preconfigure debconf for non-interactive installation - otherwise complains about terminal # Avoid ERROR: invoke-rc.d: policy-rc.d denied execution of start. ARG DEBIAN_FRONTEND=noninteractive -ARG DISPLAY localhost:0.0 +ARG DISPLAY=localhost:0.0 RUN echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections RUN dpkg-divert --local --rename --add /sbin/initctl RUN ln -sf /bin/true /sbin/initctl RUN echo "#!/bin/sh\nexit 0" > /usr/sbin/policy-rc.d # configure apt -RUN apt-get update -q -RUN apt-get install --no-install-recommends -y -q apt-utils 2>&1 \ +RUN apt update -q +RUN apt install --no-install-recommends -y -q apt-utils 2>&1 \ | grep -v "debconf: delaying package configuration" -RUN apt-get install --no-install-recommends -y -q ca-certificates +RUN apt install --no-install-recommends -y -q ca-certificates # install prerequisites # Roon prerequisites: -# - Roon requirements: ffmpeg libasound2 libicu70 +# - Roon requirements: ffmpeg libasound2 # - Roon access samba mounts: cifs-utils # - Roon play to local audio device: alsa # - Query USB devices inside Docker container: usbutils udev -RUN apt-get install --no-install-recommends -y -q ffmpeg -RUN apt-get install --no-install-recommends -y -q libasound2 -RUN apt-get install --no-install-recommends -y -q libicu70 -RUN apt-get install --no-install-recommends -y -q cifs-utils -RUN apt-get install --no-install-recommends -y -q alsa -RUN apt-get install --no-install-recommends -y -q usbutils -RUN apt-get install --no-install-recommends -y -q udev +RUN apt install --no-install-recommends -y -q ffmpeg +RUN apt install --no-install-recommends -y -q libasound2-dev +RUN apt install --no-install-recommends -y -q cifs-utils +RUN apt install --no-install-recommends -y -q alsa +RUN apt install --no-install-recommends -y -q usbutils +RUN apt install --no-install-recommends -y -q udev # app prerequisites # - Docker healthcheck: curl # - App entrypoint downloads Roon: wget bzip2 # - set timezone: tzdata -RUN apt-get install --no-install-recommends -y -q curl -RUN apt-get install --no-install-recommends -y -q wget -RUN apt-get install --no-install-recommends -y -q bzip2 -RUN apt-get install --no-install-recommends -y -q tzdata +RUN apt install --no-install-recommends -y -q curl +RUN apt install --no-install-recommends -y -q wget +RUN apt install --no-install-recommends -y -q bzip2 +RUN apt install --no-install-recommends -y -q tzdata # apt cleanup -RUN apt-get autoremove -y -q -RUN apt-get -y -q clean +RUN apt autoremove -y -q +RUN apt clean -y -q RUN rm -rf /var/lib/apt/lists/* #################### ## application stage #################### FROM scratch -COPY --from=BASE / / +COPY --from=base / / LABEL maintainer="elgeeko1" LABEL source="https://github.com/elgeeko1/roon-server-docker" @@ -104,36 +103,44 @@ RUN echo "${TZ}" > /etc/timezone \ # accessing network shares that are not public, # or if the RoonServer build is mapped in from # the host filesystem. -ARG CONTAINER_USER=roon +ARG CONTAINER_USER=ubuntu ARG CONTAINER_USER_UID=1000 -RUN adduser --disabled-password --gecos "" --uid ${CONTAINER_USER_UID} ${CONTAINER_USER} - -# add container user to audio group -RUN usermod -a -G audio ${CONTAINER_USER} +RUN if [ "${CONTAINER_USER}" != "ubuntu" ]; \ + then useradd \ + --uid ${CONTAINER_USER_UID} \ + --user-group \ + ${CONTAINER_USER}; \ + fi +RUN usermod -aG audio ${CONTAINER_USER} # copy application files COPY app/entrypoint.sh /entrypoint.sh -RUN chmod +x /entrypoint.sh +RUN chmod +x /entrypoint.sh COPY README.md /README.md # configure filesystem ## map a volume to this location to retain Roon Server data RUN mkdir -p /opt/RoonServer \ - && chown ${CONTAINER_USER} /opt/RoonServer \ - && chgrp ${CONTAINER_USER} /opt/RoonServer + && chown ${CONTAINER_USER}:${CONTAINER_USER} /opt/RoonServer ## map a volume to this location to retain Roon Server cache RUN mkdir -p /var/roon \ - && chown ${CONTAINER_USER} /var/roon \ - && chgrp ${CONTAINER_USER} /var/roon -# volume for local music library -VOLUME ["/music"] + && chown ${CONTAINER_USER}:${CONTAINER_USER} /var/roon + +# create /music directory (users may override with a volume) +RUN mkdir -p /music \ + && chown ${CONTAINER_USER}:${CONTAINER_USER} /music \ + && chmod og+r /music USER ${CONTAINER_USER} # entrypoint # set environment variables consumed by RoonServer # startup script -ENV DISPLAY localhost:0.0 +ARG DISPLAY=localhost:0.0 +ENV DISPLAY=${DISPLAY} ENV ROON_DATAROOT=/var/roon ENV ROON_ID_DIR=/var/roon + ENTRYPOINT ["/entrypoint.sh"] +HEALTHCHECK --interval=1m --timeout=1s \ + CMD curl -f http://localhost:9330/display diff --git a/README.md b/README.md index e61692d..d0a72b0 100644 --- a/README.md +++ b/README.md @@ -1,47 +1,59 @@ # Roon Sever in Docker Roon Server in a docker container. -### Features +## Features + - Downloads and installs latest Roon Server on first container start - Subsequent in-app Roon Server upgrades persist - Audio input from a local music library - Audio input from Tidal or Qobuz - Audio output to USB DAC devices connected to the Roon Server host -- Audio output to RAAT devices such as the Roon app, Roon Bridge, -RoPieee, etc. +- Audio output to RAAT devices such as the Roon app, Roon Bridge, RoPieee, etc. - Audio output to local audio output on the Roon Server host - Local timezone support for accurate last.fm tagging - Persistent cache - Secure execution (unprivileged execution, macvlan network) - Privileged execution mode and host network are supported -# Running +## Configure the Roon Host + +### Install host prerequisites + +Install the following audio packages into the host that will run Roon. -## Install host prerequisites -Install the following audio packages into your host: -```sh -apt-get install alsa-utils libasound2 libasound2-data libasound2-plugins +```bash +apt install alsa-utils libasound2 libasound2-data libasound2-plugins ``` ### Create persistent data volumes and paths + Create persistent docker volumes to retain the binary installation of -Roon Server and its configuration across restarts of the service: -```sh +Roon Server and its configuration across restarts of the service. + +```bash docker volume create roon-server-data docker volume create roon-server-cache ``` -Create the folder on your host while which contains your -local music library. Example: `/home/myuser/roon/music`. +Locate (or create) a folder to host your local music library. This step is optional and only needed if you have a local music library. - This folder can also be used as a Samba or NFS share for network access to your library. - This folder is optional. Omit if you plan to exclusively stream music. -```sh + +Example: + +```bash mkdir -p ~/roon/music ``` -## Option 1: Run in least secure mode (easiest) -Run using privileged execution mode and host network mode: -```sh +## Run Roon + +There are three ways to configure the Roon Docker container, each with different security levels. The first option is the easiest and simplest and should work for most users. + +### Least secure mode (easiest) + +This is the simplest way to run the docker container. Run using privileged execution mode and host network mode: + +```bash docker run \ --name roon-server \ --volume roon-server-data:/opt/RoonServer \ @@ -52,11 +64,13 @@ docker run \ elgeeko/roon-server ``` -## Option 2: Run in macvlan mode (more secure) -Run in an unprivileged container using macvlan network mode. Replace the subnet, gateway and IP address to match your local network. +### Run in macvlan mode (more secure) -### Create docker macvlan network -```sh +Run in an unprivileged container using macvlan network mode. Replace the subnet, gateway, IP address, and primary ethernet adapter to match your local network. + +#### Create docker macvlan network + +```bash docker network create \ --driver macvlan \ --subnet 192.168.1.0/24 \ @@ -65,8 +79,9 @@ docker network create \ roon ``` -### Run using unprivileged execution mode and macvlan network mode -```sh +### Run the container with the macvlan network + +```bash docker run \ --name roon-server \ --publish-all \ @@ -78,23 +93,25 @@ docker run \ elgeeko/roon-server ``` -## Option 3: Bridge mode -This works but with a significant limitation. Docker containers on bridged networks -don't receive broadcast or multicast communication, which is used by Roon Server +## Run the container in bridged mode + +This option works but with a significant limitation. Docker containers on bridged networks don't receive broadcast or multicast communication, which is used by Roon Server to discover RAAT devices such as Roon Bridge or RoPieee. Hence, Roon Server is limited to USB DACs or your Roon App on your PC. See the Dockerfile source below for ports to open. See Docker documentation for creating and using a bridged network. -# Additional functionality +## Additional functionality ### Useful docker flags + You may optionally want to add the `-d` flag to output to syslog instead of the console, and `--restart-unless-stopped` flag to restart the container if it fails. ### Use USB DACs connected to the host + Add the following arguments to the `docker run` command: `--volume /run/udev:/run/udev:ro` - allow Roon to enumerate USB devices `--device /dev/bus/usb` - allow Roon to access USB devices (`/dev/usbmon0` for Fedora) @@ -102,11 +119,13 @@ Add the following arguments to the `docker run` command: `--group-add $(getent group audio | cut -d: -f3)` - add container user to host 'audio' group ### Synchronize filesystem and last.fm timestamps with your local timezone + Add the following arguments to the `docker run` command: `--volume /etc/localtime:/etc/localtime:ro` - map local system clock to container clock `--volume /etc/timezone:/etc/timezone:ro` - map local system timezone to container timezone -# Known Issues +## Known Issues + - USB DACs connected to the system for the first time do not appear in Roon. The workaround is to restart the container. Once the device has been initially connected, disconnecting and reconnecting is reflected in Roon. @@ -116,10 +135,12 @@ run the container with the `user=root` option in the `docker run` command. requires. Add the following argument to the `docker run` command: `--ulimit nofile=8192` -# Building from the Dockerfile +## Building from the Dockerfile + `docker build .` -# Resources +## Resources + - [elgeeko/roon-server](https://hub.docker.com/repository/docker/elgeeko/roon-server) on Docker Hub - Ansible script to deploy the Roon Server image, as well as an optional Samba server for network sharing of a local music library: https://github.com/elgeeko1/elgeeko1-roon-server-ansible - Roon Labs Linux install instructions: https://help.roonlabs.com/portal/en/kb/articles/linux-install diff --git a/docker-compose.yml b/docker-compose.yml index 02cc54d..9e242fc 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,14 +1,15 @@ -version: '3' services: roon: container_name: roon-server - image: elgeeko/roon-server:latest + build: + context: . + image: roon-server:latest restart: unless-stopped network_mode: host privileged: true - user: roon + user: ubuntu volumes: - - ~/music:/music:ro # TODO: replace ~/mymusic with your music directory + - ~/music:/music:ro # TODO: replace ~/music with your music directory - roon-server-data:/opt/RoonServer - roon-server-cache:/var/roon - /etc/localtime:/etc/localtime:ro