jellyfin: update jellyfin cli for 10.11.6

This commit is contained in:
ibizaman 2026-02-01 17:17:58 +01:00
parent 6d4a0eb437
commit abd3bf7ceb
3 changed files with 77 additions and 45 deletions

View file

@ -13,31 +13,38 @@ let
fqdn = "${cfg.subdomain}.${cfg.domain}";
jellyfin-cli = pkgs.buildDotnetModule rec {
pname = "jellyfin-cli";
version = "10.10.7";
jellyfin = pkgs.buildDotnetModule rec {
pname = "jellyfin";
version = "10.11.6";
src = pkgs.fetchFromGitHub {
owner = "ibizaman";
repo = "jellyfin";
rev = "0b1a5d929960f852dba90c1fc36f3a19dc094f8d";
hash = "sha256-H9V65+886EYMn/xDEgmxvoEOrbZaI1wSfmkN9vAzGhw=";
rev = "c58ca41d9ee76d137be788cd6f2d089e288ad561";
hash = "sha256-gTHsz5qRT+9FjAqBb4hDBkHChYDU52snBWu6cQb10i4=";
};
propagatedBuildInputs = [ pkgs.sqlite ];
projectFile = "Jellyfin.Cli/Jellyfin.Cli.csproj";
executables = [ "jellyfin-cli" ];
projectFile = "Jellyfin.Server/Jellyfin.Server.csproj";
executables = [ "jellyfin" ];
nugetDeps = "${pkgs.path}/pkgs/by-name/je/jellyfin/nuget-deps.json";
runtimeDeps = [
pkgs.jellyfin-ffmpeg
pkgs.fontconfig
pkgs.freetype
];
dotnet-sdk = pkgs.dotnetCorePackages.sdk_8_0;
dotnet-runtime = pkgs.dotnetCorePackages.aspnetcore_8_0;
dotnet-sdk = pkgs.dotnetCorePackages.sdk_9_0;
dotnet-runtime = pkgs.dotnetCorePackages.aspnetcore_9_0;
dotnetBuildFlags = [ "--no-self-contained" ];
makeWrapperArgs = [
"--append-flags"
"--ffmpeg=${pkgs.jellyfin-ffmpeg}/bin/ffmpeg"
"--append-flags"
"--webdir=${pkgs.jellyfin-web}/share/jellyfin-web"
];
passthru.tests = {
smoke-test = pkgs.nixosTests.jellyfin;
};
@ -53,7 +60,7 @@ let
purcell
jojosch
];
mainProgram = "jellyfin-cli";
mainProgram = "jellyfin";
platforms = dotnet-runtime.meta.platforms;
};
};
@ -169,9 +176,9 @@ in
description = "Pluging used for LDAP authentication.";
default = shb.mkJellyfinPlugin (rec {
pname = "jellyfin-plugin-ldapauth";
version = "20";
version = "22";
url = "https://github.com/jellyfin/${pname}/releases/download/v${version}/ldap-authentication_${version}.0.0.0.zip";
hash = "sha256-qATHNuiC6u+/vbY610jrFZSC16FG+Zpdjo+dfOVdVIk=";
hash = "sha256-m2oD9woEuoSRiV9OeifAxZN7XQULMKS0Yq4TF+LjjpI=";
});
};
@ -232,9 +239,9 @@ in
description = "Pluging used for SSO authentication.";
default = shb.mkJellyfinPlugin (rec {
pname = "jellyfin-plugin-sso";
version = "3.5.2.4";
version = "4.0.0.3";
url = "https://github.com/9p4/${pname}/releases/download/v${version}/sso-authentication_${version}.zip";
hash = "sha256-e+w5m6/7vRAynStDj34eBexfCIEgDJ09huHzi5gQEbo=";
hash = "sha256-Jkuc+Ua7934iSutf/zTY1phTxaltUkfiujOkCi7BW8w=";
});
};
@ -340,6 +347,7 @@ in
];
services.jellyfin.enable = true;
services.jellyfin.package = jellyfin;
networking.firewall = {
# from https://jellyfin.org/docs/general/networking/index.html, for auto-discovery
@ -730,6 +738,7 @@ in
let
# We must always wait for the service to be fully initialized,
# even if we're planning on changing the config and restarting.
# And the service is not initialized until this URL returns a 200 and not a 503.
waitForCurl = pkgs.writeShellApplication {
name = "waitForCurl";
runtimeInputs = [ pkgs.curl ];
@ -770,14 +779,14 @@ in
#
# If the file does not exist, write the config, create the file then restart.
# If the file exists, do nothing and remove the file, resetting the state for the next time.
restartedFile = "${config.services.jellyfin.dataDir}/.jellyfin-restarted";
restartedFile = "${config.services.jellyfin.dataDir}/shb-jellyfin-restarted";
writeConfig = pkgs.writeShellApplication {
name = "writeConfig";
runtimeInputs = [ pkgs.systemd ];
text = ''
if ! [ -f "${restartedFile}" ]; then
${lib.getExe jellyfin-cli} wizard \
${lib.getExe config.services.jellyfin.package} config \
--datadir='${config.services.jellyfin.dataDir}' \
--configdir='${config.services.jellyfin.configDir}' \
--cachedir='${config.services.jellyfin.cacheDir}' \
@ -799,7 +808,7 @@ in
rm "${restartedFile}"
else
echo "Restarting jellyfin.service"
touch "${restartedFile}"
echo "This file is used by SelfHostBlocks to know when to restart jellyfin" > "${restartedFile}"
systemctl reload-or-restart jellyfin.service
fi
'';

View file

@ -99,19 +99,23 @@ let
# otherwise curl will not be able to verify the "legitimacy of the server".
+ lib.strings.concatMapStrings (
u:
let
url = if builtins.isString u then u else u.url;
status = if builtins.isString u then 200 else u.status;
in
''
import time
done = False
count = 15
while not done and count > 0:
response = curl(client, """{"code":%{response_code}}""", "${u}")
response = curl(client, """{"code":%{response_code}}""", "${url}")
time.sleep(5)
count -= 1
if isinstance(response, dict):
done = response.get('code') == 200
done = response.get('code') == ${toString status}
if not done:
raise Exception(f"Response was never 200, got last: {response}")
raise Exception(f"Response was never ${toString status}, got last: {response}")
''
+ "\n"
) (waitForUrls args)

View file

@ -2,6 +2,9 @@
let
port = 9096;
adminUser = "jellyfin2";
adminPassword = "admin";
commonTestScript = shb.test.mkScripts {
hasSSL = { node, ... }: !(isNull node.config.shb.jellyfin.ssl);
waitForServices =
@ -19,24 +22,36 @@ let
{ proto_fqdn, ... }:
[
"${proto_fqdn}/System/Info/Public"
{
url = "${proto_fqdn}/Users/AuthenticateByName";
status = 401;
}
];
extraScript =
{ node, ... }:
''
server.wait_until_succeeds("journalctl --since -1m --unit jellyfin --grep 'Startup complete'")
headers = unline_with(" ", """
-H 'Content-Type: application/json'
-H 'Authorization: MediaBrowser Client="Android TV", Device="Nvidia Shield", DeviceId="ZQ9YQHHrUzk24vV", Version="0.15.3"'
""")
import time
with subtest("api login success"):
response = curl(client, """{"code":%{response_code}}""", "${node.config.test.proto_fqdn}/Users/AuthenticateByName",
data="""{"Username": "jellyfin", "Pw": "admin"}""",
extra=headers)
if response['code'] != 200:
ok = False
for i in range(1, 5):
response = curl(client, """{"code":%{response_code}}""", "${node.config.test.proto_fqdn}/Users/AuthenticateByName",
data="""{"Username": "${adminUser}", "Pw": "${adminPassword}"}""",
extra=headers)
if response['code'] == 200:
ok = True
break
time.sleep(5)
if not ok:
raise Exception(f"Expected success, got: {response['code']}")
with subtest("api login failure"):
response = curl(client, """{"code":%{response_code}}""", "${node.config.test.proto_fqdn}/Users/AuthenticateByName",
data="""{"Username": "jellyfin", "Pw": "badpassword"}""",
data="""{"Username": "${adminUser}", "Pw": "badpassword"}""",
extra=headers)
if response['code'] != 401:
raise Exception(f"Expected failure, got: {response['code']}")
@ -50,6 +65,9 @@ let
shb.test.baseModule
../../modules/services/jellyfin.nix
];
# Jellyfin checks for minimum 2Gib on startup.
virtualisation.diskSize = 4096;
virtualisation.memorySize = 4096;
test = {
subdomain = "j";
};
@ -59,7 +77,7 @@ let
inherit (config.test) subdomain domain;
inherit port;
admin = {
username = "jellyfin";
username = adminUser;
password.result = config.shb.hardcodedsecret.jellyfinAdminPassword.result;
};
debug = true;
@ -67,7 +85,7 @@ let
shb.hardcodedsecret.jellyfinAdminPassword = {
request = config.shb.jellyfin.admin.password.request;
settings.content = "admin";
settings.content = adminPassword;
};
environment.systemPackages = [
@ -95,18 +113,18 @@ let
loginButtonNameRegex = "Sign In";
testLoginWith = [
{
username = "jellyfin";
username = adminUser;
password = "badpassword";
nextPageExpect = [
"expect(page).to_have_title(re.compile('Jellyfin'))"
# "expect(page).to_have_title(re.compile('Jellyfin'))"
"expect(page.get_by_text(re.compile('[Ii]nvalid'))).to_be_visible(timeout=10000)"
];
}
{
username = "jellyfin";
password = "admin";
username = adminUser;
password = adminPassword;
nextPageExpect = [
"expect(page).to_have_title(re.compile('Jellyfin'))"
# "expect(page).to_have_title(re.compile('Jellyfin'))"
"expect(page.get_by_text(re.compile('[Ii]nvalid'))).not_to_be_visible(timeout=10000)"
"expect(page.get_by_label(re.compile('^[Uu]ser'))).not_to_be_visible(timeout=10000)"
"expect(page.get_by_label(re.compile('^[Pp]assword$'))).not_to_be_visible(timeout=10000)"
@ -181,18 +199,18 @@ let
loginButtonNameRegex = "Sign In";
testLoginWith = [
{
username = "jellyfin";
username = adminUser;
password = "badpassword";
nextPageExpect = [
"expect(page).to_have_title(re.compile('Jellyfin'))"
# "expect(page).to_have_title(re.compile('Jellyfin'))"
"expect(page.get_by_text(re.compile('[Ii]nvalid'))).to_be_visible(timeout=10000)"
];
}
{
username = "jellyfin";
password = "admin";
username = adminUser;
password = adminPassword;
nextPageExpect = [
"expect(page).to_have_title(re.compile('Jellyfin'))"
# "expect(page).to_have_title(re.compile('Jellyfin'))"
"expect(page.get_by_text(re.compile('[Ii]nvalid'))).not_to_be_visible(timeout=10000)"
"expect(page.get_by_label(re.compile('^[Uu]ser'))).not_to_be_visible(timeout=10000)"
"expect(page.get_by_label(re.compile('^[Pp]assword$'))).not_to_be_visible(timeout=10000)"
@ -202,7 +220,7 @@ let
username = "alice";
password = "AlicePassword";
nextPageExpect = [
"expect(page).to_have_title(re.compile('Jellyfin'))"
# "expect(page).to_have_title(re.compile('Jellyfin'))"
# For a reason I can't explain, redirection needs to happen manually.
"page.goto('${config.test.proto}://${config.test.fqdn}/web/')"
"expect(page.get_by_text(re.compile('[Ii]nvalid'))).not_to_be_visible(timeout=10000)"
@ -214,7 +232,7 @@ let
username = "alice";
password = "NotAlicePassword";
nextPageExpect = [
"expect(page).to_have_title(re.compile('Jellyfin'))"
# "expect(page).to_have_title(re.compile('Jellyfin'))"
"expect(page.get_by_text(re.compile('[Ii]nvalid'))).to_be_visible(timeout=10000)"
];
}
@ -222,7 +240,7 @@ let
username = "bob";
password = "BobPassword";
nextPageExpect = [
"expect(page).to_have_title(re.compile('Jellyfin'))"
# "expect(page).to_have_title(re.compile('Jellyfin'))"
# For a reason I can't explain, redirection needs to happen manually.
"page.goto('${config.test.proto}://${config.test.fqdn}/web/')"
"expect(page.get_by_text(re.compile('[Ii]nvalid'))).not_to_be_visible(timeout=10000)"
@ -234,7 +252,7 @@ let
username = "bob";
password = "NotBobPassword";
nextPageExpect = [
"expect(page).to_have_title(re.compile('Jellyfin'))"
# "expect(page).to_have_title(re.compile('Jellyfin'))"
"expect(page.get_by_text(re.compile('[Ii]nvalid'))).to_be_visible(timeout=10000)"
];
}
@ -300,7 +318,7 @@ let
"page.get_by_text(re.compile('[Aa]ccept')).click()"
# For a reason I can't explain, redirection needs to happen manually.
"page.goto('${config.test.proto}://${config.test.fqdn}/web/')"
"expect(page).to_have_title(re.compile('Jellyfin'))"
# "expect(page).to_have_title(re.compile('Jellyfin'))"
"expect(page.get_by_text(re.compile('[Ii]nvalid'))).not_to_be_visible(timeout=10000)"
"expect(page.get_by_label(re.compile('^[Uu]ser'))).not_to_be_visible(timeout=10000)"
"expect(page.get_by_label(re.compile('^[Pp]assword$'))).not_to_be_visible(timeout=10000)"
@ -313,7 +331,7 @@ let
# For a reason I can't explain, redirection needs to happen manually.
# So for failing auth, we check we're back on the login page.
"page.goto('${config.test.proto}://${config.test.fqdn}/web/')"
"expect(page).to_have_title(re.compile('Jellyfin'))"
# "expect(page).to_have_title(re.compile('Jellyfin'))"
"expect(page.get_by_label(re.compile('^[Uu]ser'))).to_be_visible(timeout=10000)"
"expect(page.get_by_label(re.compile('^[Pp]assword$'))).to_be_visible(timeout=10000)"
];
@ -325,7 +343,7 @@ let
"page.get_by_text(re.compile('[Aa]ccept')).click()"
# For a reason I can't explain, redirection needs to happen manually.
"page.goto('${config.test.proto}://${config.test.fqdn}/web/')"
"expect(page).to_have_title(re.compile('Jellyfin'))"
# "expect(page).to_have_title(re.compile('Jellyfin'))"
"expect(page.get_by_text(re.compile('[Ii]nvalid'))).not_to_be_visible(timeout=10000)"
"expect(page.get_by_label(re.compile('^[Uu]ser'))).not_to_be_visible(timeout=10000)"
"expect(page.get_by_label(re.compile('^[Pp]assword$'))).not_to_be_visible(timeout=10000)"
@ -337,7 +355,7 @@ let
nextPageExpect = [
# For a reason I can't explain, redirection needs to happen manually.
"page.goto('${config.test.proto}://${config.test.fqdn}/web/')"
"expect(page).to_have_title(re.compile('Jellyfin'))"
# "expect(page).to_have_title(re.compile('Jellyfin'))"
"expect(page.get_by_label(re.compile('^[Uu]ser'))).to_be_visible(timeout=10000)"
"expect(page.get_by_label(re.compile('^[Pp]assword$'))).to_be_visible(timeout=10000)"
];
@ -352,6 +370,7 @@ let
shb.test.runNixOSTest {
name = "jellyfin_${name}";
interactive.sshBackdoor.enable = true;
interactive.nodes.server = {
environment.systemPackages = [
pkgs.sqlite