From a4c7768625f427f3fd93f9a140d288cf51162990 Mon Sep 17 00:00:00 2001 From: Erik Simon Date: Thu, 23 Jul 2026 23:14:11 +0200 Subject: [PATCH] immich: fix OIDC clientId, redirect logout to immich's own login page clientId was still the placeholder "immich" instead of Authentik's actual generated id, and the sops secret it points at (immich_oauth_client_secret) was never declared on jupiter. Wire both up, and set endSessionEndpoint so logout lands back on immich instead of Authentik's "logged out" page. --- hosts/jupiter/secrets.nix | 8 +++++ secrets/jupiter.yaml | 5 ++-- services/media/immich.nix | 62 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 72 insertions(+), 3 deletions(-) diff --git a/hosts/jupiter/secrets.nix b/hosts/jupiter/secrets.nix index b3758b0..f71991b 100644 --- a/hosts/jupiter/secrets.nix +++ b/hosts/jupiter/secrets.nix @@ -26,4 +26,12 @@ # Headscale pre-auth key for tailscale auto-registration (see configuration.nix). sops.secrets.tailscale_authkey = { }; + # Immich's OIDC client secret, from its Authentik application (a SEPARATE + # app from headscale's and headplane's — see hosts/neptun/secrets.nix). + # Referenced as settings.oauth.clientSecret._secret in + # services/media/immich.nix; the module resolves it through systemd + # LoadCredential, which reads as root before dropping privileges, so the + # sops default of root:root 0400 is correct — do NOT set `owner`. + sops.secrets.immich_oauth_client_secret = { }; + } diff --git a/secrets/jupiter.yaml b/secrets/jupiter.yaml index a50e7e9..76c18e9 100644 --- a/secrets/jupiter.yaml +++ b/secrets/jupiter.yaml @@ -5,6 +5,7 @@ mediamanager_token_secret: ENC[AES256_GCM,data:g75vj1E6B029O076yV3DS/1z99Tq6wMhE sabnzbd_api_key: ENC[AES256_GCM,data:6UW1u2Ikmnq34t4H4k/4C44SJeFHRlaPjWwUjEfH1GQ=,iv:sGsd8Sd2pfUhTUDg6PlRzfVYejRbF69jmDTIa2fvY4M=,tag:3fOLgU1K1gKHxQ3J1+3oRQ==,type:str] prowlarr_api_key: ENC[AES256_GCM,data:ab1QACaagI8ACJXFUy8r9X9lgYwRo1byUmhBPSRWwKk=,iv:EcuF6EN/4mWxlXi6R1qDzv4rOw6AT+OGSNQaaBwjJHg=,tag:hixHrbQWU6QQZNMM5rNDsg==,type:str] cinephage_better_auth_secret: ENC[AES256_GCM,data:S1ilcQeC2HmXe/4xdLi6wm5RNz954SL3qVur6JCn5ekBVCbXMd1DGCafjhU=,iv:9rS5gDuazMOAq/hWp0onvHZPzKJgQM3oWIrtplJN/9I=,tag:xNqf/unY2v/98p4v52vUqw==,type:str] +immich_oauth_client_secret: ENC[AES256_GCM,data:+NbUnwImwFTYNjz3luzczpCf7oMetzYBkj5ZnuG2QQf0Wpm6OtYS3amTC8dwoh9F/DAos5224etncfEgEu2k2iMUACLADnlCGppIx0F7Gl1Ve7UF2VzKJ3xQpgCDrXklU+o5NxfU/YBn1Vfa3580wT3tr2++SCSrcKq1XGtfhv4=,iv:tjaPDQbrA6TxsDebgNOtO/ITfXzU5wTKU9SkfC0TQcY=,tag:AnlJR0tLExkB7Aeo/ZVTng==,type:str] sops: age: - enc: | @@ -25,7 +26,7 @@ sops: CzjSDQZTcseEXZNwuzZcfB5Mvq0BQvjOj7lGuxzuE4qwWkdJWGfVLQ== -----END AGE ENCRYPTED FILE----- recipient: age1zak7glavmg4026p2389fyqe769vqm4jrryknuqckgqq4merz5f7q44rkkt - lastmodified: "2026-07-20T18:42:14Z" - mac: ENC[AES256_GCM,data:l9O/KAd/5KfC+LXwE2y5+ybflyy9Xl0PikoRfcOzIFu+fVWESyi0D22emWen6J4mpvsXgjAp4GCu4RKNTGyy47mfkaHh/KdxozEOmP8VlKOJXtqzU9lTgZwhHqkF9RKsRLKHvmREugwzWnpplJ8J7bg5Z3llBIM36FlElzQ9hUw=,iv:x0lM9Ctcn1XCsXftB9Ze8WV8Rp4YA26MQ/AdGC78MyY=,tag:gtfaSDQAcAkngRw2Ph2N0A==,type:str] + lastmodified: "2026-07-20T23:19:56Z" + mac: ENC[AES256_GCM,data:rxpctVvQPZF5+ylUgg/5UVdI4MfkSf5W2S1aSR1y5Na1juebM0+1kPGkyZxybm/hPl42DjiOmXsJbGS/Kf3np/y3vcYGpooDqxqGciYZDLcijd0M3ErODbfLgxgtGIl5nBS6cGUnabqgXRVTJwmupE7WllcIk6QM20PhnIAHzVU=,iv:I33VvRBp8iv16pRw0PA9+WiIOITPHoziwQC24x/rDh0=,tag:0gMQlEd5CzoAeDlUx99lBg==,type:str] unencrypted_suffix: _unencrypted version: 3.13.1 diff --git a/services/media/immich.nix b/services/media/immich.nix index 4b072bf..e3b8041 100644 --- a/services/media/immich.nix +++ b/services/media/immich.nix @@ -1,4 +1,4 @@ -{ pkgs, inputs, ... }: +{ config, pkgs, inputs, ... }: # Immich photo/video library. Native nixpkgs module (not the upstream compose # stack) — it owns its own postgres (with the pgvector + vectorchord extensions @@ -41,6 +41,66 @@ in port = 2283; mediaLocation = "/mnt/data/AppData/immich"; machine-learning.enable = true; + + # ⚠️ Setting `settings` at all switches immich to IMMICH_CONFIG_FILE, and + # that is ALL-OR-NOTHING (dist/utils/config.js: the config is + # `configFile ? loadFromFile(...) : metadataRepo.get(SystemConfig)` — the + # database copy is IGNORED, not merged). Two consequences: + # 1. Anything not declared here falls back to immich's DEFAULTS, not to + # whatever the admin UI had. The old settings stay in the + # system_metadata table, so deleting this block restores them. + # 2. The admin settings UI goes read-only — saving throws "Cannot update + # configuration while IMMICH_CONFIG_FILE is in use". Change settings + # HERE and redeploy. + # An unknown/misspelled key is a HARD startup failure under a config file + # (the same code path only logs a warning without one), so keys below are + # taken verbatim from `defaults` in immich's dist/config.js. + settings = { + server.externalDomain = "https://immich.mgaction.town"; + newVersionCheck.enabled = false; # nixpkgs pins the version, not immich + + # OIDC via Authentik on neptun. The Authentik application/provider is + # created BY HAND in its UI — same as headscale's and headplane's, which + # are also separate apps (hosts/neptun/secrets.nix). Only the client + # secret is managed here. + oauth = { + enabled = true; + # Authentik's per-application issuer. Trailing slash matters: immich + # appends /.well-known/openid-configuration to it. + issuerUrl = "https://auth.mgaction.town/application/o/immich/"; + # Authentik generates this; not a secret (it is sent in the browser + # redirect), so it lives here rather than in sops. + clientId = "FxrkWFe8keBrN83tu03UbAshSck52he2GphJDYSQ"; + # Read at runtime via systemd LoadCredential — never lands in the + # world-readable /nix/store copy of the rendered config. + clientSecret._secret = config.sops.secrets.immich_oauth_client_secret.path; + scope = "openid email profile"; + buttonText = "Login with Authentik"; + # Existing accounts (the 2 imported users) keep working: matching is by + # email, so an Authentik user with the same address adopts that account + # rather than creating a second one. + autoRegister = true; + # Leave the password form reachable — autoLaunch would bounce straight + # to Authentik, locking everyone out if the OIDC app is misconfigured. + autoLaunch = false; + # Land back on immich's own login page after logout. Without this, + # immich falls back to the IdP's discovered end_session_endpoint + # (auth.service.js:320-326) and logout dumps you on Authentik's + # "you've been logged out" page instead. Must be an ABSOLUTE url — + # the config schema rejects a relative path — and mirrors immich's + # internal LOGIN_URL, including autoLaunch=0. + # + # Note this ends the IMMICH session only; the Authentik SSO session + # survives, so the next "Login with Authentik" click signs straight + # back in without a credential prompt. To end both, drop this line and + # let the IdP endpoint take over again. + endSessionEndpoint = "https://auth.mgaction.town/application/o/immich/end-session?post_logout_redirect_url=https://immich.mgaction.town"; + # The mobile app can't follow a browser redirect back to a custom + # scheme through Authentik, so immich bounces it via this endpoint. + mobileOverrideEnabled = true; + mobileRedirectUri = "https://immich.mgaction.town/api/oauth/mobile-redirect"; + }; + }; # 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