diff options
| -rw-r--r-- | .ci/azure-pipelines.yml | 2 | ||||
| -rw-r--r-- | .copr/Makefile | 67 | ||||
| -rw-r--r-- | Emby.Server.Implementations/HttpServer/HttpListenerHost.cs | 5 | ||||
| -rw-r--r-- | MediaBrowser.Api/Playback/BaseStreamingService.cs | 8 | ||||
| -rw-r--r-- | MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs | 110 | ||||
| -rw-r--r-- | MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 3 | ||||
| -rw-r--r-- | deployment/centos-package-x64/Dockerfile | 11 | ||||
| -rwxr-xr-x | deployment/centos-package-x64/docker-build.sh | 71 | ||||
| -rw-r--r-- | deployment/fedora-package-x64/Dockerfile | 8 | ||||
| -rwxr-xr-x | deployment/fedora-package-x64/docker-build.sh | 69 | ||||
| -rw-r--r-- | deployment/fedora-package-x64/pkg-src/jellyfin.spec | 59 | ||||
| -rw-r--r-- | deployment/windows/build-jellyfin.ps1 | 22 | ||||
| -rw-r--r-- | deployment/windows/dialogs/setuptype.nsddef | 12 | ||||
| -rw-r--r-- | deployment/windows/dialogs/setuptype.nsdinc | 50 | ||||
| -rw-r--r-- | deployment/windows/jellyfin.nsi | 140 |
15 files changed, 400 insertions, 237 deletions
diff --git a/.ci/azure-pipelines.yml b/.ci/azure-pipelines.yml index 46c478b08..c829da98a 100644 --- a/.ci/azure-pipelines.yml +++ b/.ci/azure-pipelines.yml @@ -245,7 +245,7 @@ jobs: inputs: targetType: 'filePath' # Optional. Options: filePath, inline filePath: ./deployment/windows/build-jellyfin.ps1 # Required when targetType == FilePath - arguments: -InstallFFMPEG -InstallNSSM -MakeNSIS -UXLocation $(Agent.TempDirectory)\jellyfin-ux -InstallLocation $(build.artifactstagingdirectory) + arguments: -InstallFFMPEG -InstallNSSM -MakeNSIS -InstallTrayApp -UXLocation $(Agent.TempDirectory)\jellyfin-ux -InstallLocation $(build.artifactstagingdirectory) #script: '# Write your PowerShell commands here.Write-Host Hello World' # Required when targetType == Inline errorActionPreference: 'stop' # Optional. Options: stop, continue, silentlyContinue #failOnStderr: false # Optional diff --git a/.copr/Makefile b/.copr/Makefile index 84b98a011..ba330ada9 100644 --- a/.copr/Makefile +++ b/.copr/Makefile @@ -1,8 +1,59 @@ -srpm: - dnf -y install git - git submodule update --init --recursive - cd deployment/fedora-package-x64; \ - ./create_tarball.sh; \ - rpmbuild -bs pkg-src/jellyfin.spec \ - --define "_sourcedir $$PWD/pkg-src/" \ - --define "_srcrpmdir $(outdir)" +VERSION := $(shell sed -ne '/^Version:/s/.* *//p' \ + deployment/fedora-package-x64/pkg-src/jellyfin.spec) + +deployment/fedora-package-x64/pkg-src/jellyfin-web-$(VERSION).tar.gz: + curl -f -L -o deployment/fedora-package-x64/pkg-src/jellyfin-web-$(VERSION).tar.gz \ + https://github.com/jellyfin/jellyfin-web/archive/v$(VERSION).tar.gz \ + || curl -f -L -o deployment/fedora-package-x64/pkg-src/jellyfin-web-$(VERSION).tar.gz \ + https://github.com/jellyfin/jellyfin-web/archive/master.tar.gz \ + +srpm: deployment/fedora-package-x64/pkg-src/jellyfin-web-$(VERSION).tar.gz + cd deployment/fedora-package-x64; \ + SOURCE_DIR=../.. \ + WORKDIR="$${PWD}"; \ + package_temporary_dir="$${WORKDIR}/pkg-dist-tmp"; \ + pkg_src_dir="$${WORKDIR}/pkg-src"; \ + GNU_TAR=1; \ + tar \ + --transform "s,^\.,jellyfin-$(VERSION)," \ + --exclude='.git*' \ + --exclude='**/.git' \ + --exclude='**/.hg' \ + --exclude='**/.vs' \ + --exclude='**/.vscode' \ + --exclude='deployment' \ + --exclude='**/bin' \ + --exclude='**/obj' \ + --exclude='**/.nuget' \ + --exclude='*.deb' \ + --exclude='*.rpm' \ + -czf "pkg-src/jellyfin-$(VERSION).tar.gz" \ + -C $${SOURCE_DIR} ./ || GNU_TAR=0; \ + if [ $${GNU_TAR} -eq 0 ]; then \ + package_temporary_dir="$$(mktemp -d)"; \ + mkdir -p "$${package_temporary_dir}/jellyfin"; \ + tar \ + --exclude='.git*' \ + --exclude='**/.git' \ + --exclude='**/.hg' \ + --exclude='**/.vs' \ + --exclude='**/.vscode' \ + --exclude='deployment' \ + --exclude='**/bin' \ + --exclude='**/obj' \ + --exclude='**/.nuget' \ + --exclude='*.deb' \ + --exclude='*.rpm' \ + -czf "$${package_temporary_dir}/jellyfin/jellyfin-$(VERSION).tar.gz" \ + -C $${SOURCE_DIR} ./; \ + mkdir -p "$${package_temporary_dir}/jellyfin-$(VERSION)"; \ + tar -xzf "$${package_temporary_dir}/jellyfin/jellyfin-$(VERSION).tar.gz" \ + -C "$${package_temporary_dir}/jellyfin-$(VERSION); \ + rm -f "$${package_temporary_dir}/jellyfin/jellyfin-$(VERSION).tar.gz"; \ + tar -czf "$${SOURCE_DIR}/SOURCES/pkg-src/jellyfin-$(VERSION).tar.gz" \ + -C "$${package_temporary_dir}" "jellyfin-$(VERSION); \ + rm -rf $${package_temporary_dir}; \ + fi; \ + rpmbuild -bs pkg-src/jellyfin.spec \ + --define "_sourcedir $$PWD/pkg-src/" \ + --define "_srcrpmdir $(outdir)" diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index e4f98acb9..cd2a7dcf0 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -539,6 +539,11 @@ namespace Emby.Server.Implementations.HttpServer } finally { + if (httpRes.StatusCode >= 500) + { + _logger.LogDebug("Sending HTTP Response 500 in response to {Url}", urlToLog); + } + stopWatch.Stop(); var elapsed = stopWatch.Elapsed; if (elapsed.TotalMilliseconds > 500) diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index 8c4ccfa22..7bfe0e0ce 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -289,17 +289,22 @@ namespace MediaBrowser.Api.Playback throw; } + Logger.LogDebug("Launched ffmpeg process"); state.TranscodingJob = transcodingJob; // Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback _ = new JobLogger(Logger).StartStreamingLog(state, process.StandardError.BaseStream, logStream); // Wait for the file to exist before proceeeding - while (!File.Exists(state.WaitForPath ?? outputPath) && !transcodingJob.HasExited) + var ffmpegTargetFile = state.WaitForPath ?? outputPath; + Logger.LogDebug("Waiting for the creation of {0}", ffmpegTargetFile); + while (!File.Exists(ffmpegTargetFile) && !transcodingJob.HasExited) { await Task.Delay(100, cancellationTokenSource.Token).ConfigureAwait(false); } + Logger.LogDebug("File {0} created or transcoding has finished", ffmpegTargetFile); + if (state.IsInputVideo && transcodingJob.Type == TranscodingJobType.Progressive && !transcodingJob.HasExited) { await Task.Delay(1000, cancellationTokenSource.Token).ConfigureAwait(false); @@ -314,6 +319,7 @@ namespace MediaBrowser.Api.Playback { StartThrottler(state, transcodingJob); } + Logger.LogDebug("StartFfMpeg() finished successfully"); return transcodingJob; } diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs index f5f753684..9ecb5fe8c 100644 --- a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs @@ -192,6 +192,7 @@ namespace MediaBrowser.Api.Playback.Hls if (File.Exists(segmentPath)) { job = ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType); + Logger.LogDebug("returning {0} [it exists, try 1]", segmentPath); return await GetSegmentResult(state, playlistPath, segmentPath, segmentExtension, requestedIndex, job, cancellationToken).ConfigureAwait(false); } @@ -207,6 +208,7 @@ namespace MediaBrowser.Api.Playback.Hls job = ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType); transcodingLock.Release(); released = true; + Logger.LogDebug("returning {0} [it exists, try 2]", segmentPath); return await GetSegmentResult(state, playlistPath, segmentPath, segmentExtension, requestedIndex, job, cancellationToken).ConfigureAwait(false); } else @@ -243,6 +245,7 @@ namespace MediaBrowser.Api.Playback.Hls request.StartTimeTicks = GetStartPositionTicks(state, requestedIndex); + state.WaitForPath = segmentPath; job = await StartFfMpeg(state, playlistPath, cancellationTokenSource).ConfigureAwait(false); } catch @@ -277,7 +280,7 @@ namespace MediaBrowser.Api.Playback.Hls // await Task.Delay(50, cancellationToken).ConfigureAwait(false); //} - Logger.LogInformation("returning {0}", segmentPath); + Logger.LogDebug("returning {0} [general case]", segmentPath); job = job ?? ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType); return await GetSegmentResult(state, playlistPath, segmentPath, segmentExtension, requestedIndex, job, cancellationToken).ConfigureAwait(false); } @@ -458,56 +461,68 @@ namespace MediaBrowser.Api.Playback.Hls TranscodingJob transcodingJob, CancellationToken cancellationToken) { - var segmentFileExists = File.Exists(segmentPath); - - // If all transcoding has completed, just return immediately - if (transcodingJob != null && transcodingJob.HasExited && segmentFileExists) + var segmentExists = File.Exists(segmentPath); + if (segmentExists) { - return await GetSegmentResult(state, segmentPath, segmentIndex, transcodingJob).ConfigureAwait(false); - } + if (transcodingJob != null && transcodingJob.HasExited) + { + // Transcoding job is over, so assume all existing files are ready + Logger.LogDebug("serving up {0} as transcode is over", segmentPath); + return await GetSegmentResult(state, segmentPath, segmentIndex, transcodingJob).ConfigureAwait(false); + } - if (segmentFileExists) - { var currentTranscodingIndex = GetCurrentTranscodingIndex(playlistPath, segmentExtension); // If requested segment is less than transcoding position, we can't transcode backwards, so assume it's ready if (segmentIndex < currentTranscodingIndex) { + Logger.LogDebug("serving up {0} as transcode index {1} is past requested point {2}", segmentPath, currentTranscodingIndex, segmentIndex); return await GetSegmentResult(state, segmentPath, segmentIndex, transcodingJob).ConfigureAwait(false); } } - var segmentFilename = Path.GetFileName(segmentPath); - - while (!cancellationToken.IsCancellationRequested) + var nextSegmentPath = GetSegmentPath(state, playlistPath, segmentIndex + 1); + if (transcodingJob != null) { - try + while (!cancellationToken.IsCancellationRequested && !transcodingJob.HasExited) { - var text = File.ReadAllText(playlistPath, Encoding.UTF8); - - // If it appears in the playlist, it's done - if (text.IndexOf(segmentFilename, StringComparison.OrdinalIgnoreCase) != -1) + // To be considered ready, the segment file has to exist AND + // either the transcoding job should be done or next segment should also exist + if (segmentExists) { - if (!segmentFileExists) + if (transcodingJob.HasExited || File.Exists(nextSegmentPath)) { - segmentFileExists = File.Exists(segmentPath); + Logger.LogDebug("serving up {0} as it deemed ready", segmentPath); + return await GetSegmentResult(state, segmentPath, segmentIndex, transcodingJob).ConfigureAwait(false); } - if (segmentFileExists) + } + else + { + segmentExists = File.Exists(segmentPath); + if (segmentExists) { - return await GetSegmentResult(state, segmentPath, segmentIndex, transcodingJob).ConfigureAwait(false); + continue; // avoid unnecessary waiting if segment just became available } - //break; } + + await Task.Delay(100, cancellationToken).ConfigureAwait(false); + } + + if (!File.Exists(segmentPath)) + { + Logger.LogWarning("cannot serve {0} as transcoding quit before we got there", segmentPath); } - catch (IOException) + else { - // May get an error if the file is locked + Logger.LogDebug("serving {0} as it's on disk and transcoding stopped", segmentPath); } - - await Task.Delay(100, cancellationToken).ConfigureAwait(false); + cancellationToken.ThrowIfCancellationRequested(); + } + else + { + Logger.LogWarning("cannot serve {0} as it doesn't exist and no transcode is running", segmentPath); } - cancellationToken.ThrowIfCancellationRequested(); return await GetSegmentResult(state, segmentPath, segmentIndex, transcodingJob).ConfigureAwait(false); } @@ -521,6 +536,7 @@ namespace MediaBrowser.Api.Playback.Hls FileShare = FileShareMode.ReadWrite, OnComplete = () => { + Logger.LogDebug("finished serving {0}", segmentPath); if (transcodingJob != null) { transcodingJob.DownloadPositionTicks = Math.Max(transcodingJob.DownloadPositionTicks ?? segmentEndingPositionTicks, segmentEndingPositionTicks); @@ -909,9 +925,23 @@ namespace MediaBrowser.Api.Playback.Hls else { var keyFrameArg = string.Format( + CultureInfo.InvariantCulture, " -force_key_frames:0 \"expr:gte(t,{0}+n_forced*{1})\"", GetStartNumber(state) * state.SegmentLength, - state.SegmentLength.ToString(CultureInfo.InvariantCulture)); + state.SegmentLength); + if (state.TargetFramerate.HasValue) + { + // This is to make sure keyframe interval is limited to our segment, + // as forcing keyframes is not enough. + // Example: we encoded half of desired length, then codec detected + // scene cut and inserted a keyframe; next forced keyframe would + // be created outside of segment, which breaks seeking. + keyFrameArg += string.Format( + CultureInfo.InvariantCulture, + " -g {0} -keyint_min {0}", + (int)(state.SegmentLength * state.TargetFramerate) + ); + } var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; @@ -955,6 +985,15 @@ namespace MediaBrowser.Api.Playback.Hls var threads = EncodingHelper.GetNumberOfThreads(state, encodingOptions, videoCodec); + if (state.BaseRequest.BreakOnNonKeyFrames) + { + // FIXME: this is actually a workaround, as ideally it really should be the client which decides whether non-keyframe + // breakpoints are supported; but current implementation always uses "ffmpeg input seeking" which is liable + // to produce a missing part of video stream before first keyframe is encountered, which may lead to + // awkward cases like a few starting HLS segments having no video whatsoever, which breaks hls.js + Logger.LogInformation("Current HLS implementation doesn't support non-keyframe breaks but one is requested, ignoring that request"); + state.BaseRequest.BreakOnNonKeyFrames = false; + } var inputModifier = EncodingHelper.GetInputModifier(state, encodingOptions); // If isEncoding is true we're actually starting ffmpeg @@ -965,14 +1004,6 @@ namespace MediaBrowser.Api.Playback.Hls var outputTsArg = Path.Combine(Path.GetDirectoryName(outputPath), Path.GetFileNameWithoutExtension(outputPath)) + "%d" + GetSegmentFileExtension(state.Request); - var timeDeltaParam = string.Empty; - - if (isEncoding && state.TargetFramerate > 0) - { - float startTime = 1 / (state.TargetFramerate.Value * 2); - timeDeltaParam = string.Format(CultureInfo.InvariantCulture, "-segment_time_delta {0:F3}", startTime); - } - var segmentFormat = GetSegmentFileExtension(state.Request).TrimStart('.'); if (string.Equals(segmentFormat, "ts", StringComparison.OrdinalIgnoreCase)) { @@ -980,7 +1011,7 @@ namespace MediaBrowser.Api.Playback.Hls } return string.Format( - "{0} {1} -map_metadata -1 -map_chapters -1 -threads {2} {3} {4} {5} -f segment -max_delay 5000000 -avoid_negative_ts disabled -start_at_zero -segment_time {6} {10} -individual_header_trailer 0 -segment_format {11} -segment_list_type m3u8 -segment_start_number {7} -segment_list \"{8}\" -y \"{9}\"", + "{0} {1} -map_metadata -1 -map_chapters -1 -threads {2} {3} {4} {5} -f hls -max_delay 5000000 -avoid_negative_ts disabled -start_at_zero -hls_time {6} -individual_header_trailer 0 -hls_segment_type {7} -start_number {8} -hls_segment_filename \"{9}\" -hls_playlist_type vod -hls_list_size 0 -y \"{10}\"", inputModifier, EncodingHelper.GetInputArgument(state, encodingOptions), threads, @@ -988,11 +1019,10 @@ namespace MediaBrowser.Api.Playback.Hls GetVideoArguments(state, encodingOptions), GetAudioArguments(state, encodingOptions), state.SegmentLength.ToString(CultureInfo.InvariantCulture), + segmentFormat, startNumberParam, - outputPath, outputTsArg, - timeDeltaParam, - segmentFormat + outputPath ).Trim(); } } diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index d896a7aef..4dfb27130 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -2168,7 +2168,8 @@ namespace MediaBrowser.Controller.MediaEncoding // Important: If this is ever re-enabled, make sure not to use it with wtv because it breaks seeking if (!string.Equals(state.InputContainer, "wtv", StringComparison.OrdinalIgnoreCase) && state.TranscodingType != TranscodingJobType.Progressive - && state.EnableBreakOnNonKeyFrames(outputVideoCodec)) + && !state.EnableBreakOnNonKeyFrames(outputVideoCodec) + && (state.BaseRequest.StartTimeTicks ?? 0) > 0) { inputModifier += " -noaccurate_seek"; } diff --git a/deployment/centos-package-x64/Dockerfile b/deployment/centos-package-x64/Dockerfile index 855b0a479..04daef93c 100644 --- a/deployment/centos-package-x64/Dockerfile +++ b/deployment/centos-package-x64/Dockerfile @@ -3,7 +3,7 @@ FROM centos:7 ARG SOURCE_DIR=/jellyfin ARG PLATFORM_DIR=/jellyfin/deployment/centos-package-x64 ARG ARTIFACT_DIR=/dist -ARG SDK_VERSION=2.2 +ARG SDK_VERSION=3.0 # Docker run environment ENV SOURCE_DIR=/jellyfin ENV ARTIFACT_DIR=/dist @@ -13,13 +13,12 @@ RUN yum update -y \ && yum install -y epel-release # Install build dependencies -RUN yum install -y @buildsys-build rpmdevtools yum-plugins-core libcurl-devel fontconfig-devel freetype-devel openssl-devel glibc-devel libicu-devel wget git +RUN yum install -y @buildsys-build rpmdevtools yum-plugins-core libcurl-devel fontconfig-devel freetype-devel openssl-devel glibc-devel libicu-devel git # Install recent NodeJS and Yarn -RUN wget -O- https://raw.githubusercontent.com/creationix/nvm/v0.35.0/install.sh | /bin/bash \ - && source "$HOME/.nvm/nvm.sh" \ - && nvm install v8 \ - && npm install -g yarn +RUN curl -fSsLo /etc/yum.repos.d/yarn.repo https://dl.yarnpkg.com/rpm/yarn.repo \ + && rpm -i https://rpm.nodesource.com/pub_8.x/el/7/x86_64/nodesource-release-el7-1.noarch.rpm \ + && yum install -y yarn # Install DotNET SDK RUN rpm -Uvh https://packages.microsoft.com/config/rhel/7/packages-microsoft-prod.rpm \ diff --git a/deployment/centos-package-x64/docker-build.sh b/deployment/centos-package-x64/docker-build.sh index 18e10661c..62dd144e5 100755 --- a/deployment/centos-package-x64/docker-build.sh +++ b/deployment/centos-package-x64/docker-build.sh @@ -8,76 +8,9 @@ set -o xtrace # Move to source directory pushd ${SOURCE_DIR} -VERSION="$( grep '^Version:' ${SOURCE_DIR}/SOURCES/pkg-src/jellyfin.spec | awk '{ print $NF }' )" - -# Clone down and build Web frontend -web_build_dir="$( mktemp -d )" -web_target="${SOURCE_DIR}/MediaBrowser.WebDashboard/jellyfin-web" -git clone https://github.com/jellyfin/jellyfin-web.git ${web_build_dir}/ -pushd ${web_build_dir} -if [[ -n ${web_branch} ]]; then - checkout -b origin/${web_branch} -fi -source "$HOME/.nvm/nvm.sh" -nvm use v8 -yarn install -mkdir -p ${web_target} -mv dist/* ${web_target}/ -popd -rm -rf ${web_build_dir} - -# Create RPM source archive -GNU_TAR=1 -echo "Bundling all sources for RPM build." -tar \ ---transform "s,^\.,jellyfin-${VERSION}," \ ---exclude='.git*' \ ---exclude='**/.git' \ ---exclude='**/.hg' \ ---exclude='**/.vs' \ ---exclude='**/.vscode' \ ---exclude='deployment' \ ---exclude='**/bin' \ ---exclude='**/obj' \ ---exclude='**/.nuget' \ ---exclude='*.deb' \ ---exclude='*.rpm' \ --czf "${SOURCE_DIR}/SOURCES/pkg-src/jellyfin-${VERSION}.tar.gz" \ --C ${SOURCE_DIR} ./ || GNU_TAR=0 - -if [ $GNU_TAR -eq 0 ]; then - echo "The installed tar binary did not support --transform. Using workaround." - package_temporary_dir="$( mktemp -d )" - mkdir -p "${package_temporary_dir}/jellyfin" - # Not GNU tar - tar \ - --exclude='.git*' \ - --exclude='**/.git' \ - --exclude='**/.hg' \ - --exclude='**/.vs' \ - --exclude='**/.vscode' \ - --exclude='deployment' \ - --exclude='**/bin' \ - --exclude='**/obj' \ - --exclude='**/.nuget' \ - --exclude='*.deb' \ - --exclude='*.rpm' \ - -czf "${package_temporary_dir}/jellyfin/jellyfin-${VERSION}.tar.gz" \ - -C ${SOURCE_DIR} ./ - echo "Extracting filtered package." - mkdir -p "${package_temporary_dir}/jellyfin-${VERSION}" - tar -xzf "${package_temporary_dir}/jellyfin/jellyfin-${VERSION}.tar.gz" -C "${package_temporary_dir}/jellyfin-${VERSION}" - echo "Removing filtered package." - rm -f "${package_temporary_dir}/jellyfin/jellyfin-${VERSION}.tar.gz" - echo "Repackaging package into final tarball." - tar -czf "${SOURCE_DIR}/SOURCES/pkg-src/jellyfin-${VERSION}.tar.gz" -C "${package_temporary_dir}" "jellyfin-${VERSION}" - rm -rf ${package_temporary_dir} -fi - # Build RPM -spectool -g -R SPECS/jellyfin.spec -rpmbuild -bs SPECS/jellyfin.spec --define "_sourcedir ${SOURCE_DIR}/SOURCES/pkg-src/" -rpmbuild -bb SPECS/jellyfin.spec --define "_sourcedir ${SOURCE_DIR}/SOURCES/pkg-src/" +make -f .copr/Makefile srpm outdir=/root/rpmbuild/SRPMS +rpmbuild --rebuild -bb /root/rpmbuild/SRPMS/jellyfin-*.src.rpm # Move the artifacts out mkdir -p ${ARTIFACT_DIR}/rpm diff --git a/deployment/fedora-package-x64/Dockerfile b/deployment/fedora-package-x64/Dockerfile index b8226b173..769c62ab2 100644 --- a/deployment/fedora-package-x64/Dockerfile +++ b/deployment/fedora-package-x64/Dockerfile @@ -3,7 +3,7 @@ FROM fedora:29 ARG SOURCE_DIR=/jellyfin ARG PLATFORM_DIR=/jellyfin/deployment/fedora-package-x64 ARG ARTIFACT_DIR=/dist -ARG SDK_VERSION=2.2 +ARG SDK_VERSION=3.0 # Docker run environment ENV SOURCE_DIR=/jellyfin ENV ARTIFACT_DIR=/dist @@ -12,17 +12,13 @@ ENV ARTIFACT_DIR=/dist RUN dnf update -y # Install build dependencies -RUN dnf install -y @buildsys-build rpmdevtools dnf-plugins-core libcurl-devel fontconfig-devel freetype-devel openssl-devel glibc-devel libicu-devel nodejs wget git +RUN dnf install -y @buildsys-build rpmdevtools dnf-plugins-core libcurl-devel fontconfig-devel freetype-devel openssl-devel glibc-devel libicu-devel nodejs-yarn # Install DotNET SDK RUN dnf copr enable -y @dotnet-sig/dotnet \ && rpmdev-setuptree \ && dnf install -y dotnet-sdk-${SDK_VERSION} dotnet-runtime-${SDK_VERSION} -# Install yarn package manager -RUN wget -q -O /etc/yum.repos.d/yarn.repo https://dl.yarnpkg.com/rpm/yarn.repo \ - && dnf install -y yarn - # Create symlinks and directories RUN ln -sf ${PLATFORM_DIR}/docker-build.sh /docker-build.sh \ && mkdir -p ${SOURCE_DIR}/SPECS \ diff --git a/deployment/fedora-package-x64/docker-build.sh b/deployment/fedora-package-x64/docker-build.sh index 014f582f0..740e8d35c 100755 --- a/deployment/fedora-package-x64/docker-build.sh +++ b/deployment/fedora-package-x64/docker-build.sh @@ -8,74 +8,9 @@ set -o xtrace # Move to source directory pushd ${SOURCE_DIR} -VERSION="$( grep '^Version:' ${SOURCE_DIR}/SOURCES/pkg-src/jellyfin.spec | awk '{ print $NF }' )" - -# Clone down and build Web frontend -web_build_dir="$( mktemp -d )" -web_target="${SOURCE_DIR}/MediaBrowser.WebDashboard/jellyfin-web" -git clone https://github.com/jellyfin/jellyfin-web.git ${web_build_dir}/ -pushd ${web_build_dir} -if [[ -n ${web_branch} ]]; then - checkout -b origin/${web_branch} -fi -yarn install -mkdir -p ${web_target} -mv dist/* ${web_target}/ -popd -rm -rf ${web_build_dir} - -# Create RPM source archive -GNU_TAR=1 -echo "Bundling all sources for RPM build." -tar \ ---transform "s,^\.,jellyfin-${VERSION}," \ ---exclude='.git*' \ ---exclude='**/.git' \ ---exclude='**/.hg' \ ---exclude='**/.vs' \ ---exclude='**/.vscode' \ ---exclude='deployment' \ ---exclude='**/bin' \ ---exclude='**/obj' \ ---exclude='**/.nuget' \ ---exclude='*.deb' \ ---exclude='*.rpm' \ --czf "${SOURCE_DIR}/SOURCES/pkg-src/jellyfin-${VERSION}.tar.gz" \ --C ${SOURCE_DIR} ./ || GNU_TAR=0 - -if [ $GNU_TAR -eq 0 ]; then - echo "The installed tar binary did not support --transform. Using workaround." - package_temporary_dir="$( mktemp -d )" - mkdir -p "${package_temporary_dir}/jellyfin" - # Not GNU tar - tar \ - --exclude='.git*' \ - --exclude='**/.git' \ - --exclude='**/.hg' \ - --exclude='**/.vs' \ - --exclude='**/.vscode' \ - --exclude='deployment' \ - --exclude='**/bin' \ - --exclude='**/obj' \ - --exclude='**/.nuget' \ - --exclude='*.deb' \ - --exclude='*.rpm' \ - -czf "${package_temporary_dir}/jellyfin/jellyfin-${VERSION}.tar.gz" \ - -C ${SOURCE_DIR} ./ - echo "Extracting filtered package." - mkdir -p "${package_temporary_dir}/jellyfin-${VERSION}" - tar -xzf "${package_temporary_dir}/jellyfin/jellyfin-${VERSION}.tar.gz" -C "${package_temporary_dir}/jellyfin-${VERSION}" - echo "Removing filtered package." - rm -f "${package_temporary_dir}/jellyfin/jellyfin-${VERSION}.tar.gz" - echo "Repackaging package into final tarball." - tar -czf "${SOURCE_DIR}/SOURCES/pkg-src/jellyfin-${VERSION}.tar.gz" -C "${package_temporary_dir}" "jellyfin-${VERSION}" - rm -rf ${package_temporary_dir} -fi - # Build RPM -spectool -g -R SPECS/jellyfin.spec -rpmbuild -bs SPECS/jellyfin.spec --define "_sourcedir ${SOURCE_DIR}/SOURCES/pkg-src/" -rpmbuild -bb SPECS/jellyfin.spec --define "_sourcedir ${SOURCE_DIR}/SOURCES/pkg-src/" +make -f .copr/Makefile srpm outdir=/root/rpmbuild/SRPMS +rpmbuild -rb /root/rpmbuild/SRPMS/jellyfin-*.src.rpm # Move the artifacts out mkdir -p ${ARTIFACT_DIR}/rpm diff --git a/deployment/fedora-package-x64/pkg-src/jellyfin.spec b/deployment/fedora-package-x64/pkg-src/jellyfin.spec index 0c6bf7180..7118fcf3f 100644 --- a/deployment/fedora-package-x64/pkg-src/jellyfin.spec +++ b/deployment/fedora-package-x64/pkg-src/jellyfin.spec @@ -12,28 +12,36 @@ Release: 1%{?dist} Summary: The Free Software Media Browser License: GPLv2 URL: https://jellyfin.media -Source0: %{name}-%{version}.tar.gz -Source1: jellyfin.service -Source2: jellyfin.env -Source3: jellyfin.sudoers -Source4: restart.sh -Source5: jellyfin.override.conf -Source6: jellyfin-firewalld.xml +# Jellyfin Server tarball created by `make -f .copr/Makefile srpm`, real URL ends with `v%{version}.tar.gz` +Source0: https://github.com/%{name}/%{name}/archive/%{name}-%{version}.tar.gz +# Jellyfin Webinterface downloaded by `make -f .copr/Makefile srpm`, real URL ends with `v%{version}.tar.gz` +Source1: https://github.com/%{name}/%{name}-web/archive/%{name}-web-%{version}.tar.gz +Source11: jellyfin.service +Source12: jellyfin.env +Source13: jellyfin.sudoers +Source14: restart.sh +Source15: jellyfin.override.conf +Source16: jellyfin-firewalld.xml %{?systemd_requires} BuildRequires: systemd Requires(pre): shadow-utils BuildRequires: libcurl-devel, fontconfig-devel, freetype-devel, openssl-devel, glibc-devel, libicu-devel +%if 0%{?fedora} +BuildRequires: nodejs-yarn +%else +# Requirements not packaged in main repos +# From https://rpm.nodesource.com/pub_8.x/el/7/x86_64/ +BuildRequires: nodejs >= 8 yarn +%endif Requires: libcurl, fontconfig, freetype, openssl, glibc libicu # Requirements not packaged in main repos -# COPR @dotnet-sig/dotnet -BuildRequires: dotnet-runtime-2.2, dotnet-sdk-2.2 +# COPR @dotnet-sig/dotnet or +# https://packages.microsoft.com/rhel/7/prod/ +BuildRequires: dotnet-runtime-3.0, dotnet-sdk-3.0 # RPMfusion free Requires: ffmpeg -# Fedora has openssl1.1 which is incompatible with dotnet -%{?fedora:Requires: compat-openssl10} - # Disable Automatic Dependency Processing AutoReqProv: no @@ -42,7 +50,18 @@ Jellyfin is a free software media system that puts you in control of managing an %prep -%autosetup -n %{name}-%{version} +%autosetup -n %{name}-%{version} -b 0 -b 1 +web_build_dir="$(mktemp -d)" +web_target="$PWD/MediaBrowser.WebDashboard/jellyfin-web" +pushd ../jellyfin-web-%{version} || pushd ../jellyfin-web-master +%if 0%{?fedora} +nodejs-yarn install +%else +yarn install +%endif +mkdir -p ${web_target} +mv dist/* ${web_target}/ +popd %build @@ -52,7 +71,7 @@ export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 dotnet publish --configuration Release --output='%{buildroot}%{_libdir}/jellyfin' --self-contained --runtime %{dotnet_runtime} \ "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none" Jellyfin.Server %{__install} -D -m 0644 LICENSE %{buildroot}%{_datadir}/licenses/%{name}/LICENSE -%{__install} -D -m 0644 %{SOURCE5} %{buildroot}%{_sysconfdir}/systemd/system/%{name}.service.d/override.conf +%{__install} -D -m 0644 %{SOURCE15} %{buildroot}%{_sysconfdir}/systemd/system/%{name}.service.d/override.conf %{__install} -D -m 0644 Jellyfin.Server/Resources/Configuration/logging.json %{buildroot}%{_sysconfdir}/%{name}/logging.json %{__mkdir} -p %{buildroot}%{_bindir} tee %{buildroot}%{_bindir}/jellyfin << EOF @@ -64,11 +83,11 @@ EOF %{__mkdir} -p %{buildroot}%{_var}/log/jellyfin %{__mkdir} -p %{buildroot}%{_var}/cache/jellyfin -%{__install} -D -m 0644 %{SOURCE1} %{buildroot}%{_unitdir}/%{name}.service -%{__install} -D -m 0644 %{SOURCE2} %{buildroot}%{_sysconfdir}/sysconfig/%{name} -%{__install} -D -m 0600 %{SOURCE3} %{buildroot}%{_sysconfdir}/sudoers.d/%{name}-sudoers -%{__install} -D -m 0755 %{SOURCE4} %{buildroot}%{_libexecdir}/%{name}/restart.sh -%{__install} -D -m 0644 %{SOURCE6} %{buildroot}%{_prefix}/lib/firewalld/services/%{name}.xml +%{__install} -D -m 0644 %{SOURCE11} %{buildroot}%{_unitdir}/%{name}.service +%{__install} -D -m 0644 %{SOURCE12} %{buildroot}%{_sysconfdir}/sysconfig/%{name} +%{__install} -D -m 0600 %{SOURCE13} %{buildroot}%{_sysconfdir}/sudoers.d/%{name}-sudoers +%{__install} -D -m 0755 %{SOURCE14} %{buildroot}%{_libexecdir}/%{name}/restart.sh +%{__install} -D -m 0644 %{SOURCE16} %{buildroot}%{_prefix}/lib/firewalld/services/%{name}.xml %files %{_libdir}/%{name}/jellyfin-web/* @@ -80,7 +99,7 @@ EOF %{_libdir}/%{name}/createdump # Needs 755 else only root can run it since binary build by dotnet is 722 %attr(755,root,root) %{_libdir}/%{name}/jellyfin -%{_libdir}/%{name}/sosdocsunix.txt +%{_libdir}/%{name}/SOS_README.md %{_unitdir}/%{name}.service %{_libexecdir}/%{name}/restart.sh %{_prefix}/lib/firewalld/services/%{name}.xml diff --git a/deployment/windows/build-jellyfin.ps1 b/deployment/windows/build-jellyfin.ps1 index 5e06b9c06..dde6eb8fc 100644 --- a/deployment/windows/build-jellyfin.ps1 +++ b/deployment/windows/build-jellyfin.ps1 @@ -8,6 +8,7 @@ param( [switch]$GenerateZip, [string]$InstallLocation = "./dist/jellyfin-win-nsis", [string]$UXLocation = "../jellyfin-ux", + [switch]$InstallTrayApp, [ValidateSet('Debug','Release')][string]$BuildType = 'Release', [ValidateSet('Quiet','Minimal', 'Normal')][string]$DotNetVerbosity = 'Minimal', [ValidateSet('win','win7', 'win8','win81','win10')][string]$WindowsVersion = 'win', @@ -132,6 +133,23 @@ function Cleanup-NSIS { Remove-Item "$tempdir/nsis/" -Recurse -Force -ErrorAction Continue | Write-Verbose Remove-Item "$tempdir/nsis.zip" -Force -ErrorAction Continue | Write-Verbose } + +function Install-TrayApp { + param( + [string]$ResolvedInstallLocation, + [string]$Architecture + ) + Write-Verbose "Checking Architecture" + if($Architecture -ne 'x64'){ + Write-Warning "No builds available for your selected architecture of $Architecture" + Write-Warning "The tray app will not be available." + }else{ + Write-Verbose "Downloading Tray App and copying to Jellyfin location" + [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 + Invoke-WebRequest -Uri https://github.com/jellyfin/jellyfin-windows-tray/releases/latest/download/JellyfinTray.exe -UseBasicParsing -OutFile "$installLocation/JellyfinTray.exe" | Write-Verbose + } +} + if(-not $SkipJellyfinBuild.IsPresent -and -not ($InstallNSIS -eq $true)){ Write-Verbose "Starting Build Process: Selected Environment is $WindowsVersion-$Architecture" Build-JellyFin @@ -144,6 +162,10 @@ if($InstallNSSM.IsPresent -or ($InstallNSSM -eq $true)){ Write-Verbose "Starting NSSM Install" Install-NSSM $ResolvedInstallLocation $Architecture } +if($InstallTrayApp.IsPresent -or ($InstallTrayApp -eq $true)){ + Write-Verbose "Downloading Windows Tray App" + Install-TrayApp $ResolvedInstallLocation $Architecture +} #Copy-Item .\deployment\windows\install-jellyfin.ps1 $ResolvedInstallLocation\install-jellyfin.ps1 #Copy-Item .\deployment\windows\install.bat $ResolvedInstallLocation\install.bat Copy-Item .\LICENSE $ResolvedInstallLocation\LICENSE diff --git a/deployment/windows/dialogs/setuptype.nsddef b/deployment/windows/dialogs/setuptype.nsddef new file mode 100644 index 000000000..b55ceeaaa --- /dev/null +++ b/deployment/windows/dialogs/setuptype.nsddef @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +This file was created by NSISDialogDesigner 1.4.4.0 +http://coolsoft.altervista.org/nsisdialogdesigner +Do not edit manually! +--> +<Dialog Name="setuptype" Title="Setup Type" Subtitle="Control how Jellyfin is installed."> + <Label Name="InstallasaServiceLabel" Location="12, 115" Size="426, 46" Text="Install Jellyfin as a service. This method is recommended for Advanced Users. Additional setup is required to access network shares." TabIndex="0" /> + <RadioButton Name="InstallasaService" Location="12, 88" Size="426, 24" Text="Install as a Service (Advanced Users)" TabIndex="1" /> + <Label Name="BasicInstallLabel" Location="12, 39" Size="426, 46" Text="The basic install will run Jellyfin in your current user account.$\nThis is recommended for new users and those with existing Jellyfin installs older than 10.4." TabIndex="2" /> + <RadioButton Name="BasicInstall" Location="12, 12" Size="426, 24" Text="Basic Install (Recommended)" Font="Microsoft Sans Serif, 8.25pt, style=Bold" Checked="True" TabIndex="3" /> +</Dialog>
\ No newline at end of file diff --git a/deployment/windows/dialogs/setuptype.nsdinc b/deployment/windows/dialogs/setuptype.nsdinc new file mode 100644 index 000000000..8746ad2cc --- /dev/null +++ b/deployment/windows/dialogs/setuptype.nsdinc @@ -0,0 +1,50 @@ +; ========================================================= +; This file was generated by NSISDialogDesigner 1.4.4.0 +; http://coolsoft.altervista.org/nsisdialogdesigner +; +; Do not edit it manually, use NSISDialogDesigner instead! +; ========================================================= + +; handle variables +Var hCtl_setuptype +Var hCtl_setuptype_InstallasaServiceLabel +Var hCtl_setuptype_InstallasaService +Var hCtl_setuptype_BasicInstallLabel +Var hCtl_setuptype_BasicInstall +Var hCtl_setuptype_Font1 + + +; dialog create function +Function fnc_setuptype_Create + + ; custom font definitions + CreateFont $hCtl_setuptype_Font1 "Microsoft Sans Serif" "8.25" "700" + + ; === setuptype (type: Dialog) === + nsDialogs::Create 1018 + Pop $hCtl_setuptype + ${If} $hCtl_setuptype == error + Abort + ${EndIf} + !insertmacro MUI_HEADER_TEXT "Setup Type" "Control how Jellyfin is installed." + + ; === InstallasaServiceLabel (type: Label) === + ${NSD_CreateLabel} 8u 71u 280u 28u "Install Jellyfin as a service. This method is recommended for Advanced Users. Additional setup is required to access network shares." + Pop $hCtl_setuptype_InstallasaServiceLabel + + ; === InstallasaService (type: RadioButton) === + ${NSD_CreateRadioButton} 8u 54u 280u 15u "Install as a Service (Advanced Users)" + Pop $hCtl_setuptype_InstallasaService + ${NSD_AddStyle} $hCtl_setuptype_InstallasaService ${WS_GROUP} + + ; === BasicInstallLabel (type: Label) === + ${NSD_CreateLabel} 8u 24u 280u 28u "The basic install will run Jellyfin in your current user account.$\nThis is recommended for new users and those with existing Jellyfin installs older than 10.4." + Pop $hCtl_setuptype_BasicInstallLabel + + ; === BasicInstall (type: RadioButton) === + ${NSD_CreateRadioButton} 8u 7u 280u 15u "Basic Install (Recommended)" + Pop $hCtl_setuptype_BasicInstall + SendMessage $hCtl_setuptype_BasicInstall ${WM_SETFONT} $hCtl_setuptype_Font1 0 + ${NSD_Check} $hCtl_setuptype_BasicInstall + +FunctionEnd diff --git a/deployment/windows/jellyfin.nsi b/deployment/windows/jellyfin.nsi index e33efde91..5666d30f0 100644 --- a/deployment/windows/jellyfin.nsi +++ b/deployment/windows/jellyfin.nsi @@ -16,11 +16,14 @@ ShowUninstDetails show ; Global variables that we'll use Var _JELLYFINVERSION_ Var _JELLYFINDATADIR_ + Var _SETUPTYPE_ Var _INSTALLSERVICE_ Var _SERVICESTART_ Var _SERVICEACCOUNTTYPE_ Var _EXISTINGINSTALLATION_ Var _EXISTINGSERVICE_ + Var _MAKESHORTCUTS_ + Var _FOLDEREXISTS_ ; !ifdef x64 !define ARCH "x64" @@ -86,7 +89,12 @@ ShowUninstDetails show !insertmacro MUI_PAGE_WELCOME ; License Page !insertmacro MUI_PAGE_LICENSE "$%InstallLocation%\LICENSE" ; picking up generic GPL + +; Setup Type Page + Page custom ShowSetupTypePage SetupTypePage_Config + ; Components Page + !define MUI_PAGE_CUSTOMFUNCTION_PRE HideComponentsPage !insertmacro MUI_PAGE_COMPONENTS !define MUI_PAGE_CUSTOMFUNCTION_PRE HideInstallDirectoryPage ; Controls when to hide / show !define MUI_DIRECTORYPAGE_TEXT_DESTINATION "Install folder" ; shows just above the folder selection dialog @@ -102,6 +110,7 @@ ShowUninstDetails show !insertmacro MUI_PAGE_DIRECTORY ; Custom Dialogs + !include "dialogs\setuptype.nsdinc" !include "dialogs\service-config.nsdinc" !include "dialogs\confirmation.nsdinc" @@ -155,7 +164,9 @@ Section "!Jellyfin Server (required)" InstallJellyfinServer SetOutPath "$INSTDIR" + File "/oname=icon.ico" "${UXPATH}\branding\NSIS\modern-install.ico" File /r $%InstallLocation%\* + ; Write the InstallFolder, DataFolder, Network Service info into the registry for later use WriteRegExpandStr HKLM "${REG_CONFIG_KEY}" "InstallFolder" "$INSTDIR" @@ -170,7 +181,7 @@ Section "!Jellyfin Server (required)" InstallJellyfinServer WriteRegExpandStr HKLM "${REG_UNINST_KEY}" "UninstallString" '"$INSTDIR\Uninstall.exe"' WriteRegStr HKLM "${REG_UNINST_KEY}" "DisplayIcon" '"$INSTDIR\Uninstall.exe",0' WriteRegStr HKLM "${REG_UNINST_KEY}" "Publisher" "The Jellyfin Project" - WriteRegStr HKLM "${REG_UNINST_KEY}" "URLInfoAbout" "https://jellyfin.media/" + WriteRegStr HKLM "${REG_UNINST_KEY}" "URLInfoAbout" "https://jellyfin.org/" WriteRegStr HKLM "${REG_UNINST_KEY}" "DisplayVersion" "$_JELLYFINVERSION_" WriteRegDWORD HKLM "${REG_UNINST_KEY}" "NoModify" 1 WriteRegDWORD HKLM "${REG_UNINST_KEY}" "NoRepair" 1 @@ -180,12 +191,12 @@ Section "!Jellyfin Server (required)" InstallJellyfinServer SectionEnd Section "Jellyfin Server Service" InstallService - +${If} $_INSTALLSERVICE_ == "Yes" ; Only run this if we're going to install the service! ExecWait '"$INSTDIR\nssm.exe" statuscode JellyfinServer' $0 DetailPrint "Jellyfin Server service statuscode, $0" ${If} $0 == 0 InstallRetry: - ExecWait '"$INSTDIR\nssm.exe" install JellyfinServer "$INSTDIR\jellyfin.exe" --datadir \"$_JELLYFINDATADIR_\"' $0 + ExecWait '"$INSTDIR\nssm.exe" install JellyfinServer "$INSTDIR\jellyfin.exe" --service --datadir \"$_JELLYFINDATADIR_\"' $0 ${If} $0 <> 0 !insertmacro ShowError "Could not install the Jellyfin Server service." InstallRetry ${EndIf} @@ -201,7 +212,7 @@ Section "Jellyfin Server Service" InstallService DetailPrint "Jellyfin Server Service setting (Application), $0" ConfigureAppParametersRetry: - ExecWait '"$INSTDIR\nssm.exe" set JellyfinServer AppParameters --datadir \"$_JELLYFINDATADIR_\"' $0 + ExecWait '"$INSTDIR\nssm.exe" set JellyfinServer AppParameters --service --datadir \"$_JELLYFINDATADIR_\"' $0 ${If} $0 <> 0 !insertmacro ShowError "Could not configure the Jellyfin Server service." ConfigureAppParametersRetry ${EndIf} @@ -241,6 +252,15 @@ Section "Jellyfin Server Service" InstallService DetailPrint "Jellyfin Server service account change, $0" ${EndIf} + Sleep 3000 + ConfigureDefaultAppExit: + ExecWait '"$INSTDIR\nssm.exe" set JellyfinServer AppExit Default Exit' $0 + ${If} $0 <> 0 + !insertmacro ShowError "Could not configure the Jellyfin Server service app exit action." ConfigureDefaultAppExit + ${EndIf} + DetailPrint "Jellyfin Server service exit action set, $0" +${EndIf} + SectionEnd Section "-start service" StartService @@ -255,6 +275,16 @@ ${AndIf} $_INSTALLSERVICE_ == "Yes" ${EndIf} SectionEnd +Section "Create Shortcuts" CreateWinShortcuts + ${If} $_MAKESHORTCUTS_ == "Yes" + CreateDirectory "$SMPROGRAMS\Jellyfin Server" + CreateShortCut "$SMPROGRAMS\Jellyfin Server\Jellyfin (View Console).lnk" "$INSTDIR\jellyfin.exe" "--datadir $\"$_JELLYFINDATADIR_$\"" "$INSTDIR\icon.ico" 0 SW_SHOWMAXIMIZED + CreateShortCut "$SMPROGRAMS\Jellyfin Server\Jellyfin Tray App.lnk" "$INSTDIR\jellyfintray.exe" "" "$INSTDIR\icon.ico" 0 + ;CreateShortCut "$DESKTOP\Jellyfin Server.lnk" "$INSTDIR\jellyfin.exe" "--datadir $\"$_JELLYFINDATADIR_$\"" "$INSTDIR\icon.ico" 0 SW_SHOWMINIMIZED + CreateShortCut "$DESKTOP\Jellyfin Server\Jellyfin Server.lnk" "$INSTDIR\jellyfintray.exe" "" "$INSTDIR\icon.ico" 0 + ${EndIf} +SectionEnd + ;-------------------------------- ;Descriptions @@ -275,6 +305,7 @@ Section "Uninstall" ReadRegStr $INSTDIR HKLM "${REG_CONFIG_KEY}" "InstallFolder" ; read the installation folder ReadRegStr $_JELLYFINDATADIR_ HKLM "${REG_CONFIG_KEY}" "DataFolder" ; read the data folder + ReadRegStr $_SERVICEACCOUNTTYPE_ HKLM "${REG_CONFIG_KEY}" "ServiceAccountType" ; read the account name DetailPrint "Jellyfin Install location: $INSTDIR" DetailPrint "Jellyfin Data folder: $_JELLYFINDATADIR_" @@ -307,13 +338,18 @@ Section "Uninstall" Sleep 3000 ; Give time for Windows to catchup - NoServiceUninstall: ; existing install was present but no service was detected + NoServiceUninstall: ; existing install was present but no service was detected. Remove shortcuts if account is set to none + ${If} $_SERVICEACCOUNTTYPE_ == "None" + RMDir /r "$SMPROGRAMS\Jellyfin Server" + Delete "$DESKTOP\Jellyfin Server.lnk" + DetailPrint "Removed old shortcuts..." + ${EndIf} Delete "$INSTDIR\*.*" RMDir /r /REBOOTOK "$INSTDIR\jellyfin-web" Delete "$INSTDIR\Uninstall.exe" RMDir /r /REBOOTOK "$INSTDIR" - + DeleteRegKey HKLM "Software\Jellyfin" DeleteRegKey HKLM "${REG_UNINST_KEY}" @@ -326,6 +362,7 @@ Function .onInit StrCpy $_SERVICEACCOUNTTYPE_ "NetworkService" StrCpy $_EXISTINGINSTALLATION_ "No" StrCpy $_EXISTINGSERVICE_ "No" + StrCpy $_MAKESHORTCUTS_ "No" SetShellVarContext current StrCpy $_JELLYFINDATADIR_ "$%ProgramData%\Jellyfin\Server" @@ -353,6 +390,16 @@ Function .onInit StrCpy $_EXISTINGINSTALLATION_ "Yes" ; Set our flag to be used later SectionSetText ${InstallJellyfinServer} "Upgrade Jellyfin Server (required)" ; Change install text to "Upgrade" + ; check if service was run using Network Service account + ClearErrors + ReadRegStr $_SERVICEACCOUNTTYPE_ HKLM "${REG_CONFIG_KEY}" "ServiceAccountType" ; in case of error _SERVICEACCOUNTTYPE_ will be NetworkService as default + + ClearErrors + ReadRegStr $_JELLYFINDATADIR_ HKLM "${REG_CONFIG_KEY}" "DataFolder" ; in case of error, the default holds + + ; Hide sections which will not be needed in case of previous install + ; SectionSetText ${InstallService} "" + ; check if there is a service called Jellyfin, there should be ; hack : nssm statuscode Jellyfin will return non zero return code in case it exists ExecWait '"$INSTDIR\nssm.exe" statuscode JellyfinServer' $0 @@ -363,18 +410,17 @@ Function .onInit StrCpy $_EXISTINGSERVICE_ "Yes" StrCpy $_INSTALLSERVICE_ "Yes" StrCpy $_SERVICESTART_ "Yes" + StrCpy $_MAKESHORTCUTS_ "No" + SectionSetText ${CreateWinShortcuts} "" - ; check if service was run using Network Service account - ClearErrors - ReadRegStr $_SERVICEACCOUNTTYPE_ HKLM "${REG_CONFIG_KEY}" "ServiceAccountType" ; in case of error _SERVICEACCOUNTTYPE_ will be NetworkService as default - - ClearErrors - ReadRegStr $_JELLYFINDATADIR_ HKLM "${REG_CONFIG_KEY}" "DataFolder" ; in case of error, the default holds - - ; Hide sections which will not be needed in case of previous install - ; SectionSetText ${InstallService} "" - + NoService: ; existing install was present but no service was detected + ${If} $_SERVICEACCOUNTTYPE_ == "None" + StrCpy $_SETUPTYPE_ "Basic" + StrCpy $_INSTALLSERVICE_ "No" + StrCpy $_SERVICESTART_ "No" + StrCpy $_MAKESHORTCUTS_ "Yes" + ${EndIf} ; Let the user know that we'll upgrade and provide an option to quit. MessageBox MB_OKCANCEL|MB_ICONINFORMATION "Existing installation of Jellyfin Server was detected, it'll be upgraded, settings will be retained. \ @@ -383,8 +429,7 @@ Function .onInit ProceedWithUpgrade: - NoExisitingInstall: -; by this time, the variables have been correctly set to reflect previous install details + NoExisitingInstall: ; by this time, the variables have been correctly set to reflect previous install details FunctionEnd @@ -413,6 +458,25 @@ Function HideConfirmationPage ${EndIf} FunctionEnd +Function HideSetupTypePage + ${If} $_EXISTINGINSTALLATION_ == "Yes" ; Existing installation detected, so don't ask for SetupType + Abort + ${EndIf} +FunctionEnd + +Function HideComponentsPage + ${If} $_SETUPTYPE_ == "Basic" ; Basic installation chosen, don't show components choice + Abort + ${EndIf} +FunctionEnd + +; Setup Type dialog show function +Function ShowSetupTypePage + Call HideSetupTypePage + Call fnc_setuptype_Create + nsDialogs::Show +FunctionEnd + ; Service Config dialog show function Function ShowServiceConfigPage Call HideServiceConfigPage @@ -431,6 +495,46 @@ FunctionEnd Var StartServiceAfterInstall Var UseNetworkServiceAccount Var UseLocalSystemAccount +Var BasicInstall + + +Function SetupTypePage_Config +${NSD_GetState} $hCtl_setuptype_BasicInstall $BasicInstall + IfFileExists "$LOCALAPPDATA\Jellyfin" folderfound foldernotfound ; if the folder exists, use this, otherwise, go with new default + folderfound: + StrCpy $_FOLDEREXISTS_ "Yes" + Goto InstallCheck + foldernotfound: + StrCpy $_FOLDEREXISTS_ "No" + Goto InstallCheck + +InstallCheck: +${If} $BasicInstall == 1 + StrCpy $_SETUPTYPE_ "Basic" + StrCpy $_INSTALLSERVICE_ "No" + StrCpy $_SERVICESTART_ "No" + StrCpy $_SERVICEACCOUNTTYPE_ "None" + StrCpy $_MAKESHORTCUTS_ "Yes" + ${If} $_FOLDEREXISTS_ == "Yes" + StrCpy $_JELLYFINDATADIR_ "$LOCALAPPDATA\Jellyfin\" + ${EndIf} +${Else} + StrCpy $_SETUPTYPE_ "Advanced" + StrCpy $_INSTALLSERVICE_ "Yes" + StrCpy $_MAKESHORTCUTS_ "No" + ${If} $_FOLDEREXISTS_ == "Yes" + MessageBox MB_OKCANCEL|MB_ICONINFORMATION "An existing data folder was detected.\ + $\r$\nBasic Setup is highly recommended.\ + $\r$\nIf you proceed, you will need to set up Jellyfin again." IDOK GoAhead IDCANCEL GoBack + GoBack: + Abort + ${EndIf} + GoAhead: + StrCpy $_JELLYFINDATADIR_ "$%ProgramData%\Jellyfin\Server" + SectionSetText ${CreateWinShortcuts} "" +${EndIf} + +FunctionEnd Function ServiceConfigPage_Config ${NSD_GetState} $hCtl_service_config_StartServiceAfterInstall $StartServiceAfterInstall |
