diff --git a/modules/config.nix b/modules/config.nix index 5f7da76..2f9e4ab 100644 --- a/modules/config.nix +++ b/modules/config.nix @@ -61,7 +61,7 @@ in { commands = concatStringsSep "\n" (map - (x: "cp -s \"${pkgs.writeText x.file (toXml x.name x.content)}\" \"/var/lib/jellyfin/config/${x.file}\"") + (x: "test ! -e \"/var/lib/jellyfin/config/${x.file}\" && cp -s \"${pkgs.writeText x.file (toXml x.name x.content)}\" \"/var/lib/jellyfin/config/${x.file}\"") [ { name = "NetworkConfiguration"; @@ -92,8 +92,6 @@ in { 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 @@ -103,21 +101,23 @@ in { if value then "1" else "0" + else if (isNull value) + then "NULL" else value) - (cfg.Users + (user // { Id = - if (builtins.hasAttr "Id" cfg.Users) - then cfg.Users.Id + if !(isNull user.Id) + then user.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}))"; + if !(isNull user.InternalId) + then user.InternalId + else "$(($maxIndex+${toString (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)"; + if !(isNull user.HashedPasswordFile) + then "$(${pkgs.coreutils}/bin/cat \"${user.HashedPasswordFile}\")" + else "$(${self.packages.${pkgs.system}.genhash}/bin/genhash -k \"${user.Password}\" -i 210000 -l 128 -u)"; }); in /* @@ -130,9 +130,9 @@ in { ( builtins.filter (x: x != "HashedPasswordFile") (lib.attrsets.mapAttrsToList (name: value: "${name}") - options.services.declarative-jellyfin.Users.options) + ((import ./options/users.nix {inherit lib;}).options.services.declarative-jellyfin.Users.type.getSubOptions [])) )}) \\ - VALUES(${builtins.attrValues values})" + VALUES(${concatStringsSep "," (map toString (builtins.attrValues values))})" fi ''; in @@ -144,6 +144,7 @@ in { # Make sure there is a database if [ ! -e "${path}/${dbname}" ]; then cp ${defaultDB} "${path}/${dbname}" + chmod 770 "${path}/${dbname}" fi maxIndex=$(${sq} 'SELECT InternalId FROM Users ORDER BY InternalId DESC LIMIT 1') @@ -151,6 +152,23 @@ in { maxIndex="1" fi + ${ + concatStringsSep "\n" + ( + map ({ + fst, + snd, + }: + genUser snd fst) + ( + lib.lists.zipLists cfg.Users + ( + builtins.genList (x: x) + (builtins.length cfg.Users) + ) + ) + ) + } '' ); }; diff --git a/modules/options/users.nix b/modules/options/users.nix index 206b34f..04b6b01 100644 --- a/modules/options/users.nix +++ b/modules/options/users.nix @@ -4,15 +4,17 @@ with lib; { # Based on: https://github.com/jellyfin/jellyfin/blob/master/MediaBrowser.Model/Configuration/UserConfiguration.cs Users = mkOption { description = "User configuration"; + default = []; type = lib.types.listOf (lib.types.submodule ({config, ...}: { options = { Id = mkOption { - type = types.str; # TODO: Limit the id to the pattern: "18B51E25-33FD-46B6-BBF8-DB4DD77D0679" + type = types.nullOr types.str; # TODO: Limit the id to the pattern: "18B51E25-33FD-46B6-BBF8-DB4DD77D0679" description = "The ID of the user"; example = "18B51E25-33FD-46B6-BBF8-DB4DD77D0679"; + default = null; }; AudioLanguagePreference = mkOption { - type = with types; either null str; + type = with types; nullOr str; description = "The audio language preference. Defaults to 'Any Language'"; default = null; example = "eng"; @@ -63,10 +65,11 @@ with lib; { example = false; }; InternalId = mkOption { - type = types.int; + type = with types; nullOr 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."; example = 69; + default = null; }; LoginAttemptsBeforeLockout = mkOption { type = types.int; @@ -81,15 +84,16 @@ with lib; { example = 5; }; MaxParentalAgeRating = mkOption { - type = with types; either null int; + type = with types; nullOr int; # idk no docs man default = null; }; Password = mkOption { type = types.str; + default = ""; }; HashedPasswordFile = mkOption { - type = types.path; + type = with types; either path str; description = '' A path to a pbkdf2-sha512 hash in this format [PHC string](https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md). @@ -106,6 +110,7 @@ with lib; { ``` ''; + default = ""; example = '' # the format is: $[$=(,=)*][$[$]] $PBKDF2-SHA512$iterations=210000$D12C02D1DD15949D867BCA9971BE9987$67E75CDCD14E7F6FDDF96BAACBE9E84E5197FB9FE454FB039F5CD773D7DF558B57DC81DB42B6F7CF0E6B8207A771E5C0EE0DBFD91CE5BAF804FE53F70E61CD2E @@ -135,7 +140,7 @@ with lib; { default = 0; }; SubtitleLanguagePreference = mkOption { - type = with types; either null str; + type = with types; nullOr str; description = "The subtitle language preference. Defaults to 'Any Language'"; default = null; example = "eng"; diff --git a/tests/create_users.nix b/tests/create_users.nix index 351cbc2..f5d9ba0 100644 --- a/tests/create_users.nix +++ b/tests/create_users.nix @@ -16,8 +16,9 @@ in { virtualisation.memorySize = 1024; - environment.systemPackages = [ - pkgs.sqlite + environment.systemPackages = with pkgs; [ + sqlite + file ]; services.declarative-jellyfin = { @@ -40,9 +41,9 @@ in { 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'\"") + users = machine.succeed("sqlite3 /var/lib/jellyfin/data/jellyfin.db -- \"SELECT * FROM Users\"") print(users) - if users == "": + if machine.succeed("sqlite3 /var/lib/jellyfin/data/jellyfin.db -- \"SELECT * FROM Users WHERE Username = 'admin'\"") == "": assert False, "User not in db" ''; };