diff --git a/QPKG/package_routines b/QPKG/package_routines index 83eba56..cd84a8d 100755 --- a/QPKG/package_routines +++ b/QPKG/package_routines @@ -112,7 +112,7 @@ QPKG_NAME="RoonServer" PKG_MAIN_REMOVE="{ ## Remove Roon Servers docker image - CS_DIR=`$CMD_GETCFG "container-station" Install_Path -f SYS_QPKG_CONFIG_FILE` + CS_DIR=`$CMD_GETCFG "container-station" Install_Path -f "${SYS_QPKG_CONFIG_FILE}"` $CS_DIR/bin/system-docker image rm 'ghcr.io/roonlabs/roonserver:latest' }" @@ -153,7 +153,7 @@ pkg_install(){ ## Creating required folders and setting permissions "${CMD_MKDIR}" -m 777 "${SYS_QPKG_DIR}"/id - CS_DIR=`$CMD_GETCFG "container-station" Install_Path -f SYS_QPKG_CONFIG_FILE` + CS_DIR=`$CMD_GETCFG "container-station" Install_Path -f "${SYS_QPKG_CONFIG_FILE}"` # Pull Roon Server docker image, so the first qpkg launch will take less time. $CS_DIR/bin/system-docker pull 'ghcr.io/roonlabs/roonserver:latest' diff --git a/QPKG/qpkg.cfg b/QPKG/qpkg.cfg index ca938d3..53e52f4 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-14" +QPKG_VER="2026-04-15" # Author or maintainer of the package QPKG_AUTHOR="Christopher Rieke" # License for the packaged application @@ -12,7 +12,7 @@ QPKG_AUTHOR="Christopher Rieke" QPKG_SUMMARY="Roon organizes your personal music files, TIDAL streams, and internet radio stations and adds rich data like artist photos, bios, tour dates, lyrics, credits, and more. Using Roon Server with remote apps for Mac, Windows, iOS, and Android you can stream audio around your home to Sonos, AirPlay, Squeezebox, and Roon Ready devices." # Preferred number in start/stop sequence. -QPKG_RC_NUM="101" +QPKG_RC_NUM="200" # Init-script used to control the start and stop of the installed application. QPKG_SERVICE_PROGRAM="RoonServer.sh" diff --git a/QPKG/shared/RoonServer.sh b/QPKG/shared/RoonServer.sh index 8e243d7..4cbf516 100755 --- a/QPKG/shared/RoonServer.sh +++ b/QPKG/shared/RoonServer.sh @@ -4,10 +4,10 @@ QPKG_NAME="RoonServer" 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 +QCS_QPKG_DIR=$(/sbin/getcfg $QCS_NAME Install_Path -f ${CONF}) +DOCKER_CMD="${QCS_QPKG_DIR}/bin/system-docker" CONTAINER_NAME=roonserver -COMPOSE_YML_DIR=$QPKG_ROOT/docker/compose +COMPOSE_YML_DIR="${QPKG_ROOT}/docker/compose" ROONSERVER_OPTIONS=(`/sbin/getcfg $QPKG_NAME options -f ${CONF}`) ROON_CHANNEL="production" @@ -34,6 +34,9 @@ ROON_DATABASE_DIR_FREE_INODES=`df -PThi "${ROON_DATAROOT}" | awk '{print $5}' | ROON_FFMPEG_DIR="${ROON_DATAROOT}/bin" ROON_LOG_FILE="${ROON_DATAROOT}/RoonOnNAS.log.txt" +echo $(basename "$0") >> ${ROON_LOG_FILE} +echo $@ >> ${ROON_LOG_FILE} + ST_COLOR="\033[38;5;34m" HL_COLOR="\033[38;5;197m" REG_COLOR="\033[0m" @@ -44,22 +47,6 @@ do declare $i=true done - -compose_docker_yml_files () { -COMPOSE_FILES="\ - -f ${COMPOSE_YML_DIR}/roonserver.yml \ - -f ${COMPOSE_YML_DIR}/platform_specific.yml " - -[ -z ${smb_cifs+x} ] || COMPOSE_FILES="${COMPOSE_FILES} -f $COMPOSE_YML_DIR/smb_cifs_support.yml" - -( [ -z ${usb_audio+x} ] && [ -z ${hdmi_audio+x} ] ) || COMPOSE_FILES="${COMPOSE_FILES} -f $COMPOSE_YML_DIR/audio.yml" - -[ -z ${usb_audio+x} ] || COMPOSE_FILES="${COMPOSE_FILES} -f $COMPOSE_YML_DIR/audio_usb.yml" - -[ -z ${hdmi_audio+x} ] || COMPOSE_FILES="${COMPOSE_FILES} -f $COMPOSE_YML_DIR/audio_hdmi.yml" -} - - ## Log Function echolog () { TIMESTAMP=$(date +%d.%m.%y-%H:%M:%S) @@ -77,8 +64,18 @@ echolog () { fi } -info () -{ +compose_docker_yml_files () { + COMPOSE_FILES="\ + -f ${COMPOSE_YML_DIR}/roonserver.yml \ + -f ${COMPOSE_YML_DIR}/platform_specific.yml " + + [ -z ${smb_cifs+x} ] || COMPOSE_FILES="${COMPOSE_FILES} -f $COMPOSE_YML_DIR/smb_cifs_support.yml" + ( [ -z ${usb_audio+x} ] && [ -z ${hdmi_audio+x} ] ) || COMPOSE_FILES="${COMPOSE_FILES} -f $COMPOSE_YML_DIR/audio.yml" + [ -z ${usb_audio+x} ] || COMPOSE_FILES="${COMPOSE_FILES} -f $COMPOSE_YML_DIR/audio_usb.yml" + [ -z ${hdmi_audio+x} ] || COMPOSE_FILES="${COMPOSE_FILES} -f $COMPOSE_YML_DIR/audio_hdmi.yml" +} + +info () { ## Echoing System Info echolog "ROON_DATABASE_DIR" "${ROON_DATAROOT} - [`[ -d \"${ROON_DATAROOT}\" ] && echo \"available\" || echo \"not available\"`]" echolog "ROON_DATABASE_DIR_FS" "${ROON_DATABASE_DIR_FS}" @@ -95,30 +92,42 @@ info () echolog "Roon-Channel" "${ROON_CHANNEL}" } -RoonOnNAS_folderCheck () -{ +RoonOnNAS_folderCheck () { if [ -d "${ROONONNAS_DIR}" ]; then [ -d "${ROONONNAS_DIR}/RoonOnNAS" ] || mkdir "${ROONONNAS_DIR}/RoonOnNAS" [ -d "${ROONONNAS_DIR}/RoonOnNAS/bin" ] || mkdir "${ROONONNAS_DIR}/RoonOnNAS/bin" fi } -start_RoonServer () { - if [ "${ROON_DATAROOT}" != "/RoonOnNAS" ] && [ -d "${ROON_DATAROOT}" ]; then - compose_docker_yml_files - export_vars - - ## Creating required directories, if they do not exist - [ -d "$ROON_ID_HOST_DIR" ] || mkdir "$ROON_ID_HOST_DIR" - [ -d "$ROON_TMP_DIR" ] || mkdir "$ROON_TMP_DIR" - - ${DOCKER_CMD} compose ${COMPOSE_FILES} up -d - - fi +getCSStatus () { + echo "$(/sbin/getcfg container-station status -f /etc/qpkg_run_status)" +} +checkCS () { + case $(getCSStatus) in + 0) + echolog "Container Station down." + exit 1; + ;; + 1) + echolog "Container Station is starting..." + SECONDS=0 + until [[ $(getCSStatus) == "2" ]]; do + if (( SECONDS > 120 )); then + echolog "Giving up..." + exit 1 + fi + echolog "($SECONDS) Container Station is not up yet. Waiting..." + sleep 5 + done + ;; + 2) + echolog "Container Station is up." + ;; + esac } -export_vars () -{ + +export_vars () { export ROON_DATAROOT export QPKG_ROOT export QNAP_MODEL @@ -128,55 +137,90 @@ export_vars () export ROON_CHANNEL } -start_daemon () -{ +start_webpanel () { [ -f ${ROON_DATAROOT}/earlyaccess.txt ] && ROON_CHANNEL="earlyaccess" - info #Launch the service in the background if RoonServer share exists. ln -sfn "${QPKG_ROOT}/web" "${WEB_PATH}${WEBUI}" - start_RoonServer - } +} + +start_roonserver () { + if [ "${ROON_DATAROOT}" != "/RoonOnNAS" ] && [ -d "${ROON_DATAROOT}" ]; then + compose_docker_yml_files + export_vars + + ## Creating required directories, if they do not exist + [ -d "$ROON_ID_HOST_DIR" ] || mkdir "$ROON_ID_HOST_DIR" + [ -d "$ROON_TMP_DIR" ] || mkdir "$ROON_TMP_DIR" + + echo "Docker Command: $(ls -lha ${DOCKER_CMD})" 2>&1 >> ${ROON_LOG_FILE} + echo "Docker CMD: $(${DOCKER_CMD} --version)" 2>&1 >> ${ROON_LOG_FILE} + echo "COMPOSE_FILES: ${COMPOSE_FILES}" 2>&1 >> ${ROON_LOG_FILE} + echo "CS Run-Status: $(/sbin/getcfg container-station status -f /etc/qpkg_run_status)" >> ${ROON_LOG_FILE} + sleep 60 + ${DOCKER_CMD} compose ${COMPOSE_FILES} up -d 2>&1 >> ${ROON_LOG_FILE} + + ${DOCKER_CMD} compose ${COMPOSE_FILES} up -d + fi +} case "$1" in start) - ENABLED=$(/sbin/getcfg $QPKG_NAME Enable -u -d FALSE -f $CONF) - RoonOnNAS_folderCheck - if [ "$ENABLED" != "TRUE" ]; then - echolog "$QPKG_NAME is disabled." - exit 1 - fi - CONTAINER_ID=$(${DOCKER_CMD} ps -a -q -f name=$CONTAINER_NAME) - if [ ! "$CONTAINER_ID" ]; then - echo "not running" - start_daemon - else - echolog "${QPKG_NAME} is already running (ID: $CONTAINER_ID)" - fi + RS_ENABLED=$(/sbin/getcfg $QPKG_NAME Enable -u -d FALSE -f $CONF) + CS_ENABLED=$(/sbin/getcfg "container-station" Enable -u -d FALSE -f $CONF) + + if [ "$RS_ENABLED" != "TRUE" ]; then + echolog "$QPKG_NAME is disabled." + exit 1 + fi + if [ "$CS_ENABLED" != "TRUE" ]; then + echolog "Container Station is disabled." + exit 1 + fi + + CONTAINER_ID=$(${DOCKER_CMD} ps -a -q -f name=$CONTAINER_NAME) + if [ ! "$CONTAINER_ID" ]; then + echolog "Starting Roon Server..." + info + checkCS + RoonOnNAS_folderCheck + start_webpanel + start_roonserver + else + echolog "${QPKG_NAME} is already running (ID: $CONTAINER_ID)" + fi ;; stop) - CONTAINER_ID=$(${DOCKER_CMD} ps -a -q -f name=$CONTAINER_NAME) - if [ ! "$CONTAINER_ID" ]; then + CS_ENABLED=$(/sbin/getcfg "container-station" Enable -u -d FALSE -f $CONF) + + # Check if CS has not been stopped before Roon Server + if [ "$CS_ENABLED" == "TRUE" ]; then + # --> CS is still up. + CONTAINER_ID=$(${DOCKER_CMD} ps -a -q -f name=$CONTAINER_NAME) + if [ ! "$CONTAINER_ID" ]; then + # --> No roonserver conatiner running echolog "${QPKG_NAME} is not running." - else + else + # --> Stopping roonserver conatiner echolog "Stopping RoonServer..." compose_docker_yml_files export_vars ${DOCKER_CMD} compose ${COMPOSE_FILES} down - if [[ $2 != "keepwebalive" ]]; then - rm -rf "${QPKG_ROOT}/web/tmp"/* - rm "${WEB_PATH}${WEBUI}" - fi + [ -d "${WEB_PATH}${WEBUI}" ] && rm "${WEB_PATH}${WEBUI}" echolog "RoonServer has been stopped." + fi + else + # -> CS is disabled: + # Edge case: CS has been stopped before RoonServer. We can assume RoonServer docker is down. Only the web-panel needs to be removed + [ -d "${WEB_PATH}${WEBUI}" ] && rm "${WEB_PATH}${WEBUI}" + echolog "RoonServer is not running." fi ;; restart) - isRestart=true $0 stop $0 start ;; - *) echo "Usage: $0 {start|stop|restart}" exit 1 diff --git a/QPKG/shared/web/content/about.php b/QPKG/shared/web/content/about.php index 77d01ed..c11af6b 100644 --- a/QPKG/shared/web/content/about.php +++ b/QPKG/shared/web/content/about.php @@ -90,7 +90,7 @@ $ContributorsManual = array(

-
+

diff --git a/QPKG/shared/web/content/info.php b/QPKG/shared/web/content/info.php index 9033ca7..5d0a5db 100644 --- a/QPKG/shared/web/content/info.php +++ b/QPKG/shared/web/content/info.php @@ -28,7 +28,7 @@ include_once("/home/httpd/cgi-bin/qpkg/RoonServer/__functions.php");


-->
-
+
@@ -39,7 +39,7 @@ include_once("/home/httpd/cgi-bin/qpkg/RoonServer/__functions.php");

: 1 ) { - echo '' . localize("OVERVIEW_ROONSERVER_PANEL_STATUS_RUNNING") . ''; + echo '' . localize("OVERVIEW_ROONSERVER_PANEL_STATUS_RUNNING") . ''; } else { echo '' . localize("OVERVIEW_ROONSERVER_PANEL_STATUS_STOPPED") . ''; } ?>
@@ -49,7 +49,7 @@ include_once("/home/httpd/cgi-bin/qpkg/RoonServer/__functions.php"); :

- + :
@@ -59,19 +59,21 @@ include_once("/home/httpd/cgi-bin/qpkg/RoonServer/__functions.php");

- + "> + data-bs-title=""> - + "> + data-bs-title=""> @@ -79,23 +81,22 @@ include_once("/home/httpd/cgi-bin/qpkg/RoonServer/__functions.php");
-
+
-
Settings
-
- +
+
- +
@@ -103,14 +104,15 @@ include_once("/home/httpd/cgi-bin/qpkg/RoonServer/__functions.php");
-
- + - Save + data-bs-title="Save & Restart" + data-bs-theme="dark" + >
@@ -127,11 +129,6 @@ include_once("/home/httpd/cgi-bin/qpkg/RoonServer/__functions.php"); echo "false"; } ?>; - // Enable Tooltips - $(function () { - document.querySelectorAll('[data-bs-toggle="tooltip"]') - }); - // Action when button for Modal is clicked $('.getModal').on('click', function (e) { @@ -149,20 +146,42 @@ include_once("/home/httpd/cgi-bin/qpkg/RoonServer/__functions.php"); $('#modal').modal('show'); return false; }); - function changeSettings(el) { - // add code to guide user to press save next - //console.log(el.id + ": " + el.checked); + function changeSettings() { + var qpkg_options_str = ""; + var qpkg_options_arr = qpkg_options_str.split(' '); + + var qnap_opt_arr = []; + document.getElementById('smb_cifs').checked && qnap_opt_arr.push("smb_cifs"); + document.getElementById('usb_audio').checked && qnap_opt_arr.push("usb_audio"); + document.getElementById('hdmi_audio').checked && qnap_opt_arr.push("hdmi_audio"); + + console.log(qnap_opt_arr.join(' ') == qpkg_options_arr.join(' ')); + + console.log(qnap_opt_arr.join(' ') == qpkg_options_arr.join(' ')); + + if ( qnap_opt_arr.join(' ') == qpkg_options_arr.join(' ') ) { + $("#saveButton").addClass("disabled"); + } else { + $("#saveButton").removeClass("disabled"); + } } $( document ).ready(function() { var qpkg_options_str = ""; - var qpkg_options_arr = qpkg_options_str.split(' '); - for (let conf_option of qpkg_options_arr) { - document.getElementById(conf_option).checked = true; - } + const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]') + const tooltipList = [...tooltipTriggerList].map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl)) + + if ( qpkg_options_str.length > 0 ) { + var qpkg_options_arr = qpkg_options_str.split(' '); + + for (let conf_option of qpkg_options_arr) { + document.getElementById(conf_option).checked = true; + } + } }); + // Function to download log files function saveOptions () { var qnap_options = ""; @@ -170,6 +189,8 @@ function saveOptions () { qnap_options += document.getElementById('usb_audio').checked ? "usb_audio;" : "" ; qnap_options += document.getElementById('hdmi_audio').checked ? "hdmi_audio;" : "" ; + $("#saveButton").addClass("disabled"); + document.getElementById('smb_cifs').checked var strUrl = '/cgi-bin/qpkg/RoonServer/ajax/ajax.php?a=setOptions&o=' + qnap_options; diff --git a/QPKG/shared/web/content/setup.php b/QPKG/shared/web/content/setup.php index 9887422..d957cb1 100644 --- a/QPKG/shared/web/content/setup.php +++ b/QPKG/shared/web/content/setup.php @@ -57,6 +57,7 @@ include_once("/home/httpd/cgi-bin/qpkg/RoonServer/__functions.php"); imageCssClassField: 'faCssClass', uiLibrary: 'bootstrap5', cascadeSelection: false, + cascadeCheck: false, selectionType: 'single', icons: { expand: '', @@ -121,10 +122,11 @@ include_once("/home/httpd/cgi-bin/qpkg/RoonServer/__functions.php");
- /proc/asound/cards'", - "MODAL_REINSTALL_HEADLINE": "Roon Server erneut installieren?", - "MODAL_REINSTALL_DESCRIPTION_1": "Möchtest du die aktuelle Version von Roon Server installieren?", - "MODAL_REINSTALL_DESCRIPTION_2": "Bei diesem Vorgang werden die Programmdateien, durch die aktuelle Version von der Roon Labs Webseite ersetzt.", - "MODAL_REINSTALL_LOADING": "Laden...", - "MODAL_REINSTALL_ICON_TOOLTIP": "Aktuelle Version des Roon Server erneut von der Roon Labs Webseite laden.", - "MODAL_REINSTALL_DB_UNTOUCHED": "Deine Roon Datanbank bleibt dabei unberührt.", - "MODAL_REINSTALL_ROONSERVER_WILL_STOP": "Roon Server wird für diesen Vorgang kurzzeitig beendet.", - "MODAL_REINSTALL_PROCEED_TEXT": "Ja, mein QNAP soll die aktuelle Roon Server Version laden und installieren", - "MODAL_REINSTALL_DONE": "Roon Server wurde durch die aktuelle Version von der Roon Labs Webseite ersetzt.", "MODAL_LOGFILES_HEADLINE": "Log-Dateien laden", "MODAL_LOGFILES_ICON_TOOLTIP": "Sämtliche Log-Dateien als .zip-Datei laden.", "MODAL_LOGFILES_CHECK_DOWNLOAD_FOLDER": "Bitte schaue in deinen Download-Ordner.", diff --git a/QPKG/shared/web/index.php b/QPKG/shared/web/index.php index 297398e..97f4e3e 100644 --- a/QPKG/shared/web/index.php +++ b/QPKG/shared/web/index.php @@ -4,7 +4,7 @@ include_once("/home/httpd/cgi-bin/qpkg/RoonServer/__functions.php"); ?> - + @@ -73,44 +73,56 @@ include_once("/home/httpd/cgi-bin/qpkg/RoonServer/__functions.php"); } ?> - - + +