From 97613b227f9e5f4e70f63a2a48c268bc0f07daa0 Mon Sep 17 00:00:00 2001 From: "Josh.5" Date: Tue, 29 Aug 2023 17:21:31 +1200 Subject: [PATCH] Run Sunshine as a service Goodbye Flatpak Sunshine. Hello AppImage. Until a deb package is available for Debian Bookworm, we can just run sunshine as an AppImage. It is faster to startup and is a little less bloated. The goal here is to run Sunshine as a service managed by supervisord so that it is kept running and is restarted automatically if stopped. --- Dockerfile.debian | 17 ++++- overlay/templates/sunshine/apps.json | 17 ++++- overlay/templates/sunshine/sunshine.conf | 8 ++- overlay/usr/bin/common-functions.sh | 37 +++++++++- overlay/usr/bin/install_sunshine.sh | 91 ------------------------ overlay/usr/bin/start-desktop.sh | 9 ++- overlay/usr/bin/start-sunshine.sh | 56 +++++++++------ overlay/usr/bin/start-udev.sh | 9 ++- overlay/usr/bin/sunshine-run | 43 +++++++---- overlay/usr/bin/sunshine-stop | 47 +++++++++++- 10 files changed, 195 insertions(+), 139 deletions(-) delete mode 100755 overlay/usr/bin/install_sunshine.sh diff --git a/Dockerfile.debian b/Dockerfile.debian index fd88b2b..2bfd1c2 100644 --- a/Dockerfile.debian +++ b/Dockerfile.debian @@ -483,6 +483,17 @@ RUN \ && \ echo +# Install Sunshine +ARG SUNSHINE_VERSION=0.20.0 +RUN \ + echo "**** Install Sunshine ****" \ + && wget -O /usr/bin/sunshine \ + https://github.com/LizardByte/Sunshine/releases/download/v${SUNSHINE_VERSION}/sunshine.AppImage \ + && chmod +x /usr/bin/sunshine \ + && setcap cap_sys_admin+p /usr/bin/sunshine \ + && \ + echo + # Install Steam RUN \ echo "**** Update apt database ****" \ @@ -506,7 +517,9 @@ RUN \ ARG DUMB_INIT_VERSION=1.2.5 RUN \ echo "**** Install dumb-init ****" \ - && wget -O /usr/bin/dumb-init https://github.com/Yelp/dumb-init/releases/download/v${DUMB_INIT_VERSION}/dumb-init_${DUMB_INIT_VERSION}_x86_64 \ + && wget --no-check-certificate --no-cookies --quiet \ + -O /usr/bin/dumb-init \ + https://github.com/Yelp/dumb-init/releases/download/v${DUMB_INIT_VERSION}/dumb-init_${DUMB_INIT_VERSION}_x86_64 \ && chmod +x /usr/bin/dumb-init \ && \ echo @@ -535,7 +548,7 @@ ENV \ NEKO_PASSWORD_ADMIN=admin \ ENABLE_STEAM="true" \ STEAM_ARGS="-silent" \ - ENABLE_SUNSHINE="false" \ + ENABLE_SUNSHINE="true" \ ENABLE_EVDEV_INPUTS="false" # Configure required ports diff --git a/overlay/templates/sunshine/apps.json b/overlay/templates/sunshine/apps.json index 3674686..230b61e 100644 --- a/overlay/templates/sunshine/apps.json +++ b/overlay/templates/sunshine/apps.json @@ -4,11 +4,22 @@ }, "apps": [ { - "name": "Steam BigPicture", - "output": "steam.txt", - "cmd": "", + "name": "Desktop", + "image-path": "desktop.png", + "exclude-global-prep-cmd": "true" + }, + { + "name": "Steam Big Picture", + "image-path": "steam.png", + "exclude-global-prep-cmd": "false", "detached": [ "\/usr\/games\/steam steam:\/\/open\/bigpicture" + ], + "prep-cmd": [ + { + "do": "", + "undo": "\/usr\/bin\/xfce4-close-all-windows" + } ] } ] diff --git a/overlay/templates/sunshine/sunshine.conf b/overlay/templates/sunshine/sunshine.conf index 81a4e42..1ac09c8 100644 --- a/overlay/templates/sunshine/sunshine.conf +++ b/overlay/templates/sunshine/sunshine.conf @@ -28,6 +28,10 @@ # min_log_level = info +# A list of commands to be run before/after all applications. +# If any of the prep-commands fail, starting the application is aborted. +global_prep_cmd = [{"do":"/usr/bin/xfce4-minimise-all-windows","undo":"/usr/bin/sunshine-stop"}] + # The origin of the remote endpoint address that is not denied for HTTP method /pin # Could be any of the following values: # pc|lan|wan @@ -95,7 +99,7 @@ min_log_level = info # ping_timeout = 10000 # The file where configuration for the different applications that Sunshine can run during a stream -file_apps = /home/default/sunshine/apps.json +file_apps = apps.json # Percentage of error correcting packets per data packet in each video frame # Higher values can correct for more network packet loss, but at the cost of increasing bandwidth usage @@ -111,7 +115,7 @@ file_apps = /home/default/sunshine/apps.json # # Unlike simply broadcasting to multiple Client, this will generate distinct video streams. # Note, CPU usage increases for each distinct video stream generated -# channels = 1 +channels = 2 # The back/select button on the controller # On the Shield, the home and powerbutton are not passed to Moonlight diff --git a/overlay/usr/bin/common-functions.sh b/overlay/usr/bin/common-functions.sh index 464f45c..5068fd5 100755 --- a/overlay/usr/bin/common-functions.sh +++ b/overlay/usr/bin/common-functions.sh @@ -28,7 +28,7 @@ wait_for_x() { wait_for_udev() { MAX=10 CT=0 - while [ ! -f /tmp/.udev-started ]; do + while [ ! -f /tmp/.started-udev ]; do sleep 1 CT=$(( CT + 1 )) if [ "$CT" -ge "$MAX" ]; then @@ -53,6 +53,20 @@ wait_for_docker() { echo "DOCKERD RUNNING!" } +# Wait for desktop to start +wait_for_desktop() { + MAX=30 + CT=0 + while [ ! -f /tmp/.started-desktop ]; do + sleep 1 + CT=$(( CT + 1 )) + if [ "$CT" -ge "$MAX" ]; then + echo "FATAL: $0: Gave up waiting for Desktop to start" + exit 11 + fi + done +} + # Fech NVIDIA GPU device (if one exists) get_nvidia_gpu_id() { if [ "${NVIDIA_VISIBLE_DEVICES:-}" == "all" ]; then @@ -67,3 +81,24 @@ get_nvidia_gpu_id() { fi echo ${gpu_select} } + +export_desktop_dbus_session() { + if [ ! -f /tmp/.dbus-desktop-session.env ]; then + echo "$(dbus-launch)" > /tmp/.dbus-desktop-session.env + fi + export $(cat /tmp/.dbus-desktop-session.env) +} + +# Wait for desktop dbus session to start +wait_for_desktop_dbus_session() { + MAX=10 + CT=0 + while [ ! -f /tmp/.dbus-desktop-session.env ]; do + sleep 1 + CT=$(( CT + 1 )) + if [ "$CT" -ge "$MAX" ]; then + echo "FATAL: $0: Gave up waiting for Desktop dbus-launch session to be created" + exit 11 + fi + done +} diff --git a/overlay/usr/bin/install_sunshine.sh b/overlay/usr/bin/install_sunshine.sh deleted file mode 100755 index 458dafd..0000000 --- a/overlay/usr/bin/install_sunshine.sh +++ /dev/null @@ -1,91 +0,0 @@ -#!/usr/bin/env bash - -echo "**** Installing/upgrading Sunshine via flatpak ****" - -# Install Sunshine -flatpak --user remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo -flatpak --user install --assumeyes --or-update flathub dev.lizardbyte.app.Sunshine -# Configure any required overrides -flatpak --user override --talk-name=org.freedesktop.Flatpak dev.lizardbyte.app.Sunshine - -# Configure Sunshine as the default browser -echo "Configure Sunshine..." -sunshine_autostart_desktop="$(cat < "${USER_HOME:?}/.config/autostart/Sunshine.desktop" -fi -# Generate default launchers template: -sunshine_apps_data="$(cat < "${USER_HOME:?}/.config/sunshine/apps.json" -fi -if [[ ! -f "${USER_HOME:?}/.config/sunshine/sunshine.conf " ]]; then - echo "${sunshine_config_data:?}" > "${USER_HOME:?}/.config/sunshine/sunshine.conf" -fi - -echo "DONE" diff --git a/overlay/usr/bin/start-desktop.sh b/overlay/usr/bin/start-desktop.sh index a86505a..20d8caa 100755 --- a/overlay/usr/bin/start-desktop.sh +++ b/overlay/usr/bin/start-desktop.sh @@ -19,7 +19,12 @@ trap _term SIGTERM SIGINT # CONFIGURE: -export $(dbus-launch) +# Remove lockfile +rm -f /tmp/.started-desktop +# Start a session bus instance of dbus-daemon +# Note: This script should be the only one that waits for X after exporting this dbus session +rm -fv /tmp/.dbus-desktop-session.env +export_desktop_dbus_session # EXECUTE PROCESS: @@ -30,7 +35,6 @@ if [[ ! -f /tmp/.desktop-apps-updated.lock ]]; then xterm -geometry 200x50+0+0 -ls -e /bin/bash -c " source /usr/bin/install_firefox.sh; source /usr/bin/install_protonup.sh; - source /usr/bin/install_sunshine.sh; sleep 1; " touch /tmp/.desktop-apps-updated.lock @@ -40,6 +44,7 @@ fi echo "**** Starting Xfce4 ****" /usr/bin/startxfce4 & desktop_pid=$! +touch /tmp/.started-desktop # WAIT FOR CHILD PROCESS: wait "$desktop_pid" diff --git a/overlay/usr/bin/start-sunshine.sh b/overlay/usr/bin/start-sunshine.sh index c8fc19e..a26d94d 100755 --- a/overlay/usr/bin/start-sunshine.sh +++ b/overlay/usr/bin/start-sunshine.sh @@ -13,42 +13,58 @@ source /usr/bin/common-functions.sh # CATCH TERM SIGNAL: _term() { - kill -TERM "$sunshine_pid" 2>/dev/null + kill -INT "$sunshine_pid" 2>/dev/null + sleep 0.5 + counter=0 + while kill -0 "$sunshine_pid"; do + kill -TERM "$sunshine_pid" 2>/dev/null + counter=$((counter + 1)) + [ "$counter" -gt 2 ] && break + sleep 2 + done + counter=0 + while kill -0 "$sunshine_pid"; do + kill -KILL "$sunshine_pid" 2>/dev/null + counter=$((counter + 1)) + [ "$counter" -gt 2 ] && break + sleep 1 + done } trap _term SIGTERM SIGINT # CONFIGURE: # Install default configurations -mkdir -p /home/${USER}/sunshine -if [[ ! -f /home/${USER}/sunshine/sunshine.conf ]]; then - cp -vf /templates/sunshine/* /home/${USER}/sunshine/ - # TODO: Set the default encoder '# encoder = nvenc' - # nvidia_gpu_id=$(get_nvidia_gpu_id) - # if [[ "X${nvidia_gpu_id:-}" != "X" ]]; then - # if [[ "all video" == *"${NVIDIA_DRIVER_CAPABILITIES}"* ]]; then - # # Check if we have a nvidia GPU available - # sed -i 's|^# encoder.*=.*$|encoder = nvenc|' /home/${USER}/sunshine/sunshine.conf - # fi - # else - # # TODO: Enable the vaapi device if not using nvenc - # # vainfo --display drm --device /dev/dri/renderD128 2> /dev/null | grep -E "((VAProfileH264High|VAProfileHEVCMain|VAProfileHEVCMain10).*VAEntrypointEncSlice)" - # # Loop over any render devices - # echo - # fi +mkdir -p "${USER_HOME:?}/.config/sunshine" +if [ ! -f "${USER_HOME:?}/.config/sunshine/sunshine.conf" ]; then + cp -vf /templates/sunshine/sunshine.conf "${USER_HOME:?}/.config/sunshine/sunshine.conf" +fi +if [ ! -f "${USER_HOME:?}/.config/sunshine/apps.json" ]; then + cp -vf /templates/sunshine/apps.json "${USER_HOME:?}/.config/sunshine/apps.json" fi - # Reset the default username/password if ([ "X${SUNSHINE_USER:-}" != "X" ] && [ "X${SUNSHINE_PASS:-}" != "X" ]); then - sunshine /home/${USER}/sunshine/sunshine.conf --creds ${SUNSHINE_USER:-} ${SUNSHINE_PASS:-} + sunshine "${USER_HOME:?}/.config/sunshine/sunshine.conf" --creds "${SUNSHINE_USER:?}" "${SUNSHINE_PASS:?}" +fi +# Remove any auto-start scripts from user's .local dir +if [ -f "${USER_HOME:?}/.config/autostart/Sunshine.desktop" ]; then + rm -fv "${USER_HOME:?}/.config/autostart/Sunshine.desktop" fi # EXECUTE PROCESS: # Wait for the X server to start wait_for_x + +# Start a session bus instance of dbus-daemon +wait_for_desktop_dbus_session +export_desktop_dbus_session + +# Wait for the desktop to start +wait_for_desktop + # Start the sunshine server -sunshine /home/${USER}/sunshine/sunshine.conf & +/usr/bin/dumb-init /usr/bin/sunshine "${USER_HOME:?}/.config/sunshine/sunshine.conf" & sunshine_pid=$! diff --git a/overlay/usr/bin/start-udev.sh b/overlay/usr/bin/start-udev.sh index 16dae39..d79e766 100755 --- a/overlay/usr/bin/start-udev.sh +++ b/overlay/usr/bin/start-udev.sh @@ -17,9 +17,12 @@ _term() { trap _term SIGTERM SIGINT -# EXECUTE PROCESS: +# CONFIGURE: # Remove lockfile -rm -f /tmp/.udev-started +rm -f /tmp/.started-udev + + +# EXECUTE PROCESS: # Start udev if command -v udevd &>/dev/null; then unshare --net udevd --daemon &>/dev/null @@ -31,7 +34,7 @@ udevadm monitor & monitor_pid=$! # Touch lockfile sleep 1 -touch /tmp/.udev-started +touch /tmp/.started-udev # Wait for 10 seconds, then request device events from the kernel sleep 10 udevadm trigger diff --git a/overlay/usr/bin/sunshine-run b/overlay/usr/bin/sunshine-run index f27292f..df782ab 100755 --- a/overlay/usr/bin/sunshine-run +++ b/overlay/usr/bin/sunshine-run @@ -10,23 +10,40 @@ ### set -e -# CATCH TERM SIGNAL: +exec >> >(tee -a /tmp/sunshine-exec-run.log) 2>&1 +echo +echo "-------------------------------" +echo +date +echo +echo "-------------------------------" +echo +echo "**** Execute sunshine-run ****" +echo + +# Trap SIGINT, SIGQUIT, SIGHUP, SIGTERM to forward signals to the child process _term() { - pkill -P $$ + if [ -n "${proc_pid}" ]; then + echo " - Forwarding signal $1 to process ${proc_pid}" + kill -s "${1}" "${proc_pid}" + fi } -for sig in INT QUIT HUP TERM; do - trap " - _term - trap - $sig EXIT - kill -s $sig "'"$$"' "$sig" -done -trap _term EXIT +trap '_term INT' SIGINT +trap '_term QUIT' SIGQUIT +trap '_term HUP' SIGHUP +trap '_term TERM' SIGTERM - -# RUN CHILD PROCESS +# Run child process /usr/bin/dumb-init "${@}" & proc_pid=$! +echo " - Recording sunshine-run PID '${proc_pid}' in /tmp/sunshine-exec-run.pid" +echo "${proc_pid}" > /tmp/sunshine-exec-run.pid - -# WAIT FOR CHILD PROCESS: +# Wait for child process to exit: +echo " - Waiting for PID '${proc_pid}' to exit" wait "$proc_pid" + +# Clean up PID file +rm -f /tmp/sunshine-exec-run.pid + +echo "DONE" diff --git a/overlay/usr/bin/sunshine-stop b/overlay/usr/bin/sunshine-stop index 7f13d2a..6db2e2f 100755 --- a/overlay/usr/bin/sunshine-stop +++ b/overlay/usr/bin/sunshine-stop @@ -10,5 +10,48 @@ ### set -e -# Terminate any running sunshine-run processes: -kill $(ps aux | grep -v grep | grep sunshine-run | awk '{print $2}') +exec >> >(tee -a /tmp/sunshine-exec-stop.log) 2>&1 +echo +echo "-------------------------------" +echo +date +echo +echo "-------------------------------" +echo +echo "**** Execute sunshine-stop ****" +echo + + +process_pid="$(cat /tmp/sunshine-exec-run.pid)" +echo " - Found initial sunshine-run PID '${process_pid}'" + +echo " - Sending SIGINT to PID '${process_pid}'" +kill -INT "${process_pid}" + +echo " - Checking for other sunshine-run processes '${process_pid}'" +for process_pid in $(ps aux | grep -v grep | grep sunshine-run | awk '{print $2}'); do + echo " - Sending SIGINT to PID '${process_pid}'" + kill -INT "${process_pid}" 2>/dev/null + sleep 0.5 + counter=0 + while kill -0 "$process_pid" 2>/dev/null; do + echo " - Sending SIGTERM to PID '${process_pid}'" + kill -TERM "$process_pid" 2>/dev/null + counter=$((counter + 1)) + [ "$counter" -gt 2 ] && break + sleep 2 + done + counter=0 + while kill -0 "$process_pid" 2>/dev/null; do + echo " - Sending SIGKILL to PID '${process_pid}'" + kill -KILL "$process_pid" 2>/dev/null + counter=$((counter + 1)) + [ "$counter" -gt 2 ] && break + sleep 1 + done +done + +# Clean up PID file +rm -f /tmp/sunshine-exec-run.pid + +echo "DONE"