diff --git a/milky-way/lib/images.libsonnet b/milky-way/lib/images.libsonnet index 31e9d7d..8e36a5b 100644 --- a/milky-way/lib/images.libsonnet +++ b/milky-way/lib/images.libsonnet @@ -177,6 +177,17 @@ local images = { fullyQualifiedRepository: "docker.io/yuto7/andref-ipfs-depot", defaultDigest: { hash: std.trim(importstr "exports/whale/digests/andref-ipfs-depot.txt") }, }, + // Patched Shokofin (Jellyfin plugin) as a FILE-DELIVERY image: stable v6.0.5 + the backported + // `VFS_UseSourceFileAsVersionIdentifier` feature (see whale/outputs.nix). Not a service -- its + // /plugin dir is copied onto Jellyfin's config PVC by lib/jellyfin.libsonnet's init container. + // Digest from exports/whale/digests/jellyfin-shokofin-plugin.txt (written by + // `nix run ./flake-profiles/whale#jellyfin-shokofin-plugin-push`). This pin (and the declarative + // install it drives) is PERMANENT; when upstream ships the feature in a stable release, only the + // whale build's patch/overlay goes away -- it then packages the stock plugin, digest re-pinned here. + "jellyfin-shokofin-plugin": { + fullyQualifiedRepository: "docker.io/yuto7/jellyfin-shokofin-plugin", + defaultDigest: { hash: std.trim(importstr "exports/whale/digests/jellyfin-shokofin-plugin.txt") }, + }, // Kubo (go-ipfs), the reference IPFS implementation -- run here as a VPN-fronted pinned-mirror // node (lib/kubo.libsonnet). Multi-arch INDEX digest (k3s resolves the per-node arch; the index // includes linux/amd64 for methanol), same convention as the *arr/qbittorrent pins; tagHint is diff --git a/milky-way/lib/jellyfin.libsonnet b/milky-way/lib/jellyfin.libsonnet index 6456103..7a09353 100644 --- a/milky-way/lib/jellyfin.libsonnet +++ b/milky-way/lib/jellyfin.libsonnet @@ -17,6 +17,15 @@ local images = import 'milky-way/lib/images.libsonnet'; // strategy: Recreate (same contract as sonarr). It's sized larger than the *arr config PVCs // because that artwork/metadata cache grows with the library. The media volume is mounted // read-write so Jellyfin can manage media and (optionally) store metadata/trickplay alongside it. +// +// Shokofin plugin (declaratively installed): the anime library is served via the Shokofin plugin, +// which Jellyfin loads from a plugin dir on the config PVC. Rather than hand-install it in the UI, +// the `init-shokofin-plugin` init container materializes a PINNED, whale-built plugin dir onto the +// PVC, and autoUpdate is disabled in the baked meta.json so Jellyfin's updater can't replace it. +// This install mechanism (the param + init container) is PERMANENT -- it's how the plugin is managed. +// Only its CONTENTS are currently a backport: stock Shokofin 6.0.5 + one upstream feature +// (`VFS_UseSourceFileAsVersionIdentifier`) not yet in a stable release (see whale/outputs.nix). When +// upstream ships that feature, the whale image drops the patch and packages stock; this lib is unchanged. { new( tailscaleHostname, // required, unique tailnet-wide -> https://..ts.net @@ -24,6 +33,9 @@ local images = import 'milky-way/lib/images.libsonnet'; name='jellyfin', namespace='default', image=images.jellyfin.fullyQualifiedImageReferencePinned, + // Patched-Shokofin file-delivery image (see the header + whale/outputs.nix). Set to null to skip + // the plugin-install init container (e.g. a Jellyfin with no Shoko anime library). + shokofinPluginImage=images['jellyfin-shokofin-plugin'].fullyQualifiedImageReferencePinned, port=8096, // Jellyfin's HTTP WebUI/API port timezone='America/Los_Angeles', configStorageClassName='my-custom-zfs-generic-iscsi', // RWO; SQLite must not be on NFS @@ -32,6 +44,30 @@ local images = import 'milky-way/lib/images.libsonnet'; ):: { local this = self, + // Install the pinned patched Shokofin plugin dir onto the config PVC before Jellyfin starts. + // Idempotent + content-addressed: the marker encodes the patched Shokofin.dll's sha256, so a new + // build reinstalls but a plain restart is a no-op. Clears any other Shoko_* dir so Jellyfin loads + // exactly our version, and chowns to uid 1000 (the LSIO Jellyfin runtime uid) so it can read the + // assemblies it loads. The plugin files live at /plugin in the whale image (busybox gives sh/cp). + local shokofinPluginInstallScript = ||| + set -eu + plugins=/config/data/plugins + ver=$(sed -n 's/.*"version"[^"]*"\([^"]*\)".*/\1/p' /plugin/meta.json | head -1) + dest="$plugins/Shoko_$ver" + want=$(sha256sum /plugin/Shokofin.dll | cut -d' ' -f1) + marker="$dest/.sfvi-$want" + if [ ! -f "$marker" ]; then + echo "installing patched Shokofin $ver (Shokofin.dll $want)" + rm -rf "$plugins"/Shoko_* 2>/dev/null || true + mkdir -p "$dest" + cp -a /plugin/. "$dest/" + : > "$marker" + else + echo "patched Shokofin $ver already installed" + fi + chown -R 1000:1000 "$dest" + |||, + configPvc: { apiVersion: 'v1', kind: 'PersistentVolumeClaim', @@ -57,6 +93,24 @@ local images = import 'milky-way/lib/images.libsonnet'; tolerations: [ { key: 'ephemeral', operator: 'Exists', effect: 'NoSchedule' }, ], + // Seed the patched Shokofin plugin onto the config PVC (see the constructor header + + // shokofinPluginInstallScript). Reuses the whale plugin image purely as a file carrier; + // runs as root so it can write into /config and chown to uid 1000. Omitted entirely when + // shokofinPluginImage is null. + [if shokofinPluginImage != null then 'initContainers']: [ + { + name: 'init-shokofin-plugin', + image: shokofinPluginImage, + command: ['sh', '-c', shokofinPluginInstallScript], + volumeMounts: [ + { name: 'config', mountPath: '/config' }, + ], + resources: { + requests: { memory: '16Mi', cpu: '25m' }, + limits: { memory: '64Mi', cpu: '200m' }, + }, + }, + ], containers: [ { name: name,