{ ... }: { flake.nixosModules.systemServiceImmich = { config, lib, pkgs, ... }: let cfg = config.chiasson.system.services.immich; in { options.chiasson.system.services.immich = with lib; { enable = mkEnableOption "Immich stack (server + machine-learning + redis + postgres)."; version = mkOption { type = types.str; default = "release"; description = "Immich image tag to deploy."; }; host = mkOption { type = types.str; default = "0.0.0.0"; description = "Host interface to bind Immich server."; }; port = mkOption { type = types.int; default = 2283; description = "Host TCP port mapped to Immich server port 2283."; }; openFirewall = mkOption { type = types.bool; default = true; description = "Open firewall for Immich server port."; }; timezone = mkOption { type = types.str; default = "UTC"; description = "Timezone passed to Immich services via TZ."; }; uploadLocation = mkOption { type = types.str; default = "/var/lib/immich/library"; description = "Host path used for Immich uploads/library."; }; environmentFiles = mkOption { type = types.listOf types.path; default = [ ]; description = '' Docker `--env-file` paths for **immich-database** and **immich-server** (after inline `-e`). Use a sops template with `POSTGRES_PASSWORD` and `DB_PASSWORD` (same value) when using `sops.placeholder` for the DB secret; then set `postgres.password` to empty. ''; }; postgres = { image = mkOption { type = types.str; default = "ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0@sha256:bcf63357191b76a916ae5eb93464d65c07511da41e3bf7a8416db519b40b1c23"; description = "Immich recommended Postgres image with vector extensions."; }; user = mkOption { type = types.str; default = "postgres"; description = "Immich Postgres username."; }; password = mkOption { type = types.str; default = ""; description = '' Immich Postgres password (inline `POSTGRES_PASSWORD` / `DB_PASSWORD`). Leave empty when using `environmentFiles` with those keys from a sops template. ''; }; database = mkOption { type = types.str; default = "immich"; description = "Immich Postgres database name."; }; }; }; config = lib.mkIf cfg.enable { assertions = [ { assertion = cfg.postgres.password != "" || cfg.environmentFiles != [ ]; message = "chiasson.system.services.immich: set postgres.password or environmentFiles (e.g. sops-rendered POSTGRES_PASSWORD + DB_PASSWORD)."; } ]; virtualisation = { docker.enable = true; oci-containers = { backend = "docker"; containers = { immich-redis = { image = "docker.io/valkey/valkey:9@sha256:3b55fbaa0cd93cf0d9d961f405e4dfcc70efe325e2d84da207a0a8e6d8fde4f9"; extraOptions = [ "--network=immich-network" ]; }; immich-database = { image = cfg.postgres.image; environment = { POSTGRES_USER = cfg.postgres.user; POSTGRES_DB = cfg.postgres.database; POSTGRES_INITDB_ARGS = "--data-checksums"; } // lib.optionalAttrs (cfg.postgres.password != "") { POSTGRES_PASSWORD = cfg.postgres.password; }; environmentFiles = cfg.environmentFiles; volumes = [ "immich-postgres:/var/lib/postgresql/data" ]; extraOptions = [ "--network=immich-network" "--shm-size=128mb" ]; }; immich-machine-learning = { image = "ghcr.io/immich-app/immich-machine-learning:${cfg.version}"; environment = { TZ = cfg.timezone; }; volumes = [ "immich-model-cache:/cache" ]; extraOptions = [ "--network=immich-network" ]; }; immich-server = { image = "ghcr.io/immich-app/immich-server:${cfg.version}"; dependsOn = [ "immich-redis" "immich-database" "immich-machine-learning" ]; ports = [ "${cfg.host}:${toString cfg.port}:2283" ]; environment = { TZ = cfg.timezone; DB_HOSTNAME = "immich-database"; DB_USERNAME = cfg.postgres.user; DB_DATABASE_NAME = cfg.postgres.database; REDIS_HOSTNAME = "immich-redis"; IMMICH_MACHINE_LEARNING_URL = "http://immich-machine-learning:3003"; UPLOAD_LOCATION = "/data"; } // lib.optionalAttrs (cfg.postgres.password != "") { DB_PASSWORD = cfg.postgres.password; }; environmentFiles = cfg.environmentFiles; volumes = [ "${cfg.uploadLocation}:/data" "/etc/localtime:/etc/localtime:ro" ]; extraOptions = [ "--network=immich-network" "--pull=always" ]; }; }; }; }; systemd.services.immich-network = { description = "Create Docker network for Immich"; after = [ "docker.service" ]; requires = [ "docker.service" ]; wantedBy = [ "multi-user.target" ]; serviceConfig.Type = "oneshot"; script = '' ${pkgs.docker}/bin/docker network inspect immich-network >/dev/null 2>&1 || \ ${pkgs.docker}/bin/docker network create immich-network ''; }; systemd.services."docker-immich-redis" = { after = [ "immich-network.service" ]; requires = [ "immich-network.service" ]; }; systemd.services."docker-immich-database" = { after = [ "immich-network.service" ]; requires = [ "immich-network.service" ]; }; systemd.services."docker-immich-machine-learning" = { after = [ "immich-network.service" ]; requires = [ "immich-network.service" ]; }; systemd.services."docker-immich-server" = { after = [ "immich-network.service" "docker-immich-redis.service" "docker-immich-database.service" "docker-immich-machine-learning.service" ]; requires = [ "immich-network.service" "docker-immich-redis.service" "docker-immich-database.service" "docker-immich-machine-learning.service" ]; }; networking.firewall.allowedTCPPorts = lib.optionals cfg.openFirewall [ cfg.port ]; }; }; }