diff --git a/modules/config.nix b/modules/config.nix index 0164ac2..e73aac7 100644 --- a/modules/config.nix +++ b/modules/config.nix @@ -84,6 +84,69 @@ in { ${commands} '' ); + + create-db = lib.stringAfter ["var"] ( + let + dbname = "jellyfin.db"; + defaultDB = ./default.db; + sq = "${pkgs.sqlite}/bin/sqlite3 \"${path}/${dbname}\" --"; + path = "/var/lib/jellyfin/data"; + + # ${sq} "INSERT INTO Users (Id, AudioLanguagePreference, AuthenticationProviderId, DisplayCollectionsView, DisplayMissingEpisodes, EnableAutoLogin, EnableLocalPassword, EnableNextEpisodeAutoPlay, EnableUserPreferenceAccess, HidePlayedInLatest, InternalId, LoginAttemptsBeforeLockout, MaxActiveSessions, MaxParentalAgeRating, Password, HashedPasswordFile, PasswordResetProviderId, PlayDefaultAudioTrack, RememberAudioSelections, RememberSubtitleSelections, RemoteClientBitrateLimit, SubtitleLanguagePreference, SubtitleMode, SyncPlayAccess, Username, CastReceiverId) \ + # VALUES(${user.})" + genUser = index: user: let + values = + builtins.mapAttrs (name: value: + if (isBool value) + then + if value + then "1" + else "0" + else value) + (cfg.Users + // { + Id = + if (builtins.hasAttr "Id" cfg.Users) + then cfg.Users.Id + else "$(${pkgs.libuuid}/bin/uuidgen | ${pkgs.coreutils}/bin/tr '[:lower:]' '[:upper:]')"; + InternalId = + if (builtins.hasAttr "InternalId" cfg.Users) + then cfg.Users.InternalId + else "$(($maxIndex+${index + 1}))"; + Password = + if (hasAttr "HashedPasswordFile" cfg.Users) + then "$(${pkgs.coreutils}/bin/cat \"${cfg.Users.HashedPasswordFile}\")" + else "$(${self.packages.${pkgs.system}.genhash}/bin/genhash -k \"${cfg.Users.Password}\" -i 210000 -l 128 -u)"; + }); + in + /* + bash + */ + '' + if [ -n $(${sq} "SELECT 1 FROM Users WHERE Username = '${user.Username}'") ]; then + # Create user + ${sq} "INSERT INTO Users (${concatStringsSep "," + (builtins.filter (x: x != "HashedPasswordFile") + (lib.attrsets.mapAttrsToList (name: value: "${name}") + options.services.declarative-jellyfin.Users.options))}) \\ + VALUES(${builtins.attrValues values})" + fi + ''; + in + /* + bash + */ + '' + mkdir -p ${path} + # Make sure there is a database + if [ -z "${path}/${dbname}" ] && cp ${defaultDB} "${path}/${dbname}" + + maxIndex=$(${sq} 'SELECT InternalId FROM Users ORDER BY InternalId DESC LIMIT 1') + if [ -n "$maxIndex" ] && maxIndex="1" + + + '' + ); }; }; } diff --git a/modules/default.db b/modules/default.db new file mode 100755 index 0000000..ea8485f Binary files /dev/null and b/modules/default.db differ diff --git a/modules/options/default.nix b/modules/options/default.nix index e609ac3..3d3736c 100644 --- a/modules/options/default.nix +++ b/modules/options/default.nix @@ -5,6 +5,7 @@ with lib; { ./encoding.nix ./network.nix ./branding.nix + ./users.nix ]; options.services.declarative-jellyfin = { enable = mkEnableOption "Jellyfin Service"; diff --git a/modules/options/users.nix b/modules/options/users.nix index 4dafb18..206b34f 100644 --- a/modules/options/users.nix +++ b/modules/options/users.nix @@ -9,7 +9,6 @@ with lib; { Id = mkOption { type = types.str; # TODO: Limit the id to the pattern: "18B51E25-33FD-46B6-BBF8-DB4DD77D0679" description = "The ID of the user"; - default = "autogenerate"; example = "18B51E25-33FD-46B6-BBF8-DB4DD77D0679"; }; AudioLanguagePreference = mkOption { @@ -64,10 +63,9 @@ with lib; { example = false; }; InternalId = mkOption { - type = with types; either int str; # TODO: Limit string to "autogenerate" + type = types.int; # NOTE: index is 1-indexed! NOT 0-indexed. description = "The index of the user in the database. Be careful setting this option. 1 indexed."; - default = "autogenerate"; example = 69; }; LoginAttemptsBeforeLockout = mkOption { diff --git a/tests/create_users.nix b/tests/create_users.nix new file mode 100644 index 0000000..351cbc2 --- /dev/null +++ b/tests/create_users.nix @@ -0,0 +1,49 @@ +{pkgs ? import {}, ...}: let + name = "minimal"; +in { + inherit name; + test = pkgs.nixosTest { + inherit name; + nodes = { + machine = { + config, + pkgs, + ... + }: { + imports = [ + ../modules/default.nix + ]; + + virtualisation.memorySize = 1024; + + environment.systemPackages = [ + pkgs.sqlite + ]; + + services.declarative-jellyfin = { + enable = true; + Users = [ + { + Username = "admin"; + Password = "123"; + } + ]; + }; + }; + }; + + testScript = + /* + py + */ + '' + machine.start() + machine.wait_for_unit("multi-user.target"); + machine.succeed("file /var/lib/jellyfin/data/jellyfin.db") + users = machine.succeed("sqlite3 /var/lib/jellyfin/data/jellyfin.db -- \"SELECT * FROM Users WHERE Username = 'admin'\"") + print(users) + if users == "": + assert False, "User not in db" + ''; + }; +} diff --git a/tests/defaultdb.nix b/tests/defaultdb.nix new file mode 100644 index 0000000..6f80497 --- /dev/null +++ b/tests/defaultdb.nix @@ -0,0 +1,39 @@ +{pkgs ? import {}, ...}: let + name = "minimal"; +in { + inherit name; + test = pkgs.nixosTest { + inherit name; + nodes = { + machine = { + config, + pkgs, + ... + }: { + imports = [ + ../modules/default.nix + ]; + + services.jellyfin.enable = true; + + virtualisation.memorySize = 1024; + }; + }; + + testScript = + /* + py + */ + '' + machine.start() + machine.wait_for_unit("multi-user.target"); + machine.wait_for_unit("jellyfin.service"); + machine.wait_for_file("/var/lib/jellyfin/data/jellyfin.db", 60) + machine.succeed("sleep 10") + machine.systemctl("stop jellyfin.service") + machine.wait_until_fails("pgrep jellyfin") + machine.copy_from_vm("/var/lib/jellyfin/data/jellyfin.db", "jellyfin.db") + assert False + ''; + }; +} diff --git a/tests/xml.nix b/tests/xml.nix index 34edb49..d8c47c6 100644 --- a/tests/xml.nix +++ b/tests/xml.nix @@ -154,47 +154,51 @@ in { }; }; - testScript = '' - import xml.etree.ElementTree as ET + testScript = + /* + py + */ + '' + import xml.etree.ElementTree as ET - machine.wait_for_unit("multi-user.target"); + machine.wait_for_unit("multi-user.target"); - with subtest("network.xml"): - # stupid fucking hack because you cant open files in python for some reason - xml = machine.succeed("cat /var/lib/jellyfin/config/network.xml") - tree = ET.ElementTree(ET.fromstring(xml)) - root = tree.getroot() + with subtest("network.xml"): + # stupid fucking hack because you cant open files in python for some reason + xml = machine.succeed("cat /var/lib/jellyfin/config/network.xml") + tree = ET.ElementTree(ET.fromstring(xml)) + root = tree.getroot() - with subtest("PublishedServerUriBySubnet"): - for child in root: - if child.tag == "PublishedServerUriBySubnet": - try: - if child[0].text == "all=https://test.test.test": + with subtest("PublishedServerUriBySubnet"): + for child in root: + if child.tag == "PublishedServerUriBySubnet": + try: + if child[0].text == "all=https://test.test.test": + break + except: + print("An error occured when trying to parse xml") + print(xml) + assert False, "Exception occured, check output above" + else: + assert False, "The shit was not found. Full XML: " + xml + + with subtest("EnableHttps"): + for child in root: + if child.tag == "EnableHttps": + if child.text == "true": break - except: - print("An error occured when trying to parse xml") - print(xml) - assert False, "Exception occured, check output above" - else: - assert False, "The shit was not found. Full XML: " + xml + else: + assert False, "The shit was not found. Full XML: " + xml - with subtest("EnableHttps"): - for child in root: - if child.tag == "EnableHttps": - if child.text == "true": - break - else: - assert False, "The shit was not found. Full XML: " + xml + with subtest("RequireHttps"): + for child in root: + if child.tag == "RequireHttps": + if child.text == "true": + break + else: + assert False, "The shit was not found. Full XML: " + xml - with subtest("RequireHttps"): - for child in root: - if child.tag == "RequireHttps": - if child.text == "true": - break - else: - assert False, "The shit was not found. Full XML: " + xml - - machine.shutdown() - ''; + machine.shutdown() + ''; }; }