{ pkgs, lib, config, ... }: let cfg = config.modules.services.immich; fqdn = "${cfg.subdomain}.${config.networking.domain}"; volumeServices = names: ( lib.lists.foldl (services: name: services // { "podman-volume-immich_${name}" = { path = [ pkgs.podman ]; serviceConfig = { Type = "oneshot"; RemainAfterExit = true; }; script = '' podman volume inspect immich_${name} || podman volume create immich_${name} ''; partOf = [ "podman-compose-immich-root.target" ]; wantedBy = [ "podman-compose-immich-root.target" ]; }; } ) { } names ); containerServices = services: ( lib.lists.foldl (acc: { name, volumes ? [ ], ... }: let volume_services = map (volume: "podman-volume-immich_${volume}.service") volumes; dependent_services = [ "podman-network-immich_default.service" ] ++ volume_services; in acc // { "podman-immich_${name}" = { serviceConfig = { Restart = lib.mkOverride 500 "always"; }; after = dependent_services; requires = dependent_services; partOf = [ "podman-compose-immich-root.target" ]; wantedBy = [ "podman-compose-immich-root.target" ]; }; } ) { } services ); in { options.modules.services.immich = { enable = lib.mkEnableOption "Enable Immich"; subdomain = lib.mkOption { type = lib.types.str; }; version = lib.mkOption { type = lib.types.str; default = "latest"; }; port = lib.mkOption { type = lib.types.int; default = 2283; }; dataDir = lib.mkOption { type = lib.types.str; }; storageDirs = lib.mkOption { type = lib.types.listOf lib.types.str; default = [ ]; }; }; config = lib.mkIf cfg.enable { virtualisation.podman = { enable = true; autoPrune.enable = true; dockerCompat = true; defaultNetwork.settings = { # Required for container networking to be able to use names. dns_enabled = true; }; }; virtualisation.oci-containers.backend = "podman"; networking.firewall.trustedInterfaces = [ "podman1" ]; modules.firewall.interfaces.podman1 = [ "dns" ]; # Containers virtualisation.oci-containers.containers = { "immich_machine_learning" = { image = "ghcr.io/immich-app/immich-machine-learning:${cfg.version}"; environmentFiles = [ "/var/secrets/immich.env" ]; volumes = [ "immich_model_cache:/cache:rw" ]; log-driver = "journald"; extraOptions = [ "--network-alias=immich-machine-learning" "--network=immich_default" ]; }; "immich_postgres" = { image = "registry.hub.docker.com/tensorchord/pgvecto-rs:pg14-v0.2.0"; environmentFiles = [ "/var/secrets/immich.env" ]; environment = { POSTGRES_INITDB_ARGS = "--data-checksums"; }; volumes = [ "immich_db_data:/var/lib/postgresql/data:rw" ]; cmd = [ "postgres" "-c" "shared_preload_libraries=vectors.so" "-c" "search_path=\"$user\", public, vectors" "-c" "logging_collector=on" "-c" "max_wal_size=2GB" "-c" "shared_buffers=512MB" "-c" "wal_compression=on" ]; log-driver = "journald"; extraOptions = [ "--network-alias=database" "--network=immich_default" ]; }; "immich_redis" = { image = "registry.hub.docker.com/library/redis:6.2-alpine"; environmentFiles = [ "/var/secrets/immich.env" ]; log-driver = "journald"; extraOptions = [ "--network-alias=redis" "--network=immich_default" ]; }; "immich_server" = { image = "ghcr.io/immich-app/immich-server:${cfg.version}"; environmentFiles = [ "/var/secrets/immich.env" ]; volumes = [ "/etc/localtime:/etc/localtime:ro" "${cfg.dataDir}:/usr/src/app/upload:rw" ] ++ map (dir: "${dir}:${dir}:ro") cfg.storageDirs; ports = [ "${toString cfg.port}:2283/tcp" ]; dependsOn = [ "immich_postgres" "immich_redis" ]; log-driver = "journald"; extraOptions = [ "--network-alias=immich-server" "--network=immich_default" ]; }; }; systemd.services = (containerServices [ { name = "machine_learning"; volumes = [ "model_cache" ]; } { name = "postgres"; volumes = [ "db_data" ]; } { name = "redis"; } { name = "server"; } ]) // { # Networks "podman-network-immich_default" = { path = [ pkgs.podman ]; serviceConfig = { Type = "oneshot"; RemainAfterExit = true; ExecStop = "${pkgs.podman}/bin/podman network rm -f immich_default"; }; script = '' podman network inspect immich_default || podman network create immich_default ''; partOf = [ "podman-compose-immich-root.target" ]; wantedBy = [ "podman-compose-immich-root.target" ]; }; } // (volumeServices [ "db_data" "model_cache" ]); # 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" ]; }; modules.services.webserver = { enable = lib.mkDefault true; vHosts.${fqdn}.locations."/".proxyPort = cfg.port; }; }; }