{ pkgs, inputs, ... }: # Immich photo/video library. Native nixpkgs module (not the upstream compose # stack) — it owns its own postgres (with the pgvector + vectorchord extensions # it needs for search) and a unix-socket redis, so nothing else is required here. # # Storage: everything lives under /mnt/data/AppData/immich, which is the media # store MIGRATED from the old ZimaOS/CasaOS install's UPLOAD_LOCATION # (/mnt/data/Immich/upload — same layout: library/ upload/ thumbs/ # encoded-video/ profile/ backups/). See scripts/immich-import-legacy-db for the # matching database import. The postgres cluster itself stays on the OS disk. # # ⚠️ The immich DB is the only copy of albums/faces/dates — the files alone # can't rebuild it. It joins the other unbacked databases on this network. let # The PACKAGE comes from nixpkgs-unstable (3.0.3); the MODULE comes from the # 26.05 pin (which ships 2.7.5). That combination is safe because the two # module files are byte-identical — verified by diffing them at the revisions # in flake.lock. RE-CHECK THAT DIFF on any input bump: # diff <(nixpkgs)/nixos/modules/services/web-apps/immich.nix \ # <(unstable)/nixos/modules/services/web-apps/immich.nix # # Why: jupiter's imported database was last written by immich 3.0.0, and # immich runs its migrations forward only — 2.7.5 refuses to start against it # with "corrupted migrations: previously executed migration # 1776217577402-DropAuditTable is missing". Drop this override once nixos-26.11 # (or whatever the pin becomes) ships >= 3.0.0. unstable = import inputs.nixpkgs-unstable { inherit (pkgs.stdenv.hostPlatform) system; }; in { services.immich = { enable = true; # Both the server and immich-machine-learning follow this: the module takes # the ML service from cfg.package.machine-learning (passthru). package = unstable.immich; # Listens on all interfaces: :2283 stays closed on the LAN (no # openFirewall), reachable over tailscale0 and via localhost (caddy). host = "0.0.0.0"; port = 2283; mediaLocation = "/mnt/data/AppData/immich"; machine-learning.enable = true; # Hardware transcoding would need the iGPU passed in explicitly, e.g. # accelerationDevices = [ "/dev/dri/renderD128" ]; the default [ ] means # PrivateDevices=yes and CPU-only transcode. The ZimaBlade's Celeron does # this slowly but it only runs on upload. }; # /mnt/data/AppData is drwx--x--- darman:users — immich needs group "users" # just to TRAVERSE into its own media dir. The dir itself stays 0700 # immich:immich (the module's tmpfiles rule re-asserts that every rebuild, # and UMask=0077 keeps new files private), so this grants nothing else. users.users.immich.extraGroups = [ "users" ]; # mediaLocation is outside /var/lib, so the module won't create it — its own # tmpfiles entry only ADJUSTS an existing dir. Harmless no-op after the # legacy import, which puts the real store here. systemd.tmpfiles.rules = [ "d /mnt/data/AppData/immich 0700 immich immich -" ]; }