nix-config/modules/services/immich.nix

392 lines
11 KiB
Nix
Raw Normal View History

2024-01-20 21:07:00 +01:00
# Auto-generated using compose2nix v0.1.6.
{
pkgs,
nodes,
2024-01-20 21:07:00 +01:00
config,
...
}: let
2024-03-12 21:59:03 +01:00
version = "v1.98.2";
2024-02-07 17:02:59 +01:00
immichDomain = "immich.${config.secrets.secrets.global.domains.web}";
2024-01-21 00:43:50 +01:00
ipImmichMachineLearning = "10.89.0.10";
ipImmichMicroservices = "10.89.0.11";
ipImmichPostgres = "10.89.0.12";
ipImmichRedis = "10.89.0.13";
ipImmichServer = "10.89.0.14";
2024-02-07 17:02:59 +01:00
configFile = pkgs.writeText "immich.config.json" (
builtins.toJSON {
ffmpeg = {
accel = "disabled";
bframes = -1;
cqMode = "auto";
crf = 23;
gopSize = 0;
maxBitrate = "0";
npl = 0;
preset = "ultrafast";
refs = 0;
targetAudioCodec = "aac";
targetResolution = "720";
targetVideoCodec = "h264";
temporalAQ = false;
threads = 0;
tonemap = "hable";
transcode = "required";
twoPass = false;
};
job = {
backgroundTask.concurrency = 5;
faceDetection.concurrency = 10;
library.concurrency = 5;
metadataExtraction.concurrency = 10;
migration.concurrency = 5;
search.concurrency = 5;
sidecar.concurrency = 5;
smartSearch.concurrency = 10;
thumbnailGeneration.concurrency = 10;
videoConversion.concurrency = 5;
};
library.scan = {
enabled = true;
cronExpression = "0 0 * * *";
};
logging = {
enabled = true;
level = "log";
};
machineLearning = {
clip = {
enabled = true;
modelName = "ViT-B-32__openai";
};
enabled = true;
facialRecognition = {
enabled = true;
maxDistance = 0.45;
minFaces = 2;
minScore = 0.65;
modelName = "buffalo_l";
};
url = "http://${ipImmichMachineLearning}:3003";
};
# XXX: Immich's oauth cannot use PKCE and uses legacy crypto so we need to run:
# kanidm system oauth2 warning-insecure-client-disable-pkce immich
# kanidm system oauth2 warning-enable-legacy-crypto immich
oauth = rec {
enabled = true;
autoLaunch = false;
autoRegister = true;
buttonText = "Login with Kanidm";
mobileOverrideEnabled = true;
mobileRedirectUri = "https://${immichDomain}/api/oauth/mobile-redirect";
clientId = "immich";
# clientSecret will be dynamically added in activation script
issuerUrl = "https://auth.${config.secrets.secrets.global.domains.web}/oauth2/openid/${clientId}";
scope = "openid email profile";
storageLabelClaim = "preferred_username";
};
2024-02-07 17:02:59 +01:00
map = {
enabled = true;
darkStyle = "";
lightStyle = "";
};
newVersionCheck.enabled = true;
passwordLogin.enabled = true;
reverseGeocoding.enabled = true;
server = {
externalDomain = "https://${immichDomain}";
loginPageMessage = "Wilkommen in Patricks tollem bilderparadies";
};
storageTemplate = {
enabled = true;
hashVerificationEnabled = true;
template = "{{y}}/{{MM}}/{{filename}}";
};
theme.customCss = "";
thumbnail = {
colorspace = "p3";
jpegSize = 1440;
quality = 80;
webpSize = 250;
};
trash = {
days = 30;
enabled = true;
};
}
);
2024-01-20 21:07:00 +01:00
environment = {
DB_DATABASE_NAME = "immich";
2024-01-21 00:43:50 +01:00
DB_HOSTNAME = ipImmichPostgres;
2024-01-20 21:07:00 +01:00
DB_PASSWORD_FILE = config.age.secrets.postgrespasswd.path;
DB_USERNAME = "postgres";
IMMICH_VERSION = "${version}";
UPLOAD_LOCATION = upload_folder;
2024-01-21 00:43:50 +01:00
IMMICH_SERVER_URL = "http://${ipImmichServer}:3001/";
IMMICH_MACHINE_LEARNING_URL = "http://${ipImmichMachineLearning}:3003";
REDIS_HOSTNAME = ipImmichRedis;
2024-02-07 17:02:59 +01:00
IMMICH_CONFIG_FILE = "/immich.config.json";
2024-01-20 21:07:00 +01:00
};
upload_folder = "/panzer/immich";
2024-01-21 00:43:50 +01:00
pgdata_folder = "/persist/immich/pgdata";
2024-01-20 21:07:00 +01:00
model_folder = "/state/immich/modeldata";
serviceConfig = {
serviceConfig = {
Restart = "always";
};
after = [
"podman-network-immich-default.service"
];
requires = [
"podman-network-immich-default.service"
];
partOf = [
"podman-compose-immich-root.target"
];
wantedBy = [
"podman-compose-immich-root.target"
];
};
processedConfigFile = "/run/agenix/immich.config.json";
2024-01-20 21:07:00 +01:00
in {
2024-01-22 01:15:50 +01:00
age.secrets.resticpasswd = {
generator.script = "alnum";
};
age.secrets.immichHetznerSsh = {
generator.script = "ssh-ed25519";
};
services.restic.backups = {
main = {
user = "root";
timerConfig = {
OnCalendar = "06:00";
Persistent = true;
RandomizedDelaySec = "3h";
};
initialize = true;
passwordFile = config.age.secrets.resticpasswd.path;
hetznerStorageBox = {
enable = true;
inherit (config.secrets.secrets.global.hetzner) mainUser;
inherit (config.secrets.secrets.global.hetzner.users.immich) subUid path;
sshAgeSecret = "immichHetznerSsh";
};
backupPrepareCommand = ''
${pkgs.podman}/bin/podman exec -t immich_postgres pg_dumpall -c -U postgres > /run/immich_dump.sql
'';
paths = [
"${upload_folder}/library"
"${upload_folder}/upload"
"${upload_folder}/profile"
"/run/immich_dump.sql"
];
pruneOpts = [
"--keep-daily 10"
"--keep-weekly 7"
"--keep-monthly 12"
"--keep-yearly 75"
];
};
};
# Mirror the original oauth2 secret
age.secrets.immich-oauth2-client-secret = {
inherit (nodes.elisabeth-kanidm.config.age.secrets.oauth2-immich) rekeyFile;
mode = "440";
group = "root";
};
system.activationScripts.agenixRooterDerivedSecrets = {
# Run after agenix has generated secrets
deps = ["agenix"];
text = ''
immichClientSecret=$(< ${config.age.secrets.immich-oauth2-client-secret.path})
${pkgs.jq}/bin/jq --arg immichClientSecret "$immichClientSecret" '.oauth.clientSecret = $immichClientSecret' ${configFile} > ${processedConfigFile}
chmod 444 ${processedConfigFile}
'';
};
2024-01-21 00:43:50 +01:00
microvm = {
mem = 1024 * 8;
vcpu = 12;
};
2024-03-14 23:08:42 +01:00
wireguard.elisabeth = {
client.via = "elisabeth";
firewallRuleForNode.elisabeth.allowedTCPPorts = [3000];
};
networking.nftables.chains.forward.into-immich-container = {
after = ["conntrack"];
rules = [
"iifname elisabeth ip saddr ${nodes.elisabeth.config.wireguard.elisabeth.ipv4} tcp dport 3001 accept"
"iifname podman1 oifname lan accept"
];
2024-01-21 00:43:50 +01:00
};
2024-01-20 21:07:00 +01:00
systemd.tmpfiles.settings = {
"10-immich" = {
${upload_folder}.d = {
mode = "0770";
};
${pgdata_folder}.d = {
mode = "0770";
};
${model_folder}.d = {
mode = "0770";
};
};
};
age.secrets.postgrespasswd = {
generator.script = "alnum";
};
age.secrets.redispasswd = {
generator.script = "alnum";
};
# Runtime
virtualisation.podman = {
enable = true;
autoPrune.enable = true;
dockerCompat = true;
};
virtualisation.oci-containers.backend = "podman";
# Containers
virtualisation.oci-containers.containers."immich_machine_learning" = {
image = "ghcr.io/immich-app/immich-machine-learning:${version}";
inherit environment;
volumes = [
"${processedConfigFile}:${environment.IMMICH_CONFIG_FILE}:ro"
2024-01-20 21:07:00 +01:00
"${model_folder}:/cache:rw"
];
log-driver = "journald";
extraOptions = [
"--network-alias=immich-machine-learning"
"--network=immich-default"
2024-01-21 00:43:50 +01:00
"--ip=${ipImmichMachineLearning}"
2024-01-20 21:07:00 +01:00
];
};
systemd.services."podman-immich_machine_learning" = serviceConfig;
2024-01-21 00:43:50 +01:00
2024-01-20 21:07:00 +01:00
virtualisation.oci-containers.containers."immich_microservices" = {
image = "ghcr.io/immich-app/immich-server:${version}";
inherit environment;
volumes = [
"${processedConfigFile}:${environment.IMMICH_CONFIG_FILE}:ro"
2024-01-20 21:07:00 +01:00
"/etc/localtime:/etc/localtime:ro"
"${upload_folder}:/usr/src/app/upload:rw"
2024-01-21 00:43:50 +01:00
"${environment.DB_PASSWORD_FILE}:${environment.DB_PASSWORD_FILE}:ro"
2024-01-20 21:07:00 +01:00
];
cmd = ["start.sh" "microservices"];
dependsOn = [
"immich_postgres"
"immich_redis"
];
log-driver = "journald";
extraOptions = [
"--network-alias=immich-microservices"
"--network=immich-default"
2024-01-21 00:43:50 +01:00
"--ip=${ipImmichMicroservices}"
2024-01-20 21:07:00 +01:00
];
};
systemd.services."podman-immich_microservices" =
serviceConfig
// {
unitConfig.UpheldBy = [
"podman-immich_postgres.service"
"podman-immich_redis.service"
];
};
2024-01-21 00:43:50 +01:00
2024-01-20 21:07:00 +01:00
virtualisation.oci-containers.containers."immich_postgres" = {
2024-03-12 21:59:03 +01:00
image = "tensorchord/pgvecto-rs:pg14-v0.2.0@sha256:90724186f0a3517cf6914295b5ab410db9ce23190a2d9d0b9dd6463e3fa298f0";
2024-01-20 21:07:00 +01:00
environment = {
POSTGRES_DB = environment.DB_DATABASE_NAME;
POSTGRES_PASSWORD_FILE = environment.DB_PASSWORD_FILE;
POSTGRES_USER = environment.DB_USERNAME;
};
volumes = [
"${pgdata_folder}:/var/lib/postgresql/data:rw"
2024-01-21 00:43:50 +01:00
"${environment.DB_PASSWORD_FILE}:${environment.DB_PASSWORD_FILE}:ro"
2024-01-20 21:07:00 +01:00
];
log-driver = "journald";
extraOptions = [
2024-01-21 00:43:50 +01:00
"--network-alias=immich_postgres"
2024-01-20 21:07:00 +01:00
"--network=immich-default"
2024-01-21 00:43:50 +01:00
"--ip=${ipImmichPostgres}"
2024-01-20 21:07:00 +01:00
];
};
systemd.services."podman-immich_postgres" = serviceConfig;
virtualisation.oci-containers.containers."immich_redis" = {
2024-03-12 21:59:03 +01:00
image = "redis:6.2-alpine@sha256:51d6c56749a4243096327e3fb964a48ed92254357108449cb6e23999c37773c5";
2024-01-20 21:07:00 +01:00
log-driver = "journald";
extraOptions = [
2024-01-21 00:43:50 +01:00
"--network-alias=immich_redis"
2024-01-20 21:07:00 +01:00
"--network=immich-default"
2024-01-21 00:43:50 +01:00
"--ip=${ipImmichRedis}"
2024-01-20 21:07:00 +01:00
];
};
systemd.services."podman-immich_redis" = serviceConfig;
virtualisation.oci-containers.containers."immich_server" = {
image = "ghcr.io/immich-app/immich-server:${version}";
inherit environment;
volumes = [
"${processedConfigFile}:${environment.IMMICH_CONFIG_FILE}:ro"
2024-01-20 21:07:00 +01:00
"/etc/localtime:/etc/localtime:ro"
"${upload_folder}:/usr/src/app/upload:rw"
2024-01-21 00:43:50 +01:00
"${environment.DB_PASSWORD_FILE}:${environment.DB_PASSWORD_FILE}:ro"
2024-01-20 21:07:00 +01:00
];
ports = [
2024-03-14 20:07:10 +01:00
"3000:3001/tcp"
2024-01-20 21:07:00 +01:00
];
cmd = ["start.sh" "immich"];
dependsOn = [
"immich_postgres"
"immich_redis"
];
log-driver = "journald";
extraOptions = [
"--network-alias=immich-server"
"--network=immich-default"
2024-01-21 00:43:50 +01:00
"--ip=${ipImmichServer}"
2024-01-20 21:07:00 +01:00
];
};
systemd.services."podman-immich_server" =
serviceConfig
// {
unitConfig.UpheldBy = [
"podman-immich_postgres.service"
"podman-immich_redis.service"
];
};
# Networks
systemd.services."podman-network-immich-default" = {
path = [pkgs.podman];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
ExecStop = "${pkgs.podman}/bin/podman network rm -f immich-default";
};
script = ''
2024-01-21 00:43:50 +01:00
podman network inspect immich-default || podman network create immich-default --opt isolate=true --disable-dns --subnet=10.89.0.0/24
2024-01-20 21:07:00 +01:00
'';
partOf = ["podman-compose-immich-root.target"];
wantedBy = ["podman-compose-immich-root.target"];
};
# Root service
# When started, this will automatically create all resources and start
# the containers. When stopped, this will teardown all resources.
systemd.targets."podman-compose-immich-root" = {
unitConfig = {
Description = "Root target generated by compose2nix.";
};
wantedBy = ["multi-user.target"];
};
}