feat(web-arm): deploy Immich with Authelia OIDC, group-gated access #181

Merged
dominik.polakovics merged 1 commit from afk/178 into main 2026-06-26 15:14:09 +02:00

Summary

Deploys Immich on web-arm via the native services.immich module (immich 2.7.5, nixpkgs 26.05), fronted at https://immich.cloonar.com with OAuth-only login through Authelia, restricted to members of the immich LDAP group. This is the thin end-to-end slice: a working, access-gated, empty Immich.

What's included

  • hosts/web-arm/modules/immich/ — native module on 127.0.0.1:2283 (nginx fronts it, firewall closed), its own redis instance, machine-learning on.
  • Storage split — originals (library/) on the Hetzner Storage-Box CIFS share; thumbs//upload//profile/ stay on the local SSD under /var/lib/immich.
  • Declarative config via sops template — the OIDC client secret is injected at render time, so it never enters the Nix store. Password login off, transcoding off, ML on, heavy job queues throttled to concurrency 1. All config keys were validated against the immich 2.7.5 schema (oauth.*, passwordLogin, ffmpeg.transcode enum, machineLearning, job..concurrency, newVersionCheck).
  • CPU throttlingimmich-server / immich-machine-learning at CPUWeight=20; the websites keep the default 100, so under contention they get ~5x the CPU share.
  • Autheliaimmich OIDC client + deny-by-default authorization policy gating on group:immich.
  • Backups/var/lib/immich excluded from borg (CIFS originals snapshotted Hetzner-side, thumbs regenerable, DB already in the postgresql dump).

Deviation from the issue

The issue asked to pin a fixed users.users.immich.uid/gid for the CIFS mount. Instead the mount uses uid=immich,gid=immich, which mount.cifs resolves by name at mount time — same effect, no invented magic uid to risk colliding with web-arm's auto-allocated ids. (Discussed and approved.)

First-deploy / post-deploy notes

  • First deploy reuses the existing postgresql_14 instance and flips shared_preload_libraries for pgvector/vectorchord → Postgres restarts, briefly interrupting PowerSync/Grafana.
  • Verify after deploy (issue acceptance criteria): a group:immich member can log in (first login becomes admin), a non-member is denied at Authelia; a test upload lands in /var/lib/immich/library (CIFS) with its thumbnail under local /var/lib/immich/thumbs; journalctl -u immich-server shows no config-schema errors. The immich units run with PrivateMounts=true, so confirm the CIFS submount is visible/writable inside the service.

Verification

  • nix-instantiate dry-build (pre-commit hook) passes for all hosts, including web-arm.

Closes #178

## Summary Deploys Immich on **web-arm** via the native `services.immich` module (immich 2.7.5, nixpkgs 26.05), fronted at https://immich.cloonar.com with **OAuth-only login through Authelia**, restricted to members of the `immich` LDAP group. This is the thin end-to-end slice: a working, access-gated, empty Immich. ## What's included - **`hosts/web-arm/modules/immich/`** — native module on `127.0.0.1:2283` (nginx fronts it, firewall closed), its own redis instance, machine-learning on. - **Storage split** — originals (`library/`) on the Hetzner Storage-Box CIFS share; `thumbs/`/`upload/`/`profile/` stay on the local SSD under `/var/lib/immich`. - **Declarative config via sops template** — the OIDC client secret is injected at render time, so it never enters the Nix store. Password login off, transcoding off, ML on, heavy job queues throttled to concurrency 1. **All config keys were validated against the immich 2.7.5 schema** (oauth.*, passwordLogin, ffmpeg.transcode enum, machineLearning, job.<queue>.concurrency, newVersionCheck). - **CPU throttling** — `immich-server` / `immich-machine-learning` at `CPUWeight=20`; the websites keep the default 100, so under contention they get ~5x the CPU share. - **Authelia** — `immich` OIDC client + deny-by-default authorization policy gating on `group:immich`. - **Backups** — `/var/lib/immich` excluded from borg (CIFS originals snapshotted Hetzner-side, thumbs regenerable, DB already in the postgresql dump). ## Deviation from the issue The issue asked to pin a fixed `users.users.immich.uid`/`gid` for the CIFS mount. Instead the mount uses `uid=immich,gid=immich`, which `mount.cifs` resolves by name at mount time — same effect, no invented magic uid to risk colliding with web-arm's auto-allocated ids. (Discussed and approved.) ## First-deploy / post-deploy notes - First deploy reuses the existing `postgresql_14` instance and flips `shared_preload_libraries` for pgvector/vectorchord → **Postgres restarts**, briefly interrupting PowerSync/Grafana. - Verify after deploy (issue acceptance criteria): a group:immich member can log in (first login becomes admin), a non-member is denied at Authelia; a test upload lands in `/var/lib/immich/library` (CIFS) with its thumbnail under local `/var/lib/immich/thumbs`; `journalctl -u immich-server` shows no config-schema errors. The immich units run with `PrivateMounts=true`, so confirm the CIFS submount is visible/writable inside the service. ## Verification - `nix-instantiate` dry-build (pre-commit hook) passes for **all hosts**, including web-arm. Closes #178
Deploy Immich via the native services.immich module (immich 2.7.5,
nixpkgs 26.05), fronted at https://immich.cloonar.com with OAuth-only
login through Authelia, restricted to the immich LDAP group.

- hosts/web-arm/modules/immich: native module on loopback:2283 (nginx
  fronts it), own redis, machine-learning on. Originals (library/) live
  on the Hetzner Storage-Box CIFS share; thumbs/upload/profile stay on
  local SSD. CIFS maps ownership via uid=immich,gid=immich resolved by
  mount.cifs at mount time, so no uid pin is needed.
- System config rendered through a sops template so the OIDC client
  secret never enters the Nix store: password login off, transcoding
  off, ML on, heavy job queues throttled to concurrency 1. Keys
  validated against the immich 2.7.5 config schema.
- immich-server / immich-machine-learning run at CPUWeight=20 so the
  websites (default 100) keep ~5x the CPU share under contention.
- authelia: immich OIDC client plus a deny-by-default authorization
  policy gating on group:immich.
- borg: exclude /var/lib/immich (CIFS originals are snapshotted
  Hetzner-side, thumbs are regenerable, the DB is in the postgresql dump).

First deploy reuses the existing postgresql_14 instance and flips
shared_preload_libraries for pgvector/vectorchord, which restarts
Postgres and briefly interrupts PowerSync/Grafana.
Author
Owner

This was generated by AI while landing a PR.

Validation summary — /land-pr #181

Verdict: PASS (merge pending human free-text go-ahead).

Check Result
State / draft open, not a draft, mainafk/178, author dominik
Mergeable no conflicts
Verification signal pre-commit dry-build (eval) — the repo's commit-time gate; PR body asserts it passes for all hosts incl. web-arm. Relied on, not re-run.
Title Conventional Commits (feat(web-arm):)
secrets.yaml edits none (secrets added separately in 3e49ee6)
system.stateVersion unchanged
Module imports explicit path (./modules/immich)
Authelia client secret inline $pbkdf2-sha512$ digest — same pattern as the 6 existing OIDC clients; plaintext lives in sops
AFK contract head afk/178, body carries Closes #178

Diff review vs issue #178 intent — faithfully implements all seven sections: native immich service (loopback, firewall closed, own redis, DB reuse + pgvector), CIFS storage split for originals, sops-template config with the OIDC secret kept out of the store, CPUWeight=20 throttling, Authelia deny-by-default group:immich policy + OIDC client, nginx vhost, and the borg exclude. Also pulls in pkgs.cifs-utils for the mount. One documented, approved deviation: the CIFS mount uses uid=immich,gid=immich (name-resolved by mount.cifs) instead of a pinned uid/gid.

Caveats (out of scope at merge time, by design):

  • The repo gate is eval-only — it cannot validate the immich.json runtime config schema. The keys are asserted validated against immich 2.7.5; a bad key fails startup loudly. This is the issue's post-deploy journalctl -u immich-server acceptance check.
  • No derivation src/*Hash changed (native module + nixpkgs), so the eval gate's vendorHash blind spot doesn't apply here.
  • First deploy flips shared_preload_librariesPostgres restarts, briefly blipping PowerSync/Grafana (expected; called out in the PR).
  • CIFS submount writability under the immich units' PrivateMounts=true is a post-deploy verification step.
> *This was generated by AI while landing a PR.* ## Validation summary — `/land-pr` #181 **Verdict: PASS** (merge pending human free-text go-ahead). | Check | Result | |---|---| | State / draft | open, not a draft, `main` ← `afk/178`, author dominik | | Mergeable | ✅ no conflicts | | Verification signal | pre-commit **dry-build (eval)** — the repo's commit-time gate; PR body asserts it passes for all hosts incl. web-arm. **Relied on, not re-run.** | | Title | ✅ Conventional Commits (`feat(web-arm):`) | | `secrets.yaml` edits | ✅ none (secrets added separately in `3e49ee6`) | | `system.stateVersion` | ✅ unchanged | | Module imports | ✅ explicit path (`./modules/immich`) | | Authelia client secret | ✅ inline `$pbkdf2-sha512$` digest — same pattern as the 6 existing OIDC clients; plaintext lives in sops | | AFK contract | ✅ head `afk/178`, body carries `Closes #178` | **Diff review vs issue #178 intent** — faithfully implements all seven sections: native immich service (loopback, firewall closed, own redis, DB reuse + pgvector), CIFS storage split for originals, sops-template config with the OIDC secret kept out of the store, `CPUWeight=20` throttling, Authelia deny-by-default `group:immich` policy + OIDC client, nginx vhost, and the borg exclude. Also pulls in `pkgs.cifs-utils` for the mount. One **documented, approved** deviation: the CIFS mount uses `uid=immich,gid=immich` (name-resolved by `mount.cifs`) instead of a pinned uid/gid. **Caveats (out of scope at merge time, by design):** - The repo gate is **eval-only** — it cannot validate the `immich.json` runtime config schema. The keys are asserted validated against immich 2.7.5; a bad key fails startup loudly. This is the issue's post-deploy `journalctl -u immich-server` acceptance check. - No derivation `src`/`*Hash` changed (native module + nixpkgs), so the eval gate's vendorHash blind spot doesn't apply here. - First deploy flips `shared_preload_libraries` → **Postgres restarts**, briefly blipping PowerSync/Grafana (expected; called out in the PR). - CIFS submount writability under the immich units' `PrivateMounts=true` is a post-deploy verification step.
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Cloonar/nixos!181
No description provided.