From a6921fffad2180cd237dbc4af11920a0d14de58f Mon Sep 17 00:00:00 2001
From: h <65380846+thatsysadmin@users.noreply.github.com>
Date: Tue, 12 Apr 2022 20:29:08 -0700
Subject: [PATCH] Add initial support for RPM packaging (#121)

- Add gen-rpm
- Package rpm in CI testing and releases
- Remove fedora 33 from testing (end of life)
- Update arguments for `build_private.sh` and `build_sunshine.sh`

Co-authored-by: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com>
---
 .github/workflows/CI.yml        |   9 +-
 CMakeLists.txt                  |   1 +
 README.md                       |  45 +++++++-
 assets/85-sunshine-rules.rules  |   1 +
 gen-rpm.in                      | 179 ++++++++++++++++++++++++++++++++
 scripts/Dockerfile-debian       |   2 +-
 scripts/Dockerfile-fedora_33    |   3 +-
 scripts/Dockerfile-fedora_35    |   3 +-
 scripts/Dockerfile-ubuntu_18_04 |   2 +-
 scripts/Dockerfile-ubuntu_20_04 |   2 +-
 scripts/Dockerfile-ubuntu_21_04 |   2 +-
 scripts/Dockerfile-ubuntu_21_10 |   2 +-
 scripts/build-private.sh        |  15 ++-
 scripts/build-sunshine.sh       |  25 ++++-
 14 files changed, 271 insertions(+), 20 deletions(-)
 create mode 100644 assets/85-sunshine-rules.rules
 create mode 100755 gen-rpm.in

diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml
index 76d38cd0..397ee3d8 100644
--- a/.github/workflows/CI.yml
+++ b/.github/workflows/CI.yml
@@ -153,12 +153,9 @@ jobs:
         distro: [ debian, ubuntu_18_04, ubuntu_20_04, ubuntu_21_04, ubuntu_21_10 ]
         package: [ -p ]
         extension: [ deb ]
-        include:  # don't package these
-          - distro: fedora_33
-            package: ''
-            extension: rpm
+        include:  # package these differently
           - distro: fedora_35
-            package: ''
+            package: '-p'
             extension: rpm
 
     steps:
@@ -176,7 +173,7 @@ jobs:
       - name: Build Linux
         run: |
           cd scripts
-          sudo ./build-sunshine.sh ${{ matrix.package }} -u -n sunshine-${{ matrix.distro }} -s ..
+          sudo ./build-sunshine.sh ${{ matrix.package }} -e ${{ matrix.extension }} -u -n sunshine-${{ matrix.distro }} -s ..
       - name: Package Linux
         if: ${{ matrix.package != '' }}
         run: |
diff --git a/CMakeLists.txt b/CMakeLists.txt
index c3a1936e..862e4f1a 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -313,6 +313,7 @@ else()
 		set(SUNSHINE_EXECUTABLE_PATH "sunshine")
 	endif()
 	configure_file(gen-deb.in gen-deb @ONLY)
+	configure_file(gen-rpm.in gen-rpm @ONLY)
 	configure_file(sunshine.desktop.in sunshine.desktop @ONLY)
 	configure_file(sunshine.service.in sunshine.service @ONLY)
 endif()
diff --git a/README.md b/README.md
index d61b812c..f062769e 100644
--- a/README.md
+++ b/README.md
@@ -21,7 +21,7 @@ They make use of docker to handle building Sunshine automatically
 
 ### Requirements:
 
-Ubuntu 20.04:
+#### Ubuntu 20.04:
 Install the following:
 
 #### Common
@@ -52,15 +52,56 @@ On Ubuntu 20.04, the cuda compiler will fail since it's version is too old, it's
 sudo apt install nvidia-cuda-dev nvidia-cuda-toolkit
 ```
 
+#### Fedora 35:
+
+You will need some things in the RPMFusion repo, nost notably ffmpeg.
+```
+sudo dnf install https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm https://mirrors.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-$(rpm -E %fedora).noarch.rpm
+```
+#### Development tools and libraries
+```
+sudo dnf install \
+        boost-devel \
+        boost-static.x86_64 \
+        cmake \
+        ffmpeg-devel \
+        gcc-c++ \
+        libevdev-devel \
+        libxcb-devel \
+        libX11-devel \
+        libXcursor-devel \
+        libXfixes-devel \
+        libXinerama-devel \
+        libXi-devel \
+        libXrandr-devel \
+        libXtst-devel \
+        mesa-libGL-devel \
+        openssl-devel \
+        opus-devel \
+        pulseaudio-libs-devel
+```
+#### If you need to build an RPM binary package:
+```
+sudo dnf install rpmbuild
+```
+
 #### Warning:
 You might require ffmpeg version >= 4.3. Check the troubleshooting section for more information.
 
 ### Compilation:
-- `git clone https://github.com/loki-47-6F-64/sunshine.git --recurse-submodules`
+
+#### Ubuntu
+- `git clone https://github.com/SunshineStream/Sunshine.git --recurse-submodules`
 - `cd sunshine && mkdir build && cd build`
 - `cmake -DCMAKE_C_COMPILER=gcc-10 -DCMAKE_CXX_COMPILER=g++-10 ..`
 - `make -j ${nproc}`
 
+#### Fedora
+- `git clone https://github.com/SunshineStream/Sunshine.git --recurse-submodules`
+- `cd sunshine && mkdir build && cd build`
+- `cmake -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ ..`
+- `make -j ${nproc}`
+
 ### Setup:
 sunshine needs access to uinput to create mouse and gamepad events:
 
diff --git a/assets/85-sunshine-rules.rules b/assets/85-sunshine-rules.rules
new file mode 100644
index 00000000..46a78a4a
--- /dev/null
+++ b/assets/85-sunshine-rules.rules
@@ -0,0 +1 @@
+KERNEL=="uinput", GROUP="input", MODE="0660"
\ No newline at end of file
diff --git a/gen-rpm.in b/gen-rpm.in
new file mode 100755
index 00000000..e2bec8f4
--- /dev/null
+++ b/gen-rpm.in
@@ -0,0 +1,179 @@
+#!/bin/sh
+
+# Export filepaths
+export BUILDDIR=@CMAKE_CURRENT_SOURCE_DIR@/build
+export BUILDROOT=~/rpmbuild/
+export RPMSRC=~/rpmbuild/SOURCES
+export RPMSPEC=~/rpmbuild/SPECS
+export RPMBUILD=~/rpmbuild/BUILD
+
+# Check for Docker switch
+if [ "$1" == "-d" ]; then
+	export DOCKERSTATUS=TRUE
+else
+	export DOCKERSTATUS=FALSE
+fi
+
+# Check if user's rpmbuild folder is there, if so, temoprairly rename it.
+if [ -d ~/rpmbuild ]; then
+	echo "Backing up rpmbuild"
+	~/rpmbuild ~/rpmbuild.bkp
+	export RPMBUILDEXISTS=TRUE
+else
+	export RPMBUILDEXISTS=FALSE
+fi
+
+# Create rpmbuild folder structure
+mkdir ~/rpmbuild
+mkdir ~/rpmbuild/BUILD
+mkdir ~/rpmbuild/BUILDROOT
+mkdir ~/rpmbuild/RPMS
+mkdir ~/rpmbuild/SOURCES
+mkdir ~/rpmbuild/SPECS
+mkdir ~/rpmbuild/SRPMS
+
+# Create sunshine .spec file with preinstall and postinstall scripts
+cat << 'EOF' > $RPMSPEC/sunshine.spec
+Name:           sunshine
+Version:        @PROJECT_VERSION@
+Release:        1%{?dist}
+Summary:        An NVIDIA Gamestream-compatible hosting server
+BuildArch:      x86_64
+
+License:        GPLv3
+URL:            https://github.com/SunshineStream/Sunshine
+Source0:        sunshine-@PROJECT_VERSION@_bin.tar.gz
+
+Requires:       systemd
+
+%description
+An NVIDIA Gamestream-compatible hosting server
+
+%pre
+#!/bin/sh
+
+# Sunshine Pre-Install Script
+# Store backup for old config files to prevent it from being overwritten
+if [ -f /etc/sunshine/sunshine.conf ]; then
+        cp /etc/sunshine/sunshine.conf /etc/sunshine/sunshine.conf.old
+fi
+
+if [ -f /etc/sunshine/apps_linux.json ]; then
+        cp /etc/sunshine/apps_linux.json /etc/sunshine/apps_linux.json.old
+fi
+
+%post
+#!/bin/sh
+
+# Sunshine Post-Install Script
+export GROUP_INPUT=input
+
+if [ -f /etc/group ]; then
+        if ! grep -q $GROUP_INPUT /etc/group; then
+                echo "Creating group $GROUP_INPUT"
+
+                groupadd $GROUP_INPUT
+        fi
+else
+        echo "Warning: /etc/group not found"
+fi
+
+if [ -f /etc/sunshine/sunshine.conf.old ]; then
+	echo "Restoring old sunshine.conf"
+	mv /etc/sunshine/sunshine.conf.old /etc/sunshine/sunshine.conf
+fi
+
+if [ -f /etc/sunshine/apps_linux.json.old ]; then
+	echo "Restoring old apps_linux.json"
+	mv /etc/sunshine/apps_linux.json.old /etc/sunshine/apps_linux.json
+fi
+
+# Update permissions on config files for Web Manager
+if [ -f /etc/sunshine/apps_linux.json ]; then
+	echo "chmod 666 /etc/sunshine/apps_linux.json"
+	chmod 666 /etc/sunshine/apps_linux.json
+fi
+
+if [ -f /etc/sunshine/sunshine.conf ]; then
+	echo "chmod 666 /etc/sunshine/sunshine.conf"
+	chmod 666 /etc/sunshine/sunshine.conf
+fi
+
+# Ensure Sunshine can grab images from KMS
+path_to_setcap=$(which setcap)
+if [ -x "$path_to_setcap" ] ; then
+  echo "$path_to_setcap cap_sys_admin+p /usr/bin/sunshine"
+	$path_to_setcap cap_sys_admin+p /usr/bin/sunshine
+fi
+
+%prep
+%setup -q
+
+%install
+rm -rf $RPM_BUILD_ROOT
+mkdir -p $RPM_BUILD_ROOT/%{_bindir}
+mkdir -p $RPM_BUILD_ROOT/etc/sunshine
+mkdir -p $RPM_BUILD_ROOT/usr/lib/systemd/user
+mkdir -p $RPM_BUILD_ROOT/usr/share/applications
+mkdir -p $RPM_BUILD_ROOT/etc/udev/rules.d
+
+cp sunshine $RPM_BUILD_ROOT/%{_bindir}/sunshine
+cp sunshine.conf $RPM_BUILD_ROOT/etc/sunshine/sunshine.conf
+cp apps_linux.json $RPM_BUILD_ROOT/etc/sunshine/apps_linux.json
+cp sunshine.service $RPM_BUILD_ROOT/usr/lib/systemd/user/sunshine.service
+cp sunshine.desktop $RPM_BUILD_ROOT/usr/share/applications/sunshine.desktop
+cp 85-sunshine-rules.rules $RPM_BUILD_ROOT/etc/udev/rules.d/85-sunshine-rules.rules
+
+%clean
+rm -rf $RPM_BUILD_ROOT
+
+%files
+%{_bindir}/sunshine
+/usr/lib/systemd/user/sunshine.service
+/etc/sunshine/sunshine.conf
+/etc/sunshine/apps_linux.json
+/usr/share/applications/sunshine.desktop
+/etc/udev/rules.d/85-sunshine-rules.rules
+
+%changelog
+* Sat Mar 12 2022 h <65380846+thatsysadmin@users.noreply.github.com>
+- Initial packaging of Sunshine.
+EOF
+
+# Copy over sunshine binary and supplemental files into rpmbuild/BUILD/
+mkdir genrpm
+mkdir genrpm/sunshine-@PROJECT_VERSION@
+cp sunshine-@PROJECT_VERSION@ genrpm/sunshine-@PROJECT_VERSION@/sunshine
+cp sunshine.service genrpm/sunshine-@PROJECT_VERSION@/sunshine.service
+cp sunshine.desktop genrpm/sunshine-@PROJECT_VERSION@/sunshine.desktop
+cp @CMAKE_CURRENT_SOURCE_DIR@/assets/sunshine.conf genrpm/sunshine-@PROJECT_VERSION@/sunshine.conf
+cp @CMAKE_CURRENT_SOURCE_DIR@/assets/apps_linux.json genrpm/sunshine-@PROJECT_VERSION@/apps_linux.json
+cp @CMAKE_CURRENT_SOURCE_DIR@/assets/85-sunshine-rules.rules genrpm/sunshine-@PROJECT_VERSION@/85-sunshine-rules.rules
+cd genrpm
+
+# tarball everything as if it was a source file for rpmbuild
+tar --create --file sunshine-@PROJECT_VERSION@_bin.tar.gz sunshine-@PROJECT_VERSION@/
+cp sunshine-@PROJECT_VERSION@_bin.tar.gz ~/rpmbuild/SOURCES
+
+# Use rpmbuild to build the RPM package.
+rpmbuild -bb $RPMSPEC/sunshine.spec
+
+# Check if running in a CT
+if [ "$DOCKERSTATUS" == "FALSE" ]; then
+	# Move the completed RPM into the cmake build folder
+	mv ~/rpmbuild/RPMS/x86_64/sunshine-@PROJECT_VERSION@-1.fc*.x86_64.rpm @CMAKE_CURRENT_BINARY_DIR@/
+	echo "Moving completed RPM package into CMake build folder."
+elif [ "$DOCKERSTATUS" == "TRUE" ]; then
+	# Move into pickup location
+	mkdir -p /root/sunshine-build/package-rpm/
+	mv ~/rpmbuild/RPMS/x86_64/sunshine-@PROJECT_VERSION@-1.fc*.x86_64.rpm /root/sunshine-build/package-rpm/sunshine.rpm
+	echo "Moving completed RPM package for pickup."
+fi
+
+# Clean up; delete the rpmbuild folder we created and move back the original one
+if [ "$RPMBUILDEXISTS" == "TRUE" ]; then
+        echo "Removing and replacing original rpmbuild folder."
+        rm -rf ~/rpmbuild
+        mv ~/rpmbuild.bkp ~/rpmbuild
+fi
+exit 0
diff --git a/scripts/Dockerfile-debian b/scripts/Dockerfile-debian
index 809b20d3..3a53fd9f 100644
--- a/scripts/Dockerfile-debian
+++ b/scripts/Dockerfile-debian
@@ -35,4 +35,4 @@ RUN apt-get update -y && \
 
 # Entrypoint
 COPY build-private.sh /root/build.sh
-ENTRYPOINT ["/root/build.sh"]
+ENTRYPOINT ["/root/build.sh", "-deb"]
diff --git a/scripts/Dockerfile-fedora_33 b/scripts/Dockerfile-fedora_33
index a2c8dfa4..50a4e3db 100644
--- a/scripts/Dockerfile-fedora_33
+++ b/scripts/Dockerfile-fedora_33
@@ -18,9 +18,10 @@ RUN dnf -y update && \
         openssl-devel \
         opus-devel \
         pulseaudio-libs-devel \
+        rpm-build \
     && dnf clean all \
     && rm -rf /var/cache/yum
 
 # Entrypoint
 COPY build-private.sh /root/build.sh
-ENTRYPOINT ["/root/build.sh"]
+ENTRYPOINT ["/root/build.sh", "-rpm"]
diff --git a/scripts/Dockerfile-fedora_35 b/scripts/Dockerfile-fedora_35
index ba7abac5..d4f5d843 100644
--- a/scripts/Dockerfile-fedora_35
+++ b/scripts/Dockerfile-fedora_35
@@ -23,9 +23,10 @@ RUN dnf -y update && \
         openssl-devel \
         opus-devel \
         pulseaudio-libs-devel \
+        rpm-build \
     && dnf clean all \
     && rm -rf /var/cache/yum
 
 # Entrypoint
 COPY build-private.sh /root/build.sh
-ENTRYPOINT ["/root/build.sh"]
+ENTRYPOINT ["/root/build.sh", "-rpm"]
diff --git a/scripts/Dockerfile-ubuntu_18_04 b/scripts/Dockerfile-ubuntu_18_04
index b6a05f6f..7225c0c7 100644
--- a/scripts/Dockerfile-ubuntu_18_04
+++ b/scripts/Dockerfile-ubuntu_18_04
@@ -58,4 +58,4 @@ RUN cmake --version
 
 # Entrypoint
 COPY build-private.sh /root/build.sh
-ENTRYPOINT ["/root/build.sh"]
+ENTRYPOINT ["/root/build.sh", "-deb"]
diff --git a/scripts/Dockerfile-ubuntu_20_04 b/scripts/Dockerfile-ubuntu_20_04
index e65f9176..9ae7dfa9 100644
--- a/scripts/Dockerfile-ubuntu_20_04
+++ b/scripts/Dockerfile-ubuntu_20_04
@@ -41,4 +41,4 @@ RUN /root/cuda.run --silent --toolkit --toolkitpath=/usr --no-opengl-libs --no-m
 
 # Entrypoint
 COPY build-private.sh /root/build.sh
-ENTRYPOINT ["/root/build.sh"]
+ENTRYPOINT ["/root/build.sh", "-deb"]
diff --git a/scripts/Dockerfile-ubuntu_21_04 b/scripts/Dockerfile-ubuntu_21_04
index c7c7a3d4..00801c55 100644
--- a/scripts/Dockerfile-ubuntu_21_04
+++ b/scripts/Dockerfile-ubuntu_21_04
@@ -34,4 +34,4 @@ RUN apt-get update -y && \
 
 # Entrypoint
 COPY build-private.sh /root/build.sh
-ENTRYPOINT ["/root/build.sh"]
+ENTRYPOINT ["/root/build.sh", "-deb"]
diff --git a/scripts/Dockerfile-ubuntu_21_10 b/scripts/Dockerfile-ubuntu_21_10
index 7b51da3d..83b3e7f8 100644
--- a/scripts/Dockerfile-ubuntu_21_10
+++ b/scripts/Dockerfile-ubuntu_21_10
@@ -34,4 +34,4 @@ RUN apt-get update -y && \
 
 # Entrypoint
 COPY build-private.sh /root/build.sh
-ENTRYPOINT ["/root/build.sh"]
+ENTRYPOINT ["/root/build.sh", "-deb"]
diff --git a/scripts/build-private.sh b/scripts/build-private.sh
index 69446de6..93d18748 100755
--- a/scripts/build-private.sh
+++ b/scripts/build-private.sh
@@ -32,4 +32,17 @@ cmake "-DCMAKE_BUILD_TYPE=$CMAKE_BUILD_TYPE" "-DSUNSHINE_EXECUTABLE_PATH=$SUNSHI
 
 make -j ${nproc}
 
-./gen-deb
+# Get preferred package format
+if [ "$1" == "-rpm" ]
+then
+  echo "Packaging in .rpm format."
+  ./gen-rpm -d
+elif [ "$1" == "-deb" ]
+then
+  echo "Packaging in .deb format."
+  ./gen-deb
+else
+  echo "Preferred packaging not specified."
+  echo "Use -deb or -rpm to specify preferred package format."
+  exit 1
+fi
diff --git a/scripts/build-sunshine.sh b/scripts/build-sunshine.sh
index df21df3c..5be82808 100755
--- a/scripts/build-sunshine.sh
+++ b/scripts/build-sunshine.sh
@@ -4,7 +4,8 @@ set -e
 usage() {
 	echo "Usage: $0"
 	echo "	-d: Generate a debug build"
-	echo "	-p: Generate a debian package"
+	echo "	-p: Generate a linux package"
+	echo "	-e: Extension of package... i.e. 'deb', 'rpm' --> default [deb]"
 	echo "	-u: The input device is not a TTY"
 	echo "	-n name: Docker container name --> default [sunshine]"
 	echo "	-s path/to/sources/sunshine: Use local sources instead of a git repository"
@@ -26,13 +27,14 @@ absolute_path() {
 
 CMAKE_BUILD_TYPE="-e CMAKE_BUILD_TYPE=Release"
 SUNSHINE_PACKAGE_BUILD=OFF
+SUNSHINE_PACKAGE_EXTENSION=deb
 SUNSHINE_GIT_URL=https://github.com/sunshinestream/sunshine.git
 CONTAINER_NAME=sunshine
 
 # Docker will fail if ctrl+c is passed through and the input is not a tty
 DOCKER_INTERACTIVE=-ti
 
-while getopts ":dpuhc:s:n:" arg; do
+while getopts ":dpuhc:e:s:n:" arg; do
 	case ${arg} in
 		u)
 			echo "Input device is not a TTY"
@@ -49,6 +51,21 @@ while getopts ":dpuhc:s:n:" arg; do
 			SUNSHINE_ASSETS_DIR="-e SUNSHINE_ASSETS_DIR=/etc/sunshine"
 			SUNSHINE_EXECUTABLE_PATH="-e SUNSHINE_EXECUTABLE_PATH=/usr/bin/sunshine"
 			;;
+		e)
+			echo "Defining package extension: $OPTARG"
+			if [ "$OPTARG" == "deb" ]
+			then
+				SUNSHINE_PACKAGE_EXTENSION=$OPTARG
+				echo "Package extension: deb"
+			elif [ "$OPTARG" == "rpm" ]
+			then
+				SUNSHINE_PACKAGE_EXTENSION=$OPTARG
+				echo "Package extension: rpm"
+			else
+				echo "Package extension not supported: $OPTARG"
+				echo "Falling back to default package extension: $SUNSHINE_PACKAGE_EXTENSION"
+			fi
+			;;
 		s)
 			absolute_path "$OPTARG"
 			OPTARG="$RETURN"
@@ -98,8 +115,8 @@ then
 	mkdir -p $BUILD_DIR
 	case $SUNSHINE_PACKAGE_BUILD in
 		ON)
-			echo "Downloading package to: $BUILD_DIR/$CONTAINER_NAME.deb"
-			docker cp $CONTAINER_NAME:/root/sunshine-build/package-deb/sunshine.deb "$BUILD_DIR/$CONTAINER_NAME.deb"
+			echo "Downloading package to: $BUILD_DIR/$CONTAINER_NAME.$SUNSHINE_PACKAGE_EXTENSION"
+			docker cp $CONTAINER_NAME:/root/sunshine-build/package-$SUNSHINE_PACKAGE_EXTENSION/sunshine.$SUNSHINE_PACKAGE_EXTENSION "$BUILD_DIR/$CONTAINER_NAME.$SUNSHINE_PACKAGE_EXTENSION"
 			;;
 		*)
 			echo "Downloading binary and assets to: $BUILD_DIR"