diff --git a/nixos/modules/services/web-apps/immich.nix b/nixos/modules/services/web-apps/immich.nix index a68066c30df0..c1958ffffd2e 100644 --- a/nixos/modules/services/web-apps/immich.nix +++ b/nixos/modules/services/web-apps/immich.nix @@ -10,6 +10,10 @@ let isPostgresUnixSocket = lib.hasPrefix "/" cfg.database.host; isRedisUnixSocket = lib.hasPrefix "/" cfg.redis.host; + # convert a Nix attribute path to jq object identifier-index: + # https://jqlang.org/manual/#object-identifier-index + attrPathToIndex = attrPath: "." + lib.concatStringsSep "." attrPath; + commonServiceConfig = { Type = "simple"; Restart = "on-failure"; @@ -147,6 +151,27 @@ in ); }; + secretSettings = mkOption { + default = { }; + description = '' + Secrets to to be added to the JSON file generated from {option}`settings`, read from files. + ''; + example = lib.literalExpression '' + { + notifications.smtp.transport.password = "/path/to/secret"; + oauth.clientSecret = "/path/to/other/secret"; + } + ''; + type = + let + inherit (types) attrsOf either path; + recursiveType = either (attrsOf recursiveType) path // { + description = "nested " + (attrsOf path).description; + }; + in + recursiveType; + }; + machine-learning = { enable = mkEnableOption "immich's machine-learning functionality to detect faces and search for objects" @@ -352,8 +377,8 @@ in IMMICH_MEDIA_LOCATION = cfg.mediaLocation; IMMICH_MACHINE_LEARNING_URL = "http://localhost:3003"; } - // lib.optionalAttrs (cfg.settings != null) { - IMMICH_CONFIG_FILE = "${format.generate "immich.json" cfg.settings}"; + // lib.optionalAttrs (cfg.settings != null || cfg.settingsFile != null) { + IMMICH_CONFIG_FILE = "/run/immich/config.json"; }; services.immich.machine-learning.environment = { @@ -382,7 +407,24 @@ in postgresqlPackage ]; + preStart = mkIf (cfg.settings != null) ( + '' + cat '${format.generate "immich-config.json" cfg.settings}' > /run/immich/config.json + '' + + lib.concatStrings ( + lib.mapAttrsToListRecursive (attrPath: _: '' + tmp="$(mktemp)" + ${lib.getExe pkgs.jq} --rawfile secret "$CREDENTIALS_DIRECTORY/${attrPathToIndex attrPath}" \ + '${attrPathToIndex attrPath} = $secret' /run/immich/config.json > "$tmp" + mv "$tmp" /run/immich/config.json + '') cfg.secretSettings + ) + ); + serviceConfig = commonServiceConfig // { + LoadCredential = lib.mapAttrsToListRecursive ( + attrPath: file: "${attrPathToIndex attrPath}:${file}" + ) cfg.secretSettings; ExecStart = lib.getExe cfg.package; EnvironmentFile = mkIf (cfg.secretsFile != null) cfg.secretsFile; Slice = "system-immich.slice"; diff --git a/nixos/tests/web-apps/immich.nix b/nixos/tests/web-apps/immich.nix index 550a1630bda8..e9b3c38961fd 100644 --- a/nixos/tests/web-apps/immich.nix +++ b/nixos/tests/web-apps/immich.nix @@ -17,6 +17,15 @@ services.immich = { enable = true; environment.IMMICH_LOG_LEVEL = "verbose"; + settings.backup.database = { + enabled = true; + cronExpression = "invalid"; + }; + secretSettings = { + backup.database.cronExpression = "${pkgs.writeText "cron" "0 02 * * *"}"; + # thanks to LoadCredential files only readable by root should work + notifications.smtp.transport.password = "/etc/shadow"; + }; }; }; @@ -25,6 +34,8 @@ machine.wait_for_unit("immich-server.service") + machine.succeed("stat -L -c '%a %U %G' /run/immich/config.json | grep '600 immich immich'") + machine.wait_for_open_port(2283) # Server machine.wait_for_open_port(3003) # Machine learning machine.succeed("curl --fail http://localhost:2283/")