From 01406dead2ea3d64477da2ec7a2d3694e8b30511 Mon Sep 17 00:00:00 2001 From: Yuto Nishida Date: Sat, 5 Sep 2026 17:49:03 -0700 Subject: [PATCH] [whale] Build patched Shokofin plugin image (source-file version identifier) Add a whale file-delivery image (jellyfin-shokofin-plugin) that packages the Shokofin Jellyfin plugin as stable v6.0.5 + one upstream feature commit backported: VFS_UseSourceFileAsVersionIdentifier (upstream 30eb9b55), which labels Jellyfin's multi-version episode picker by each version's source-file basename instead of the opaque [Shoko File=N] VFS name. The option is gated (default off), so the build is behaviorally identical to stock 6.0.5 until enabled. This is the repo's first buildDotnetModule: fetch v6.0.5, apply the patch, build only Shokofin.dll (net9.0), and overlay it onto the pinned upstream release zip -- so every shipped dependency DLL + meta.json comes from upstream unchanged (we never guess the dep set). autoUpdate is disabled in meta.json. NuGet deps are vendored in whale/shokofin-nuget-deps.json. Only the backport is temporary; the file-delivery image is the permanent, declarative way the plugin is installed (see milky-way/lib/jellyfin.libsonnet). Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_014jeXv3ZvJf3dsENLipLJiG --- .../digests/jellyfin-shokofin-plugin.txt | 1 + whale/outputs.nix | 82 ++++ ...kofin-source-file-version-identifier.patch | 97 +++++ whale/shokofin-nuget-deps.json | 357 ++++++++++++++++++ 4 files changed, 537 insertions(+) create mode 100644 exports/whale/digests/jellyfin-shokofin-plugin.txt create mode 100644 whale/patches/shokofin-source-file-version-identifier.patch create mode 100644 whale/shokofin-nuget-deps.json diff --git a/exports/whale/digests/jellyfin-shokofin-plugin.txt b/exports/whale/digests/jellyfin-shokofin-plugin.txt new file mode 100644 index 0000000..8b0d300 --- /dev/null +++ b/exports/whale/digests/jellyfin-shokofin-plugin.txt @@ -0,0 +1 @@ +sha256:0d824afc769abb185b87e74de5b9fde9d6e4427b41c92b3cc2c3304cc651ee63 \ No newline at end of file diff --git a/whale/outputs.nix b/whale/outputs.nix index 5cb7853..99f44cb 100644 --- a/whale/outputs.nix +++ b/whale/outputs.nix @@ -291,6 +291,85 @@ }; }; + # Patched Shokofin (Jellyfin plugin) -- stable v6.0.5 + ONE upstream feature commit backported: + # `VFS_UseSourceFileAsVersionIdentifier` (upstream 30eb9b55), which makes Jellyfin's multi-version + # episode picker label each version by its source file's basename instead of the opaque + # `[Shoko File=N]` VFS name. The option is gated (default off), so the build is behaviorally + # identical to stock 6.0.5 until enabled. See + # whale/patches/shokofin-source-file-version-identifier.patch and milky-way/lib/jellyfin.libsonnet. + # + # Only the BACKPORT is temporary. Once upstream ships this feature in a stable Shokofin, drop the + # patch + buildDotnetModule + whale/shokofin-nuget-deps.json + the DLL overlay and collapse this to + # a plain image that packages the stock release zip (fetchurl -> /plugin). The file-delivery image + # itself, the images.libsonnet pin, and the jellyfin init container that consumes it are PERMANENT + # -- they are how the plugin is installed declaratively, patched or not (never revert to a + # hand-installed UI plugin). + # + # This is the repo's first buildDotnetModule. We build ONLY Shokofin.dll (the single patched + # managed assembly) and OVERLAY it onto the pinned upstream release zip -- so every shipped + # dependency DLL + meta.json comes from upstream unchanged and we never have to guess the dep set. + # NuGet deps are vendored in whale/shokofin-nuget-deps.json (regenerate by pointing a scratch + # buildDotnetModule flake at v6.0.5 + this patch and running `nix run .#default.fetch-deps`). + shokofinSrc = imagePkgs.fetchFromGitHub { + owner = "ShokoAnime"; + repo = "Shokofin"; + rev = "v6.0.5"; + hash = "sha256-vAbhbMnnfnFkBIramuRccuSvDb+WJKGG6hOX9V5luNc="; + }; + shokofinPatchedBuild = imagePkgs.buildDotnetModule { + pname = "shokofin"; + version = "6.0.5-sfvi"; + src = shokofinSrc; + patches = [ ./patches/shokofin-source-file-version-identifier.patch ]; + # Collapse the multi-target (net9.0;net8) to a single net9.0 target: Jellyfin 10.11 is .NET 9, so + # net8 is dead weight, and a single lets `dotnet publish` run without an + # explicit -f (a plural , even with one value, forces multi-target publish). + postPatch = '' + substituteInPlace Shokofin/Shokofin.csproj \ + --replace-fail 'net9.0;net8' 'net9.0' + ''; + projectFile = "Shokofin/Shokofin.csproj"; + nugetDeps = ./shokofin-nuget-deps.json; + dotnet-sdk = imagePkgs.dotnetCorePackages.sdk_9_0; + dotnet-runtime = imagePkgs.dotnetCorePackages.runtime_9_0; + executables = [ ]; # a library plugin, no executables to wrap + doCheck = false; + }; + stockShokofinZip = imagePkgs.fetchurl { + url = "https://github.com/ShokoAnime/Shokofin/releases/download/v6.0.5/shoko_6.0.5.0_for_10.11.zip"; + hash = "sha256-Pm/uMugqSAOExmr4jQhpvPaL7ZqJescCPzeur7Rma+8="; + }; + # The installable plugin dir = the pinned upstream release zip with ONLY Shokofin.dll swapped for + # our patched build, and autoUpdate disabled in meta.json so Jellyfin's updater can't replace the + # patched DLL with a stock release. + shokofinPluginDir = imagePkgs.runCommand "shokofin-plugin-6.0.5-sfvi" { + nativeBuildInputs = [ imagePkgs.unzip imagePkgs.jq ]; + } '' + mkdir -p "$out" + unzip -q ${stockShokofinZip} -d "$out" + cp -f ${shokofinPatchedBuild}/lib/shokofin/Shokofin.dll "$out/Shokofin.dll" + jq '.autoUpdate = false' "$out/meta.json" > "$out/meta.json.tmp" + mv "$out/meta.json.tmp" "$out/meta.json" + ''; + # File-delivery image (the repo's first): bakes the plugin dir at /plugin plus busybox for the init + # container's sh/cp. There is no service Entrypoint -- milky-way/lib/jellyfin.libsonnet runs this as + # an init container that copies /plugin into Jellyfin's config PVC (cf. grand-central, which reuses + # its own image as an init container). + jellyfin-shokofin-plugin = image-nix-artifacts { + name = "jellyfin-shokofin-plugin"; + buildLayeredImageArg = { + tag = "latest"; + contents = [ imagePkgs.busybox ]; + extraCommands = '' + mkdir -p plugin + cp -r ${shokofinPluginDir}/. plugin/ + ''; + config = { + Cmd = [ "sh" ]; + }; + }; + }; + # `nix develop` target for a long-lived `skopeo login`. Uses the same skopeo (and # nixpkgs) as the push-scripts, so the auth.json written here is always compatible. mkAuthShell = pkgs: pkgs.mkShell { @@ -315,6 +394,8 @@ in { seadexarr-push = seadexarr.push-script.x86_64-linux; andref-ipfs-depot-image = andref-ipfs-depot.image.x86_64-linux; andref-ipfs-depot-push = andref-ipfs-depot.push-script.x86_64-linux; + jellyfin-shokofin-plugin-image = jellyfin-shokofin-plugin.image.x86_64-linux; + jellyfin-shokofin-plugin-push = jellyfin-shokofin-plugin.push-script.x86_64-linux; }; aarch64-darwin = { whale-push-example = example-artifacts.push-script.aarch64-darwin; @@ -323,6 +404,7 @@ in { autobrr-push = autobrr.push-script.aarch64-darwin; seadexarr-push = seadexarr.push-script.aarch64-darwin; andref-ipfs-depot-push = andref-ipfs-depot.push-script.aarch64-darwin; + jellyfin-shokofin-plugin-push = jellyfin-shokofin-plugin.push-script.aarch64-darwin; }; }; diff --git a/whale/patches/shokofin-source-file-version-identifier.patch b/whale/patches/shokofin-source-file-version-identifier.patch new file mode 100644 index 0000000..c1096d8 --- /dev/null +++ b/whale/patches/shokofin-source-file-version-identifier.patch @@ -0,0 +1,97 @@ +diff --git a/Shokofin/Configuration/PluginConfiguration.cs b/Shokofin/Configuration/PluginConfiguration.cs +index 06fb16e..7507c89 100644 +--- a/Shokofin/Configuration/PluginConfiguration.cs ++++ b/Shokofin/Configuration/PluginConfiguration.cs +@@ -593,6 +593,12 @@ public class PluginConfiguration : BasePluginConfiguration { + /// + public bool VFS_ResolveLinks { get; set; } + ++ /// ++ /// Use the file name of the source file as the version identifier in the ++ /// UI. ++ /// ++ public bool VFS_UseSourceFileAsVersionIdentifier { get; set; } = false; ++ + /// + /// Maximum number of exceptions before aborting the VFS generation. Set to + /// 0 to disable limit. +diff --git a/Shokofin/Pages/Scripts/Common.js b/Shokofin/Pages/Scripts/Common.js +index 013d8ea..f4bcf72 100644 +--- a/Shokofin/Pages/Scripts/Common.js ++++ b/Shokofin/Pages/Scripts/Common.js +@@ -539,6 +539,7 @@ export const LibraryMenu = globalThis.LibraryMenu; + * VFS_AddReleaseGroup: boolean; + * VFS_AddResolution: boolean; + * VFS_ResolveLinks: boolean; ++ * VFS_UseSourceFileAsVersionIdentifier: boolean; + * VFS_MaxTotalExceptionsBeforeAbort: number; + * VFS_MaxSeriesExceptionsBeforeAbort: number; + * VFS_UseSemaphore: boolean; +diff --git a/Shokofin/Pages/Scripts/Settings.js b/Shokofin/Pages/Scripts/Settings.js +index 76f2080..9846897 100644 +--- a/Shokofin/Pages/Scripts/Settings.js ++++ b/Shokofin/Pages/Scripts/Settings.js +@@ -669,6 +669,7 @@ function applyFormToConfig(form, config) { + config.AddCreditsAsSpecialFeatures = form.querySelector("#AddCreditsAsSpecialFeatures").checked; + config.VFS_AddReleaseGroup = form.querySelector("#VFS_AddReleaseGroup").checked; + config.VFS_AddResolution = form.querySelector("#VFS_AddResolution").checked; ++ config.VFS_UseSourceFileAsVersionIdentifier = form.querySelector("#VFS_UseSourceFileAsVersionIdentifier").checked; + + config.VFS_Threads = vfsTreads; + form.querySelector("#VFS_Threads").value = vfsTreads; +@@ -929,6 +930,7 @@ async function applyConfigToForm(form, config) { + form.querySelector("#AddCreditsAsSpecialFeatures").checked = config.AddCreditsAsSpecialFeatures; + form.querySelector("#VFS_AddReleaseGroup").checked = config.VFS_AddReleaseGroup; + form.querySelector("#VFS_AddResolution").checked = config.VFS_AddResolution; ++ form.querySelector("#VFS_UseSourceFileAsVersionIdentifier").checked = config.VFS_UseSourceFileAsVersionIdentifier; + + form.querySelector("#VFS_Threads").value = config.VFS_Threads; + form.querySelector("#VFS_ResolveLinks").checked = config.VFS_ResolveLinks; +diff --git a/Shokofin/Pages/Settings.html b/Shokofin/Pages/Settings.html +index 285cd4f..34be47a 100644 +--- a/Shokofin/Pages/Settings.html ++++ b/Shokofin/Pages/Settings.html +@@ -2081,6 +2081,13 @@ form:not(.advanced-mode) .advanced-only { + +
Add the full or short release group name to all automatically linked files in the VFS. "No Group" will be used for all manually linked files. Warning: The release group in the file name may change if the release group info is incomplete, unavailable, or otherwise updated in Shoko at a later date, and thus may cause episode/movie entries to be "removed" and "added" as new entries when that happens. Use at your own risk.
+ ++
++ ++
Use the source file name as the version identifier.
++
+
+