Files
hearth/nixos/module/hectic/service/immich.nix
T
yukkop 252d3418a5
runner nix smoke / nix label and flake smoke (push) Failing after 1m15s
fix: immitch 502
2026-09-13 21:49:57 +00:00

169 lines
5.3 KiB
Nix

{ ... }:
{
lib,
config,
...
}:
let
cfg = config.hectic.services.immich;
in
{
options.hectic.services.immich = {
enable = lib.mkEnableOption "Immich self-hosted photo and video service";
domain = lib.mkOption {
type = lib.types.strMatching "[A-Za-z0-9][A-Za-z0-9.-]*";
description = "Public hostname used to reach Immich.";
};
mediaLocation = lib.mkOption {
type = lib.types.path;
default = "/var/lib/immich";
description = ''
Local filesystem path used for Immich media. Upstream Immich does not
support S3 as its media backend; use a local disk or block volume here.
'';
};
maxUploadSize = lib.mkOption {
type = lib.types.strMatching "[1-9][0-9]*[KMG]?";
default = "10G";
description = "Maximum request body accepted by nginx in front of Immich.";
};
secretsFile = lib.mkOption {
type = lib.types.nullOr (lib.types.strMatching "/[^[:space:]]+");
default = null;
description = ''
SOPS-backed environment file passed to Immich. Use this for secrets
such as DB_PASSWORD; never put secret values in Nix configuration.
'';
};
machineLearning = {
enable = lib.mkOption {
type = lib.types.bool;
default = true;
description = "Whether to enable Immich machine-learning features.";
};
};
accelerationDevices = lib.mkOption {
type = lib.types.nullOr (lib.types.listOf lib.types.str);
default = [ ];
description = "Device paths exposed to Immich for hardware acceleration.";
};
storageBox = {
enable = lib.mkEnableOption "Hetzner Storage Box media storage";
host = lib.mkOption {
type = lib.types.strMatching "[A-Za-z0-9][A-Za-z0-9.-]*";
default = "u666713-sub1.your-storagebox.de";
description = "Hetzner Storage Box SMB hostname.";
};
username = lib.mkOption {
type = lib.types.strMatching "[A-Za-z0-9][A-Za-z0-9_-]*";
default = "u666713-sub1";
description = "Storage Box SMB username.";
};
share = lib.mkOption {
type = lib.types.strMatching "[A-Za-z0-9][A-Za-z0-9_-]*";
default = "u666713-sub1";
description = "SMB share exported by Storage Box.";
};
subdirectory = lib.mkOption {
type = lib.types.nullOr (lib.types.strMatching "[A-Za-z0-9][A-Za-z0-9_./-]*");
default = null;
description = "Directory within the SMB share used by Immich.";
};
credentialsFile = lib.mkOption {
type = lib.types.nullOr (lib.types.strMatching "/[^[:space:]]+");
default = null;
description = ''
Runtime file containing the SMB password. Keep this in a SOPS
secret, outside the Nix store.
'';
};
};
};
config = lib.mkIf cfg.enable {
assertions = [
{
assertion = cfg.secretsFile == null || !lib.hasPrefix "/nix/store/" cfg.secretsFile;
message = "hectic.services.immich.secretsFile must reference a runtime secret path, not /nix/store.";
}
{
assertion = !cfg.storageBox.enable || cfg.storageBox.credentialsFile != null;
message = "hectic.services.immich.storageBox.credentialsFile is required when Storage Box is enabled.";
}
{
assertion =
!cfg.storageBox.enable
|| cfg.storageBox.credentialsFile == null
|| !lib.hasPrefix "/nix/store/" cfg.storageBox.credentialsFile;
message = "hectic.services.immich.storageBox.credentialsFile must reference a runtime secret path, not /nix/store.";
}
];
services.immich = {
enable = true;
host = "127.0.0.1";
mediaLocation = cfg.mediaLocation;
secretsFile = cfg.secretsFile;
accelerationDevices = cfg.accelerationDevices;
machine-learning.enable = cfg.machineLearning.enable;
settings.server.externalDomain = "https://${cfg.domain}";
};
services.nginx = {
enable = true;
virtualHosts.${cfg.domain} = {
enableACME = true;
forceSSL = true;
extraConfig = lib.mkForce ''
client_max_body_size ${cfg.maxUploadSize};
'';
locations."/" = {
proxyPass = "http://127.0.0.1:${toString config.services.immich.port}";
proxyWebsockets = true;
extraConfig = ''
proxy_read_timeout 600s;
proxy_send_timeout 600s;
'';
};
};
};
fileSystems.${toString cfg.mediaLocation} = lib.mkIf cfg.storageBox.enable {
device = "//${cfg.storageBox.host}/${cfg.storageBox.share}";
fsType = "cifs";
options = [
"_netdev"
"nofail"
"x-systemd.mount-timeout=60s"
"vers=3.1.1"
"seal"
"cache=none"
"credentials=${cfg.storageBox.credentialsFile}"
"username=${cfg.storageBox.username}"
"uid=${config.services.immich.user}"
"gid=${config.services.immich.group}"
"file_mode=0660"
"dir_mode=0770"
] ++ lib.optional (cfg.storageBox.subdirectory != null)
"prefixpath=${cfg.storageBox.subdirectory}";
};
systemd.services.immich-server.unitConfig.RequiresMountsFor = lib.mkIf cfg.storageBox.enable [
cfg.mediaLocation
];
systemd.services.immich-server.serviceConfig.Restart = lib.mkForce "always";
};
}