diff --git a/QPKG/qpkg.cfg b/QPKG/qpkg.cfg index c416187..428ed5a 100644 --- a/QPKG/qpkg.cfg +++ b/QPKG/qpkg.cfg @@ -3,7 +3,7 @@ QPKG_NAME="RoonServer" # Name of the display application. QPKG_DISPLAY_NAME="Roon Server" # Version of the packaged application. -QPKG_VER="2026-04-10" +QPKG_VER="2026-04-12" # Author or maintainer of the package QPKG_AUTHOR="Christopher Rieke" # License for the packaged application diff --git a/QPKG/shared/RoonServer.sh b/QPKG/shared/RoonServer.sh index 1df8ba6..a058d36 100755 --- a/QPKG/shared/RoonServer.sh +++ b/QPKG/shared/RoonServer.sh @@ -6,7 +6,16 @@ QPKG_ROOT=`/sbin/getcfg $QPKG_NAME Install_Path -f ${CONF}` QCS_NAME="container-station" QCS_QPKG_DIR=$(/sbin/getcfg $QCS_NAME Install_Path -f $CONF) DOCKER_CMD=$QCS_QPKG_DIR/bin/system-docker -CONT_NAME="qnap-roonserver" +CONTAINER_NAME=roonserver +COMPOSE_YML_DIR=$QPKG_ROOT/docker/compose/ + +COMPOSE_FILES="\ + -f $COMPOSE_YML_DIR/roonserver.yml \ + -f $COMPOSE_YML_DIR/platform_specific.yml \ + -f $COMPOSE_YML_DIR/smb_cifs_support.yml \ + -f $COMPOSE_YML_DIR/audio.yml \ + -f $COMPOSE_YML_DIR/audio_usb.yml \ + -f $COMPOSE_YML_DIR/audio_hdmi.yml" WEB_PATH="/home/httpd" WEBUI=$(/sbin/getcfg $QPKG_NAME webUI -f ${CONF}); @@ -87,12 +96,14 @@ start_RoonServer () { export QNAP_SERIAL export QNAP_QTS_VER + export CONTAINER_NAME + ## Creating required directories, if they do not exist [ -d "$ROON_ID_DIR" ] || mkdir "$ROON_ID_DIR" [ -d "$ROON_TMP_DIR" ] || mkdir "$ROON_TMP_DIR" - ${DOCKER_CMD} compose -f $QPKG_ROOT/docker/docker-compose.yml -f $QPKG_ROOT/docker/qnap.yml up -d - + ${DOCKER_CMD} compose ${COMPOSE_FILES} up -d + fi } @@ -113,7 +124,8 @@ case "$1" in echolog "$QPKG_NAME is disabled." exit 1 fi - if [ -z `$DOCKER_CMD compose ps -q $CONT_NAME` ] || [ -z `$DOCKER_CMD ps -q --no-trunc | grep $($DOCKER_CMD compose ps -q $CONT_NAME)` ]; then + if [ ! "$(${DOCKER_CMD} ps -a -q -f name=$CONTAINER_NAME)" ]; then + echo "not running" start_daemon else echolog "${QPKG_NAME} is already running with..." @@ -121,18 +133,18 @@ case "$1" in ;; stop) - if [ -z `$DOCKER_CMD compose ps -q $CONT_NAME` ] || [ -z `$DOCKER_CMD ps -q --no-trunc | grep $($DOCKER_CMD compose ps -q $CONT_NAME)` ]; then + if [ ! "$(${DOCKER_CMD} ps -a -q -f name=$CONTAINER_NAME)" ]; then echolog "${QPKG_NAME} is not running." else echolog "Stopping RoonServer..." - ${DOCKER_CMD} compose -f $QPKG_ROOT/docker/docker-compose.yml -f $QPKG_ROOT/docker/qnap.yml down + export CONTAINER_NAME + ${DOCKER_CMD} compose ${COMPOSE_FILES} down if [[ $2 != "keepwebalive" ]]; then rm -rf "${QPKG_ROOT}/web/tmp"/* rm "${WEB_PATH}${WEBUI}" fi echolog "RoonServer has been stopped." fi - ${DOCKER_CMD} compose -f $QPKG_ROOT/docker/docker-compose.yml -f $QPKG_ROOT/docker/qnap.yml down ;; restart) diff --git a/QPKG/shared/docker/Dockerfile b/QPKG/shared/docker/Dockerfile deleted file mode 100755 index a3dc7a5..0000000 --- a/QPKG/shared/docker/Dockerfile +++ /dev/null @@ -1,30 +0,0 @@ -FROM debian:12-slim - -RUN apt-get update \ - && apt-get -y upgrade \ - && apt-get -y install bash curl bzip2 ffmpeg cifs-utils alsa-utils libicu72 - -ENV ROON_SERVER_PKG RoonServer_linuxx64.tar.bz2 -ENV ROON_SERVER_URL https://download.roonlabs.net/builds/${ROON_SERVER_PKG} -ENV ROON_DATAROOT /data -ENV ROON_ID_DIR /data - -ENV ROON_DATAROOT=/Roon/data -ENV ROON_ID_DIR=/Roon/data - -COPY entrypoint.sh /entrypoint.sh - -# Informational only — requires --net=host for multicast discovery -EXPOSE 9003/udp 9100-9200/tcp 9200-9250/tcp 9330-9339/tcp 55000/tcp - -# Healthcheck uses /proc directly instead of pgrep to avoid procps dependency -HEALTHCHECK --interval=30s --timeout=10s --start-period=120s --retries=3 \ - CMD grep -ql '[R]oonServer.dll' /proc/[0-9]*/cmdline 2>/dev/null || exit 1 - - -STOPSIGNAL SIGTERM - -# entrypoint.sh downloads RoonServer on first run (to /Roon/app), then -# exec's into Server/RoonServer — the stock bash launcher that handles -# .NET runtime discovery, ulimit, self-update swap, and restart (exit 122). -ENTRYPOINT ["/entrypoint.sh"] diff --git a/QPKG/shared/docker/compose/audio.yml b/QPKG/shared/docker/compose/audio.yml new file mode 100644 index 0000000..ab59288 --- /dev/null +++ b/QPKG/shared/docker/compose/audio.yml @@ -0,0 +1,6 @@ +services: + roonserver: + devices: + - /dev/snd:/dev/snd + group_add: + - audio diff --git a/QPKG/shared/docker/compose/audio_hdmi.yml b/QPKG/shared/docker/compose/audio_hdmi.yml new file mode 100644 index 0000000..3c2d5ca --- /dev/null +++ b/QPKG/shared/docker/compose/audio_hdmi.yml @@ -0,0 +1,5 @@ +services: + roonserver: + devices: + - /dev/dri:/dev/dri + diff --git a/QPKG/shared/docker/compose/audio_usb.yml b/QPKG/shared/docker/compose/audio_usb.yml new file mode 100644 index 0000000..478c014 --- /dev/null +++ b/QPKG/shared/docker/compose/audio_usb.yml @@ -0,0 +1,6 @@ +services: + roonserver: + volumes: + - /run/udev:/run/udev:ro + devices: + - /dev/bus/usb:/dev/bus/usb diff --git a/QPKG/shared/docker/qnap.yml b/QPKG/shared/docker/compose/platform_specific.yml similarity index 66% rename from QPKG/shared/docker/qnap.yml rename to QPKG/shared/docker/compose/platform_specific.yml index 964b962..91b31e8 100755 --- a/QPKG/shared/docker/qnap.yml +++ b/QPKG/shared/docker/compose/platform_specific.yml @@ -1,13 +1,14 @@ services: - qnap-roonserver: + roonserver: environment: - "QNAP_MODEL=${QNAP_MODEL:-Docker}" - "QNAP_SERIAL=${QNAP_SERIAL:-NoSerial}" - "QNAP_QTS_VER=${QNAP_QTS_VER:-System}" - "ROONMNT_DIR=/Roon" volumes: - # mount all disks and shared folder as read-only - - /share:/share:ro + # mount all disks and shared folders + - /share:/share + # mount database folder - ${ROON_DATAROOT:-/dev/null}:/Roon/data @@ -16,6 +17,6 @@ services: - /etc/os-release:/etc/os-release:ro # fake qnap binaries - - ./getcfg.sh:/sbin/getcfg:ro - - ./getsysinfo.sh:/sbin/getsysinfo - - ./get_hwsn.sh:/sbin/get_hwsn + - ../scripts/getcfg.sh:/sbin/getcfg:ro + - ../scripts/getsysinfo.sh:/sbin/getsysinfo + - ../scripts/get_hwsn.sh:/sbin/get_hwsn diff --git a/QPKG/shared/docker/compose/roonserver.yml b/QPKG/shared/docker/compose/roonserver.yml new file mode 100755 index 0000000..d6cf8f8 --- /dev/null +++ b/QPKG/shared/docker/compose/roonserver.yml @@ -0,0 +1,11 @@ +services: + roonserver: + image: ghcr.io/roonlabs/roonserver:latest + container_name: ${CONTAINER_NAME} + network_mode: host + environment: + - TZ=${TZ:-UTC} + stop_grace_period: 45s + restart: unless-stopped + logging: + driver: local \ No newline at end of file diff --git a/QPKG/shared/docker/compose/smb_cifs_support.yml b/QPKG/shared/docker/compose/smb_cifs_support.yml new file mode 100644 index 0000000..7223e19 --- /dev/null +++ b/QPKG/shared/docker/compose/smb_cifs_support.yml @@ -0,0 +1,6 @@ +services: + roonserver: + cap_add: + - SYS_ADMIN + - DAC_READ_SEARCH + diff --git a/QPKG/shared/docker/docker-compose.yml b/QPKG/shared/docker/docker-compose.yml deleted file mode 100755 index 65c86b8..0000000 --- a/QPKG/shared/docker/docker-compose.yml +++ /dev/null @@ -1,21 +0,0 @@ -services: - qnap-roonserver: - #image: ghcr.io/roonlabs/roonserver:latest - build: . - container_name: qnap-roonserver - network_mode: host - environment: - - TZ=${TZ:-UTC} - stop_grace_period: 45s - restart: unless-stopped - cap_add: - - SYS_ADMIN - - DAC_READ_SEARCH - devices: - - /dev/snd:/dev/snd - - /dev/bus/usb:/dev/bus/usb - - /dev/dri:/dev/dri - group_add: - - audio - logging: - driver: local \ No newline at end of file diff --git a/QPKG/shared/docker/entrypoint.sh b/QPKG/shared/docker/entrypoint.sh deleted file mode 100755 index e2e5a86..0000000 --- a/QPKG/shared/docker/entrypoint.sh +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -ROON_APP_DIR="/Roon/app" -ROON_INSTALLED="${ROON_APP_DIR}/.installed" -ROON_DOWNLOAD_URL="${ROON_DOWNLOAD_URL:-https://download.roonlabs.net/builds/RoonServer_linuxx64.tar.bz2}" - -# Download and install RoonServer on first run -if [ ! -f "$ROON_INSTALLED" ]; then - echo "RoonServer not found — downloading..." - mkdir -p "$ROON_APP_DIR" - curl -fL --progress-bar -o /tmp/RoonServer.tar.bz2 "$ROON_DOWNLOAD_URL" - echo "Extracting..." - tar xjf /tmp/RoonServer.tar.bz2 -C "$ROON_APP_DIR" - rm -f /tmp/RoonServer.tar.bz2 - - # libharfbuzz.so links against libfreetype.so.6 but bundled lib has no soname suffix - ln -sf "${ROON_APP_DIR}/RoonServer/Appliance/libfreetype.so" \ - "${ROON_APP_DIR}/RoonServer/Appliance/libfreetype.so.6" - - # Record the installed Roon version from the tarball's VERSION file - if [ -f "${ROON_APP_DIR}/RoonServer/VERSION" ]; then - cp "${ROON_APP_DIR}/RoonServer/VERSION" "$ROON_INSTALLED" - else - echo "unknown" > "$ROON_INSTALLED" - fi - echo "RoonServer installed successfully." -fi - -# Log versions at startup -echo "Image: $(cat /etc/roon-image-version 2>/dev/null || echo 'unknown')" -echo "Roon: $(sed -n '2p' "$ROON_INSTALLED" 2>/dev/null || echo 'unknown')" - -# start.sh handles restart-on-exit-122 without a full container restart -exec "${ROON_APP_DIR}/RoonServer/start.sh" diff --git a/QPKG/shared/docker/get_hwsn.sh b/QPKG/shared/docker/get_hwsn.sh deleted file mode 100755 index ba3a236..0000000 --- a/QPKG/shared/docker/get_hwsn.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh -echo $QNAP_SERIAL -echo "get_hwsn $@" >> /Roon/data/args.txt diff --git a/QPKG/shared/docker/scripts/get_hwsn.sh b/QPKG/shared/docker/scripts/get_hwsn.sh new file mode 100755 index 0000000..15a747d --- /dev/null +++ b/QPKG/shared/docker/scripts/get_hwsn.sh @@ -0,0 +1,2 @@ +#!/bin/sh +echo $QNAP_SERIAL \ No newline at end of file diff --git a/QPKG/shared/docker/getcfg.sh b/QPKG/shared/docker/scripts/getcfg.sh similarity index 100% rename from QPKG/shared/docker/getcfg.sh rename to QPKG/shared/docker/scripts/getcfg.sh diff --git a/QPKG/shared/docker/getsysinfo.sh b/QPKG/shared/docker/scripts/getsysinfo.sh similarity index 100% rename from QPKG/shared/docker/getsysinfo.sh rename to QPKG/shared/docker/scripts/getsysinfo.sh diff --git a/QPKG/shared/web/RoonServerQNAP.css b/QPKG/shared/web/RoonServerQNAP.css index 56b17ec..5b402b0 100644 --- a/QPKG/shared/web/RoonServerQNAP.css +++ b/QPKG/shared/web/RoonServerQNAP.css @@ -59,7 +59,21 @@ body { stroke-dashoffset: 48; animation: stroke 0.3s cubic-bezier(0.65, 0, 0.45, 1) 0.8s forwards; } - + /* Toggle switch */ +.toggle-track { + width: 40px; height: 22px; border-radius: 11px; + background: #a8a8a8; position: relative; cursor: pointer; + transition: background 0.2s ease; flex-shrink: 0; +} +.toggle-track::after { + content: ''; position: absolute; top: 2px; left: 2px; + width: 18px; height: 18px; border-radius: 50%; + background: #64748b; transition: all 0.2s ease; +} +input:checked + .toggle-track { background: #7ac142; } +input:checked + .toggle-track::after { + transform: translateX(18px); background: #fff; +} @keyframes stroke { 100% { stroke-dashoffset: 0; diff --git a/QPKG/shared/web/__functions.php b/QPKG/shared/web/__functions.php index 34048e8..4a2e259 100644 --- a/QPKG/shared/web/__functions.php +++ b/QPKG/shared/web/__functions.php @@ -156,6 +156,14 @@ unset($arrData); unset($arrTemp); unset($arrNodes); +function debug_to_console($data) { + $output = $data; + if (is_array($output)) + $output = implode(',', $output); + + echo ""; +} + function localize($phrase) { /* Static keyword is used to ensure the file is loaded only once */ @@ -192,67 +200,19 @@ function localize($phrase) function isRunning($option = null) { - $getPIDcmd = 'ps aux | grep "' . APPINSTALLPATH . '/RoonServer/start.sh" | grep -v grep | awk \'{print $1}\''; - $RoonServerPID = exec($getPIDcmd); - - if ($RoonServerPID > 0) { - if (is_dir('/proc/' . $RoonServerPID)) { - $pid = $RoonServerPID; - $running = true; - } else { - $pid = ""; - $running = false; - } - } else { - $pid = ""; - $running = false; - } - switch ($option) { - case 'getpid': - return $pid; - break; - default: - return $running; - } + $getDockerCont = CS_INSTALLPATH . '/bin/system-docker ps -aqf "name=roonserver"'; + $containerID = exec($getDockerCont); + + return $containerID; + } -function GetAsoundCards() +function getRoonServerVersion($option = null) { - $cards = array(); - $cardsPath = "/proc/asound/cards"; - $cardsContents = trim(file_get_contents($cardsPath)); - $splitNewline = explode(PHP_EOL, $cardsContents); - foreach ($splitNewline as $line => $key) { - if (strpos($key, "]: ")) { - $r = parseCardsLine(trim($key)); - $cards[] = $r; - } - } - return $cards; -} + $getRoonServerVer = CS_INSTALLPATH . '/bin/system-docker exec -it roonserver cat /Roon/app/RoonServer/VERSION'; + $roonServerVer = exec($getRoonServerVer); -function parseCardsLine($str) -{ - $result = array(); - $r = explode(" - ", $str); - $result["connection"] = substr($r[0], strpos($r[0], ']: ') +3 ); - preg_match('/\[([\s\S])\w+/', $r[0], $matches, PREG_OFFSET_CAPTURE); - $result["name"] = $r[1]; - return $result; -} - -function acardsNice() -{ - $arrSCards = GetAsoundCards(); - $strOutput = ""; - foreach ($arrSCards as &$value) { - $strSCardName = $value['name']; - $strSCardConnection = $value['connection']; - - $strOutput = $strOutput . '
- : ' . localize("OVERVIEW_ROONSERVER_PANEL_STATUS_RUNNING") . '';
+ : 1 ) {
+ echo '' . localize("OVERVIEW_ROONSERVER_PANEL_STATUS_RUNNING") . '';
} else {
echo '' . localize("OVERVIEW_ROONSERVER_PANEL_STATUS_STOPPED") . '';
} ?>
:
- :
-
- :
- : ">
+ :