diff --git a/docs/redirects.json b/docs/redirects.json
index 73ea0dc..f863386 100644
--- a/docs/redirects.json
+++ b/docs/redirects.json
@@ -3512,12 +3512,21 @@
"services-jellyfin": [
"services-jellyfin.html#services-jellyfin"
],
+ "services-jellyfin-certs": [
+ "services-jellyfin.html#services-jellyfin-certs"
+ ],
"services-jellyfin-debug": [
"services-jellyfin.html#services-jellyfin-debug"
],
+ "services-jellyfin-declarative-ldap": [
+ "services-jellyfin.html#services-jellyfin-declarative-ldap"
+ ],
"services-jellyfin-features": [
"services-jellyfin.html#services-jellyfin-features"
],
+ "services-jellyfin-impermanence": [
+ "services-jellyfin.html#services-jellyfin-impermanence"
+ ],
"services-jellyfin-options": [
"services-jellyfin.html#services-jellyfin-options"
],
@@ -3632,12 +3641,18 @@
"services-jellyfin-options-shb.jellyfin.ldap.host": [
"services-jellyfin.html#services-jellyfin-options-shb.jellyfin.ldap.host"
],
+ "services-jellyfin-options-shb.jellyfin.ldap.plugin": [
+ "services-jellyfin.html#services-jellyfin-options-shb.jellyfin.ldap.plugin"
+ ],
"services-jellyfin-options-shb.jellyfin.ldap.port": [
"services-jellyfin.html#services-jellyfin-options-shb.jellyfin.ldap.port"
],
"services-jellyfin-options-shb.jellyfin.ldap.userGroup": [
"services-jellyfin.html#services-jellyfin-options-shb.jellyfin.ldap.userGroup"
],
+ "services-jellyfin-options-shb.jellyfin.plugins": [
+ "services-jellyfin.html#services-jellyfin-options-shb.jellyfin.plugins"
+ ],
"services-jellyfin-options-shb.jellyfin.port": [
"services-jellyfin.html#services-jellyfin-options-shb.jellyfin.port"
],
@@ -3659,9 +3674,6 @@
"services-jellyfin-options-shb.jellyfin.sso": [
"services-jellyfin.html#services-jellyfin-options-shb.jellyfin.sso"
],
- "services-jellyfin-options-shb.jellyfin.sso.adminUserGroup": [
- "services-jellyfin.html#services-jellyfin-options-shb.jellyfin.sso.adminUserGroup"
- ],
"services-jellyfin-options-shb.jellyfin.sso.authorization_policy": [
"services-jellyfin.html#services-jellyfin-options-shb.jellyfin.sso.authorization_policy"
],
@@ -3674,6 +3686,9 @@
"services-jellyfin-options-shb.jellyfin.sso.endpoint": [
"services-jellyfin.html#services-jellyfin-options-shb.jellyfin.sso.endpoint"
],
+ "services-jellyfin-options-shb.jellyfin.sso.plugin": [
+ "services-jellyfin.html#services-jellyfin-options-shb.jellyfin.sso.plugin"
+ ],
"services-jellyfin-options-shb.jellyfin.sso.provider": [
"services-jellyfin.html#services-jellyfin-options-shb.jellyfin.sso.provider"
],
@@ -3725,9 +3740,6 @@
"services-jellyfin-options-shb.jellyfin.sso.sharedSecretForAuthelia.result.path": [
"services-jellyfin.html#services-jellyfin-options-shb.jellyfin.sso.sharedSecretForAuthelia.result.path"
],
- "services-jellyfin-options-shb.jellyfin.sso.userGroup": [
- "services-jellyfin.html#services-jellyfin-options-shb.jellyfin.sso.userGroup"
- ],
"services-jellyfin-options-shb.jellyfin.subdomain": [
"services-jellyfin.html#services-jellyfin-options-shb.jellyfin.subdomain"
],
@@ -3737,18 +3749,6 @@
"services-jellyfin-usage-backup": [
"services-jellyfin.html#services-jellyfin-usage-backup"
],
- "services-jellyfin-usage-configuration": [
- "services-jellyfin.html#services-jellyfin-usage-configuration"
- ],
- "services-jellyfin-usage-https": [
- "services-jellyfin.html#services-jellyfin-usage-https"
- ],
- "services-jellyfin-usage-ldap": [
- "services-jellyfin.html#services-jellyfin-usage-ldap"
- ],
- "services-jellyfin-usage-sso": [
- "services-jellyfin.html#services-jellyfin-usage-sso"
- ],
"services-karakeep": [
"services-karakeep.html#services-karakeep"
],
diff --git a/modules/services/jellyfin.nix b/modules/services/jellyfin.nix
index 83114b2..28417e0 100644
--- a/modules/services/jellyfin.nix
+++ b/modules/services/jellyfin.nix
@@ -57,6 +57,34 @@ let
platforms = dotnet-runtime.meta.platforms;
};
};
+
+ pluginName = src: (builtins.fromJSON (builtins.readFile "${src}/meta.json")).name;
+
+ mkJellyfinPlugin =
+ {
+ pname,
+ version,
+ hash,
+ url,
+ }:
+ pkgs.callPackage (
+ { stdenv, fetchzip }:
+ stdenv.mkDerivation (finalAttrs: {
+ inherit pname version;
+
+ src = fetchzip {
+ inherit url hash;
+ stripRoot = false;
+ };
+
+ dontBuild = true;
+
+ installPhase = ''
+ mkdir $out
+ cp -r . $out
+ '';
+ })
+ ) { };
in
{
options.shb.jellyfin = {
@@ -119,6 +147,21 @@ in
);
};
+ plugins = lib.mkOption {
+ description = ''
+ Install plugins declaratively.
+
+ The LDAP and SSO plugins will be added if their respective
+ shb.jellyfin.ldap.enable and shb.jellyfin.sso.enable options are set to true.
+
+ The interface for plugin creation is WIP.
+ Feel free to add yours following the examples from the LDAP and SSO plugins
+ but know that they may require some tweaks later on.
+ '';
+ default = [ ];
+ type = types.listOf types.package;
+ };
+
ldap = lib.mkOption {
description = "LDAP configuration.";
default = { };
@@ -126,6 +169,17 @@ in
options = {
enable = lib.mkEnableOption "LDAP";
+ plugin = lib.mkOption {
+ type = lib.types.package;
+ description = "Pluging used for LDAP authentication.";
+ default = mkJellyfinPlugin (rec {
+ pname = "jellyfin-plugin-ldapauth";
+ version = "20";
+ url = "https://github.com/jellyfin/${pname}/releases/download/v${version}/ldap-authentication_${version}.0.0.0.zip";
+ hash = "sha256-qATHNuiC6u+/vbY610jrFZSC16FG+Zpdjo+dfOVdVIk=";
+ });
+ };
+
host = lib.mkOption {
type = types.str;
description = "Host serving the LDAP server.";
@@ -178,6 +232,17 @@ in
options = {
enable = lib.mkEnableOption "SSO";
+ plugin = lib.mkOption {
+ type = lib.types.package;
+ description = "Pluging used for SSO authentication.";
+ default = mkJellyfinPlugin (rec {
+ pname = "jellyfin-plugin-sso";
+ version = "3.5.2.4";
+ url = "https://github.com/9p4/${pname}/releases/download/v${version}/sso-authentication_${version}.zip";
+ hash = "sha256-e+w5m6/7vRAynStDj34eBexfCIEgDJ09huHzi5gQEbo=";
+ });
+ };
+
provider = lib.mkOption {
type = types.str;
description = "OIDC provider name";
@@ -196,18 +261,6 @@ in
default = "jellyfin";
};
- adminUserGroup = lib.mkOption {
- type = types.str;
- description = "OIDC admin group";
- default = "jellyfin_admin";
- };
-
- userGroup = lib.mkOption {
- type = types.str;
- description = "OIDC user group";
- default = "jellyfin_user";
- };
-
authorization_policy = lib.mkOption {
type = types.enum [
"one_factor"
@@ -270,6 +323,16 @@ in
[ "shb" "jellyfin" "adminPassword" ]
[ "shb" "jellyfin" "admin" "password" ]
)
+
+ # (lib.mkRenamedOptionModule
+ # [ "shb" "jellyfin" "sso" "userGroup" ]
+ # [ "shb" "jellyfin" "ldap" "userGroup" ]
+ # )
+
+ # (lib.mkRenamedOptionModule
+ # [ "shb" "jellyfin" "sso" "adminUserGroup" ]
+ # [ "shb" "jellyfin" "ldap" "adminGroup" ]
+ # )
];
config = lib.mkIf cfg.enable {
@@ -471,10 +534,10 @@ in
true
- ${cfg.sso.adminUserGroup}
+ ${cfg.ldap.adminGroup}
- ${cfg.sso.userGroup}
+ ${cfg.ldap.userGroup}
false
@@ -596,7 +659,7 @@ in
];
})
+ lib.strings.optionalString cfg.ldap.enable (
- shb.replaceSecretsScript {
+ (shb.replaceSecretsScript {
file = ldapConfig;
resultPath = "${config.services.jellyfin.dataDir}/plugins/configurations/LDAP-Auth.xml";
replacements = [
@@ -605,7 +668,7 @@ in
source = cfg.ldap.adminPassword.result.path;
}
];
- }
+ })
)
+ lib.strings.optionalString cfg.sso.enable (
shb.replaceSecretsScript {
@@ -626,8 +689,35 @@ in
replacements = [
];
}
+ )
+ + (
+ let
+ pluginInstallScript = p: ''
+ pluginDir="${config.services.jellyfin.dataDir}/plugins/${pluginName p}"
+ mkdir -p "$pluginDir"
+ for f in "${p}"/*; do
+ ln -sf "$f" "$pluginDir"
+ done
+
+ rm "$pluginDir/meta.json"
+ ${pkgs.jq}/bin/jq ". + {
+ status: \"Active\",
+ autoUpdate: false,
+ assemblies: []
+ }" "${p}/meta.json" > "$pluginDir/meta.json"
+ '';
+ in
+ lib.concatMapStringsSep "\n" pluginInstallScript cfg.plugins
);
+ shb.jellyfin.plugins =
+ lib.optionals cfg.ldap.enable [ cfg.ldap.plugin ]
+ ++ lib.optionals cfg.sso.enable [ cfg.sso.plugin ];
+
+ systemd.tmpfiles.rules = lib.optionals cfg.ldap.enable [
+ "d '${config.services.jellyfin.dataDir}/plugins' 0750 jellyfin jellyfin - -"
+ ];
+
systemd.services.jellyfin.serviceConfig.ExecStartPost =
let
# We must always wait for the service to be fully initialized,
@@ -718,7 +808,7 @@ in
systemd.services.jellyfin.serviceConfig.TimeoutStartSec = 300;
- shb.authelia.oidcClients = lib.lists.optionals (!(isNull cfg.sso)) [
+ shb.authelia.oidcClients = lib.optionals cfg.sso.enable [
{
client_id = cfg.sso.clientID;
client_name = "Jellyfin";
diff --git a/modules/services/jellyfin/docs/default.md b/modules/services/jellyfin/docs/default.md
index 9a13983..53b6e56 100644
--- a/modules/services/jellyfin/docs/default.md
+++ b/modules/services/jellyfin/docs/default.md
@@ -13,17 +13,21 @@ and LDAP and SSO integration.
- Declarative creation of admin user.
- Declarative selection of listening port.
-- Access through [subdomain](#services-jellyfin-options-shb.jellyfin.subdomain) using reverse proxy. [Manual](#services-jellyfin-usage-configuration).
-- Access through [HTTPS](#services-jellyfin-options-shb.jellyfin.ssl) using reverse proxy. [Manual](#services-jellyfin-usage-https).
-- Declarative [LDAP](#services-jellyfin-options-shb.jellyfin.ldap) configuration although plugin must be installed manually. [Manual](#services-jellyfin-usage-ldap).
-- Declarative [SSO](#services-jellyfin-options-shb.jellyfin.sso) configuration although plugin must be installed manually. [Manual](#services-jellyfin-usage-sso).
+- Access through [subdomain](#services-jellyfin-options-shb.jellyfin.subdomain)
+ and [HTTPS](#services-jellyfin-options-shb.jellyfin.ssl) using reverse proxy. [Manual](#services-jellyfin-usage).
+- Declarative plugin installation. [Manual](#services-jellyfin-options-shb.jellyfin.plugins).
+- Declarative [LDAP](#services-jellyfin-options-shb.jellyfin.ldap) configuration.
+- Declarative [SSO](#services-jellyfin-options-shb.jellyfin.sso) configuration.
- [Backup](#services-jellyfin-options-shb.jellyfin.backup) through the [backup block](./blocks-backup.html). [Manual](#services-jellyfin-usage-backup).
## Usage {#services-jellyfin-usage}
-### Initial Configuration {#services-jellyfin-usage-configuration}
+The following snippet assumes a few blocks have been setup already:
-The following snippet enables Jellyfin and makes it available under the `jellyfin.example.com` endpoint.
+- the [secrets block](usage.html#usage-secrets) with SOPS,
+- the [`shb.ssl` block](blocks-ssl.html#usage),
+- the [`shb.lldap` block](blocks-lldap.html#blocks-lldap-global-setup).
+- the [`shb.authelia` block](blocks-authelia.html#blocks-sso-global-setup).
```nix
shb.jellyfin = {
@@ -33,137 +37,59 @@ shb.jellyfin = {
admin = {
username = "admin";
- password.result = config.shb.sops.secret.jellyfinAdminPassword.result;
+ password.result = config.shb.sops.secret."jellyfin/adminPassword".result;
+ };
+
+ ldap = {
+ enable = true;
+ host = "127.0.0.1";
+ port = config.shb.lldap.ldapPort;
+ dcdomain = config.shb.lldap.dcdomain;
+ adminPassword.result = config.shb.sops.secrets."jellyfin/ldap/adminPassword".result
+ };
+
+ sso = {
+ enable = true;
+ endpoint = "https://${config.shb.authelia.subdomain}.${config.shb.authelia.domain}";
+
+ secretFile = config.shb.sops.secret."jellyfin/sso_secret".result;
+ secretFileForAuthelia = config.shb.sops.secret."jellyfin/authelia/sso_secret".result;
};
};
-shb.sops.secret.jellyfinAdminPassword.request = config.shb.jellyfin.admin.password.request;
-```
+shb.sops.secret."jellyfin/adminPassword".request = config.shb.jellyfin.admin.password.request;
-This assumes secrets are setup with SOPS
-as mentioned in [the secrets setup section](usage.html#usage-secrets) of the manual.
+shb.sops.secrets."jellyfin/ldap/adminPassword".request = config.shb.jellyfin.ldap.adminPassword.request;
-### Jellyfin through HTTPS {#services-jellyfin-usage-https}
-
-:::: {.note}
-We will build upon the [Initial Configuration](#services-jellyfin-usage-configuration) section,
-so please follow that first.
-::::
-
-If the `shb.ssl` block is used (see [manual](blocks-ssl.html#usage) on how to set it up),
-the instance will be reachable at `https://jellyfin.example.com`.
-
-Here is an example with Let's Encrypt certificates, validated using the HTTP method.
-First, set the global configuration for your domain:
-
-```nix
-shb.certs.certs.letsencrypt."example.com" = {
- domain = "example.com";
- group = "nginx";
- reloadServices = [ "nginx.service" ];
- adminEmail = "myemail@mydomain.com";
+shb.sops.secret."jellyfin/sso_secret".request = config.shb.jellyfin.sso.sharedSecret.request;
+shb.sops.secret."jellyfin/authelia/sso_secret" = {
+ request = config.shb.jellyfin.sso.sharedSecretForAuthelia.request;
+ settings.key = "jellyfin/sso_secret";
};
```
-Then you can tell Jellyfin to use those certificates.
+Secrets can be randomly generated with `nix run nixpkgs#openssl -- rand -hex 64`.
-```nix
-shb.certs.certs.letsencrypt."example.com".extraDomains = [ "jellyfin.example.com" ];
-
-shb.jellyfin = {
- ssl = config.shb.certs.certs.letsencrypt."example.com";
-};
-```
-
-### With LDAP Support {#services-jellyfin-usage-ldap}
-
-:::: {.note}
-We will build upon the [HTTPS](#services-jellyfin-usage-https) section,
-so please follow that first.
-::::
-
-We will use the [LLDAP block][] provided by Self Host Blocks.
-Assuming it [has been set already][LLDAP block setup], add the following configuration:
-
-[LLDAP block]: blocks-lldap.html
-[LLDAP block setup]: blocks-lldap.html#blocks-lldap-global-setup
-
-```nix
-shb.jellyfin.ldap
- enable = true;
- host = "127.0.0.1";
- port = config.shb.lldap.ldapPort;
- dcdomain = config.shb.lldap.dcdomain;
- adminPassword.result = config.shb.sops.secrets."jellyfin/ldap/adminPassword".result
-};
-
-shb.sops.secrets."jellyfin/ldap/adminPassword" = {
- request = config.shb.jellyfin.ldap.adminPassword.request;
- settings.key = "ldap/userPassword";
-};
-```
-
-The `shb.jellyfin.ldap.adminPasswordFile` must be the same
-as the `shb.lldap.ldapUserPasswordFile` which is achieved
-with the `key` option.
-The other secrets can be randomly generated with
-`nix run nixpkgs#openssl -- rand -hex 64`.
-
-Then, install the plugin [LDAP-Auth](https://github.com/jellyfin/jellyfin-plugin-ldapauth).
-It should be available from the official repository already.
-No manual configuration of the plugin is needed, just installation.
-Note that the version tested with is 19.0.0.0.
-If your version differs, it could lead to some configuration mismatch.
-If that's the case, please [open an issue](https://github.com/ibizaman/selfhostblocks/issues/new)
-or join the [support channel](https://img.shields.io/matrix/selfhostblocks%3Amatrix.org).
-
-And that's it.
-Now, go to the LDAP server at `http://ldap.example.com`,
-create the `jellyfin_user` and `jellyfin_admin` groups,
-create a user and add it to one or both groups.
-When that's done, go back to the Jellyfin server at
-`http://jellyfin.example.com` and login with that user.
-
-Work is in progress to make the creation of the LDAP user and group declarative too.
-
-### With SSO Support {#services-jellyfin-usage-sso}
-
-:::: {.note}
-We will build upon the [LDAP](#services-jellyfin-usage-ldap) section,
-so please follow that first.
-::::
-
-We will use the [SSO block][] provided by Self Host Blocks.
-Assuming it [has been set already][SSO block setup], add the following configuration:
-
-[SSO block]: blocks-sso.html
-[SSO block setup]: blocks-sso.html#blocks-sso-global-setup
-
-```nix
-shb.jellyfin.sso = {
- enable = true;
- endpoint = "https://${config.shb.authelia.subdomain}.${config.shb.authelia.domain}";
-
- secretFile = ;
- secretFileForAuthelia = ;
-};
-```
-
-Passing the `ssl` option will auto-configure nginx to force SSL connections with the given
-certificate.
+The [user](#services-jellyfin-options-shb.jellyfin.ldap.userGroup)
+and [admin](#services-jellyfin-options-shb.jellyfin.ldap.adminGroup)
+LDAP groups are created automatically.
The `shb.jellyfin.sso.secretFile` and `shb.jellyfin.sso.secretFileForAuthelia` options
must have the same content. The former is a file that must be owned by the `jellyfin` user while
the latter must be owned by the `authelia` user. I want to avoid needing to define the same secret
twice with a future secrets SHB block.
-Then, install the plugin [SSO-Auth](https://github.com/9p4/jellyfin-plugin-sso).
-It should be available from the official repository already.
-No manual configuration of the plugin is needed, just installation.
-Note that the version tested with is 3.5.2.4.
-If your version differs, it could lead to some configuration mismatch.
-If that's the case, please [open an issue](https://github.com/ibizaman/selfhostblocks/issues/new)
-or join the [support channel](https://img.shields.io/matrix/selfhostblocks%3Amatrix.org).
+### Certificates {#services-jellyfin-certs}
+
+For Let's Encrypt certificates, add:
+
+```nix
+{
+ shb.certs.certs.letsencrypt.${domain}.extraDomains = [
+ "${config.shb.jellyfin.subdomain}.${config.shb.jellyfin.domain}"
+ ];
+}
+```
### Backup {#services-jellyfin-usage-backup}
@@ -185,6 +111,27 @@ You can define any number of Restic instances to backup Jellyfin multiple times.
You will then need to configure more options like the `repository`,
as explained in the [restic](blocks-restic.html) documentation.
+### Impermanence {#services-jellyfin-impermanence}
+
+To save the data folder in an impermanence setup, add:
+
+```nix
+{
+ shb.zfs.datasets."safe/jellyfin".path = config.shb.jellyfin.impermanence;
+}
+```
+
+### Declarative LDAP {#services-jellyfin-declarative-ldap}
+
+To add a user `USERNAME` to the user and admin groups for jellyfin, add:
+
+```nix
+shb.lldap.ensureUsers.USERNAME.groups = [
+ config.shb.jellyfin.ldap.userGroup
+ config.shb.jellyfin.ldap.adminGroup
+];
+```
+
## Debug {#services-jellyfin-debug}
In case of an issue, check the logs for systemd service `jellyfin.service`.
diff --git a/test/common.nix b/test/common.nix
index e908d9a..c3216b2 100644
--- a/test/common.nix
+++ b/test/common.nix
@@ -283,6 +283,10 @@ in
type = str;
default = "[Ll]ogin";
};
+ loginSpawnsNewPage = mkOption {
+ type = bool;
+ default = false;
+ };
testLoginWith = mkOption {
type = listOf (submodule {
options = {
@@ -350,6 +354,8 @@ in
with open("${testCfg}") as f:
testCfg = json.load(f)
+ print("Test configuration:")
+ print(json.dumps(testCfg, indent=2))
browser_name = testCfg['browser']
browser_args = browsers.get(browser_name)
@@ -366,11 +372,24 @@ in
context.tracing.start(screenshots=True, snapshots=True, sources=True)
try:
page = context.new_page()
+ # This is used to debug frame changes.
+ # Frame changes or popup are somewhat handled with the expect_page() call later.
+ page.on("framenavigated", lambda frame: print("NAV:", frame.url))
+ page.on("frameattached", lambda frame: print("ATTACHED:", frame.url))
+ page.on("framedetached", lambda frame: print("DETACHED:", frame.url))
+
print(f"Going to {testCfg['startUrl']}")
page.goto(testCfg['startUrl'])
if testCfg.get("beforeHook") is not None:
- exec(testCfg.get("beforeHook"))
+ if testCfg['loginSpawnsNewPage']:
+ print("Login spawns new page")
+ # The with clause handles window.open() or .
+ with context.expect_page() as p:
+ exec(testCfg.get("beforeHook"))
+ page = p.value
+ else:
+ exec(testCfg.get("beforeHook"))
if u['username'] is not None:
print(f"Filling field username with {u['username']}")
diff --git a/test/services/jellyfin.nix b/test/services/jellyfin.nix
index c090d97..394401d 100644
--- a/test/services/jellyfin.nix
+++ b/test/services/jellyfin.nix
@@ -79,6 +79,7 @@ let
{ config, ... }:
{
imports = [
+ shb.test.baseModule
shb.test.clientLoginModule
];
virtualisation.memorySize = 4096;
@@ -89,28 +90,28 @@ let
test.login = {
browser = "firefox";
- # I tried without the path part but it randomly selects either the wizard
- # or the page that selects a server.
- # startUrl = "${config.test.proto}://${config.test.fqdn}/web/#/wizardstart.html";
- # startUrl = "${config.test.proto}://${config.test.fqdn}";
- startUrl = "${config.test.proto}://${config.test.fqdn}/web/#/login.html";
+ startUrl = "${config.test.proto}://${config.test.fqdn}";
usernameFieldLabelRegex = "[Uu]ser";
loginButtonNameRegex = "Sign In";
testLoginWith = [
- # I just couldn't make this work. It's very flaky.
- # Most of the time, the login jellyfin page doesn't even load
- # and the playwright browser is stuck on the splash page.
- # I resorted to test the API directly.
- # { username = "jellyfin"; password = "badpassword"; nextPageExpect = [
- # "expect(page).to_have_title(re.compile('Jellyfin'))"
- # "expect(page.get_by_text(re.compile('[Ii]nvalid'))).to_be_visible(timeout=30000)"
- # ]; }
- # { username = "jellyfin"; password = "admin"; nextPageExpect = [
- # "expect(page).to_have_title(re.compile('Jellyfin'))"
- # "expect(page.get_by_text(re.compile('[Ii]nvalid'))).not_to_be_visible(timeout=30000)"
- # "expect(page.get_by_role('label', re.compile('[Uu]ser'))).not_to_be_visible(timeout=30000)"
- # "expect(page.get_by_text(re.compile('[Pp]assword'))).not_to_be_visible(timeout=30000)"
- # ]; }
+ {
+ username = "jellyfin";
+ password = "badpassword";
+ nextPageExpect = [
+ "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";
+ nextPageExpect = [
+ "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)"
+ ];
+ }
];
};
};
@@ -135,6 +136,8 @@ let
host = "127.0.0.1";
port = config.shb.lldap.ldapPort;
dcdomain = config.shb.lldap.dcdomain;
+ userGroup = "user_group";
+ adminGroup = "admin_group";
adminPassword.result = config.shb.hardcodedsecret.jellyfinLdapUserPassword.result;
};
};
@@ -145,10 +148,95 @@ let
};
};
+ clientLoginLdap =
+ { config, ... }:
+ {
+ imports = [
+ shb.test.baseModule
+ shb.test.clientLoginModule
+ ];
+ virtualisation.memorySize = 4096;
+
+ test = {
+ subdomain = "j";
+ };
+
+ test.login = {
+ startUrl = "${config.test.proto}://${config.test.fqdn}";
+ usernameFieldLabelRegex = "[Uu]ser";
+ loginButtonNameRegex = "Sign In";
+ testLoginWith = [
+ {
+ username = "jellyfin";
+ password = "badpassword";
+ nextPageExpect = [
+ "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";
+ nextPageExpect = [
+ "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)"
+ ];
+ }
+ {
+ username = "alice";
+ password = "AlicePassword";
+ nextPageExpect = [
+ "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)"
+ "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)"
+ ];
+ }
+ {
+ username = "alice";
+ password = "NotAlicePassword";
+ nextPageExpect = [
+ "expect(page).to_have_title(re.compile('Jellyfin'))"
+ "expect(page.get_by_text(re.compile('[Ii]nvalid'))).to_be_visible(timeout=10000)"
+ ];
+ }
+ {
+ username = "bob";
+ password = "BobPassword";
+ nextPageExpect = [
+ "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)"
+ "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)"
+ ];
+ }
+ {
+ username = "bob";
+ password = "NotBobPassword";
+ nextPageExpect = [
+ "expect(page).to_have_title(re.compile('Jellyfin'))"
+ "expect(page.get_by_text(re.compile('[Ii]nvalid'))).to_be_visible(timeout=10000)"
+ ];
+ }
+ ];
+ };
+ };
+
sso =
{ config, ... }:
{
shb.jellyfin = {
+ ldap = {
+ userGroup = "user_group";
+ adminGroup = "admin_group";
+ };
+
sso = {
enable = true;
endpoint = "https://${config.shb.authelia.subdomain}.${config.shb.authelia.domain}";
@@ -168,6 +256,82 @@ let
};
};
+ clientLoginSso =
+ { config, ... }:
+ {
+ imports = [
+ shb.test.baseModule
+ shb.test.clientLoginModule
+ ];
+ virtualisation.memorySize = 4096;
+
+ test = {
+ subdomain = "j";
+ };
+
+ test.login = {
+ startUrl = "${config.test.proto}://${config.test.fqdn}";
+ beforeHook = ''
+ page.locator('text=Sign in with Authelia').click()
+ '';
+ usernameFieldLabelRegex = "Username";
+ passwordFieldLabelRegex = "Password";
+ loginButtonNameRegex = "[Ss]ign [Ii]n";
+ loginSpawnsNewPage = true;
+ testLoginWith = [
+ {
+ username = "alice";
+ password = "AlicePassword";
+ nextPageExpect = [
+ "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.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)"
+ ];
+ }
+ {
+ username = "alice";
+ password = "NotAlicePassword";
+ nextPageExpect = [
+ # 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.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)"
+ ];
+ }
+ {
+ username = "bob";
+ password = "BobPassword";
+ nextPageExpect = [
+ "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.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)"
+ ];
+ }
+ {
+ username = "bob";
+ password = "NotBobPassword";
+ 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.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)"
+ ];
+ }
+ ];
+ };
+ };
+
jellyfinTest =
name:
{ nodes, testScript }:
@@ -189,13 +353,14 @@ in
nodes.server = {
imports = [
basic
- clientLogin
];
};
- # Client login does not work without SSL.
- # At least, I couldn't make it work.
- nodes.client = { };
+ nodes.client = {
+ imports = [
+ clientLogin
+ ];
+ };
testScript = commonTestScript.access;
};
@@ -228,7 +393,6 @@ in
{ config, lib, ... }:
{
imports = [
- shb.test.baseModule
clientLogin
];
};
@@ -240,12 +404,18 @@ in
nodes.server = {
imports = [
basic
+ shb.test.certs
+ https
shb.test.ldap
ldap
];
};
- nodes.client = { };
+ nodes.client = {
+ imports = [
+ clientLoginLdap
+ ];
+ };
testScript = commonTestScript.access;
};
@@ -264,7 +434,11 @@ in
];
};
- nodes.client = { };
+ nodes.client = {
+ imports = [
+ clientLoginSso
+ ];
+ };
testScript = commonTestScript.access;
};