From 50424fc57480ec86a0648c7389d2261984ecd09f Mon Sep 17 00:00:00 2001 From: jubnl Date: Wed, 1 Apr 2026 23:09:57 +0200 Subject: [PATCH 1/6] feat: support ADMIN_EMAIL and ADMIN_PASSWORD env vars for initial admin setup Allow the first-boot admin account to be configured via ADMIN_EMAIL and ADMIN_PASSWORD environment variables. If both are set the account is created with those credentials; otherwise the existing random-password fallback is used. Documented across .env.example, docker-compose.yml, Helm chart (values.yaml, secret.yaml, deployment.yaml), and CLAUDE.md. Co-Authored-By: Claude Sonnet 4.6 --- chart/templates/deployment.yaml | 12 ++++++++++++ chart/templates/secret.yaml | 12 ++++++++++++ chart/values.yaml | 5 +++++ docker-compose.yml | 2 ++ server/.env.example | 6 ++++++ server/src/db/seeds.ts | 16 ++++++++++++++-- 6 files changed, 51 insertions(+), 2 deletions(-) diff --git a/chart/templates/deployment.yaml b/chart/templates/deployment.yaml index df5884b..2f0cdb8 100644 --- a/chart/templates/deployment.yaml +++ b/chart/templates/deployment.yaml @@ -42,6 +42,18 @@ spec: name: {{ default (printf "%s-secret" (include "trek.fullname" .)) .Values.existingSecret }} key: {{ .Values.existingSecretKey | default "ENCRYPTION_KEY" }} optional: true + - name: ADMIN_EMAIL + valueFrom: + secretKeyRef: + name: {{ default (printf "%s-secret" (include "trek.fullname" .)) .Values.existingSecret }} + key: ADMIN_EMAIL + optional: true + - name: ADMIN_PASSWORD + valueFrom: + secretKeyRef: + name: {{ default (printf "%s-secret" (include "trek.fullname" .)) .Values.existingSecret }} + key: ADMIN_PASSWORD + optional: true volumeMounts: - name: data mountPath: /app/data diff --git a/chart/templates/secret.yaml b/chart/templates/secret.yaml index 204e91c..a205f8f 100644 --- a/chart/templates/secret.yaml +++ b/chart/templates/secret.yaml @@ -8,6 +8,12 @@ metadata: type: Opaque data: {{ .Values.existingSecretKey | default "ENCRYPTION_KEY" }}: {{ .Values.secretEnv.ENCRYPTION_KEY | b64enc | quote }} + {{- if .Values.secretEnv.ADMIN_EMAIL }} + ADMIN_EMAIL: {{ .Values.secretEnv.ADMIN_EMAIL | b64enc | quote }} + {{- end }} + {{- if .Values.secretEnv.ADMIN_PASSWORD }} + ADMIN_PASSWORD: {{ .Values.secretEnv.ADMIN_PASSWORD | b64enc | quote }} + {{- end }} {{- end }} {{- if and (not .Values.existingSecret) (.Values.generateEncryptionKey) }} @@ -26,4 +32,10 @@ stringData: {{- else }} {{ .Values.existingSecretKey | default "ENCRYPTION_KEY" }}: {{ randAlphaNum 32 }} {{- end }} + {{- if .Values.secretEnv.ADMIN_EMAIL }} + ADMIN_EMAIL: {{ .Values.secretEnv.ADMIN_EMAIL }} + {{- end }} + {{- if .Values.secretEnv.ADMIN_PASSWORD }} + ADMIN_PASSWORD: {{ .Values.secretEnv.ADMIN_PASSWORD }} + {{- end }} {{- end }} diff --git a/chart/values.yaml b/chart/values.yaml index 2f82f4f..bee6c50 100644 --- a/chart/values.yaml +++ b/chart/values.yaml @@ -36,6 +36,11 @@ secretEnv: # 1. data/.jwt_secret (existing installs — encrypted data stays readable after upgrade) # 2. data/.encryption_key auto-generated on first start (fresh installs) ENCRYPTION_KEY: "" + # Initial admin account — only used on first boot when no users exist yet. + # If both values are non-empty the admin account is created with these credentials. + # If either is empty a random password is generated and printed to the server log. + ADMIN_EMAIL: "" + ADMIN_PASSWORD: "" # If true, a random ENCRYPTION_KEY is generated at install and preserved across upgrades generateEncryptionKey: false diff --git a/docker-compose.yml b/docker-compose.yml index 731a77c..397f7ef 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -32,6 +32,8 @@ services: # - OIDC_DISPLAY_NAME=SSO # Label shown on the SSO login button # - OIDC_ONLY=false # Set true to disable local password auth entirely (SSO only) # - OIDC_DISCOVERY_URL= # Override the OIDC discovery endpoint for providers with non-standard paths (e.g. Authentik) +# - ADMIN_EMAIL=admin@trek.local # Initial admin e-mail — only used on first boot when no users exist +# - ADMIN_PASSWORD=changeme # Initial admin password — only used on first boot when no users exist volumes: - ./data:/app/data - ./uploads:/app/uploads diff --git a/server/.env.example b/server/.env.example index 35be0dd..049468c 100644 --- a/server/.env.example +++ b/server/.env.example @@ -26,3 +26,9 @@ OIDC_ADMIN_VALUE=app-trek-admins # Value of the OIDC claim that grants admin rol OIDC_DISCOVERY_URL= # Override the auto-constructed OIDC discovery endpoint. Useful for providers (e.g. Authentik) that expose it at a non-standard path. Example: https://auth.example.com/application/o/trek/.well-known/openid-configuration DEMO_MODE=false # Demo mode - resets data hourly + +# Initial admin account — only used on first boot when no users exist yet. +# If both are set the admin account is created with these credentials. +# If either is omitted a random password is generated and printed to the server log. +# ADMIN_EMAIL=admin@trek.local +# ADMIN_PASSWORD=changeme diff --git a/server/src/db/seeds.ts b/server/src/db/seeds.ts index 248d10a..8e0d9c6 100644 --- a/server/src/db/seeds.ts +++ b/server/src/db/seeds.ts @@ -22,9 +22,21 @@ function seedAdminAccount(db: Database.Database): void { } const bcrypt = require('bcryptjs'); - const password = crypto.randomBytes(12).toString('base64url'); + + const env_admin_email = process.env.ADMIN_EMAIL; + const env_admin_pw = process.env.ADMIN_PASSWORD; + + let password; + let email; + if (env_admin_email && env_admin_pw) { + password = env_admin_pw; + email = env_admin_email; + } else { + password = crypto.randomBytes(12).toString('base64url'); + email = 'admin@trek.local'; + } + const hash = bcrypt.hashSync(password, 12); - const email = 'admin@trek.local'; const username = 'admin'; db.prepare('INSERT INTO users (username, email, password_hash, role, must_change_password) VALUES (?, ?, ?, ?, 1)').run(username, email, hash, 'admin'); From b1cca15f6f7c151061f8394fc57e4debda3a1bfa Mon Sep 17 00:00:00 2001 From: jubnl Date: Wed, 1 Apr 2026 23:22:18 +0200 Subject: [PATCH 2/6] docs: add ADMIN_EMAIL and ADMIN_PASSWORD to README env vars table and compose snippet Co-Authored-By: Claude Sonnet 4.6 --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index bdd5e3f..3110a60 100644 --- a/README.md +++ b/README.md @@ -156,6 +156,8 @@ services: # - OIDC_ADMIN_VALUE=app-trek-admins # Value of the OIDC claim that grants admin role # - OIDC_DISCOVERY_URL= # Override the OIDC discovery endpoint for providers with non-standard paths (e.g. Authentik) # - DEMO_MODE=false # Enable demo mode (resets data hourly) + # - ADMIN_EMAIL=admin@trek.local # Initial admin e-mail — only used on first boot when no users exist + # - ADMIN_PASSWORD=changeme # Initial admin password — only used on first boot when no users exist volumes: - ./data:/app/data - ./uploads:/app/uploads @@ -288,6 +290,9 @@ trek.yourdomain.com { | `OIDC_DISPLAY_NAME` | Label shown on the SSO login button | `SSO` | | `OIDC_ONLY` | Disable local password auth entirely (first SSO login becomes admin) | `false` | | `OIDC_DISCOVERY_URL` | Override the auto-constructed OIDC discovery endpoint. Useful for providers that expose it at a non-standard path (e.g. Authentik: `https://auth.example.com/application/o/trek/.well-known/openid-configuration`) | — | +| **Initial Setup** | | | +| `ADMIN_EMAIL` | Email for the first admin account created on initial boot. Must be set together with `ADMIN_PASSWORD`. If either is omitted a random password is generated and printed to the server log. Has no effect once any user exists. | `admin@trek.local` | +| `ADMIN_PASSWORD` | Password for the first admin account created on initial boot. Must be set together with `ADMIN_EMAIL`. | random | | **Other** | | | | `DEMO_MODE` | Enable demo mode (hourly data resets) | `false` | From 32b63adc687e26f926ba6a7d48c58c3aa33e2f95 Mon Sep 17 00:00:00 2001 From: jubnl Date: Thu, 2 Apr 2026 07:46:27 +0200 Subject: [PATCH 3/6] fix: add OIDC_SCOPE env var and document it across all config files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #306 — OIDC scopes were hardcoded to 'openid email profile', causing OIDC_ADMIN_CLAIM-based role mapping to fail when the required scope (e.g. 'groups') wasn't requested. The new OIDC_SCOPE variable defaults to 'openid email profile groups' so group-based admin mapping works out of the box. Variable is now documented in README, docker-compose, .env.example, and the Helm chart values. --- README.md | 4 ++++ chart/values.yaml | 2 ++ docker-compose.yml | 3 +++ server/.env.example | 1 + server/src/routes/oidc.ts | 2 +- 5 files changed, 11 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3110a60..572d849 100644 --- a/README.md +++ b/README.md @@ -154,6 +154,7 @@ services: # - OIDC_ONLY=false # Set to true to disable local password auth entirely (SSO only) # - OIDC_ADMIN_CLAIM=groups # OIDC claim used to identify admin users # - OIDC_ADMIN_VALUE=app-trek-admins # Value of the OIDC claim that grants admin role + # - OIDC_SCOPE=openid email profile groups # Space-separated OIDC scopes to request (must include scopes for any claim used by OIDC_ADMIN_CLAIM) # - OIDC_DISCOVERY_URL= # Override the OIDC discovery endpoint for providers with non-standard paths (e.g. Authentik) # - DEMO_MODE=false # Enable demo mode (resets data hourly) # - ADMIN_EMAIL=admin@trek.local # Initial admin e-mail — only used on first boot when no users exist @@ -289,6 +290,9 @@ trek.yourdomain.com { | `OIDC_CLIENT_SECRET` | OIDC client secret | — | | `OIDC_DISPLAY_NAME` | Label shown on the SSO login button | `SSO` | | `OIDC_ONLY` | Disable local password auth entirely (first SSO login becomes admin) | `false` | +| `OIDC_ADMIN_CLAIM` | OIDC claim used to identify admin users | — | +| `OIDC_ADMIN_VALUE` | Value of the OIDC claim that grants admin role | — | +| `OIDC_SCOPE` | Space-separated OIDC scopes to request. Must include scopes for any claim used by `OIDC_ADMIN_CLAIM` (e.g. add `groups` for group-based admin mapping) | `openid email profile groups` | | `OIDC_DISCOVERY_URL` | Override the auto-constructed OIDC discovery endpoint. Useful for providers that expose it at a non-standard path (e.g. Authentik: `https://auth.example.com/application/o/trek/.well-known/openid-configuration`) | — | | **Initial Setup** | | | | `ADMIN_EMAIL` | Email for the first admin account created on initial boot. Must be set together with `ADMIN_PASSWORD`. If either is omitted a random password is generated and printed to the server log. Has no effect once any user exists. | `admin@trek.local` | diff --git a/chart/values.yaml b/chart/values.yaml index bee6c50..471dafa 100644 --- a/chart/values.yaml +++ b/chart/values.yaml @@ -24,6 +24,8 @@ env: # Set to "false" to allow session cookies over plain HTTP (e.g. no ingress TLS). Not recommended for production. # OIDC_DISCOVERY_URL: "" # Override the OIDC discovery endpoint for providers with non-standard paths (e.g. Authentik). + # OIDC_SCOPE: "openid email profile groups" + # Space-separated OIDC scopes to request. Must include scopes for any claim used by OIDC_ADMIN_CLAIM. # Secret environment variables stored in a Kubernetes Secret. diff --git a/docker-compose.yml b/docker-compose.yml index 397f7ef..768f73f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -31,6 +31,9 @@ services: # - OIDC_CLIENT_SECRET=supersecret # OpenID Connect client secret # - OIDC_DISPLAY_NAME=SSO # Label shown on the SSO login button # - OIDC_ONLY=false # Set true to disable local password auth entirely (SSO only) +# - OIDC_ADMIN_CLAIM=groups # OIDC claim used to identify admin users +# - OIDC_ADMIN_VALUE=app-trek-admins # Value of the OIDC claim that grants admin role +# - OIDC_SCOPE=openid email profile groups # Space-separated OIDC scopes to request (must include scopes for any claim used by OIDC_ADMIN_CLAIM) # - OIDC_DISCOVERY_URL= # Override the OIDC discovery endpoint for providers with non-standard paths (e.g. Authentik) # - ADMIN_EMAIL=admin@trek.local # Initial admin e-mail — only used on first boot when no users exist # - ADMIN_PASSWORD=changeme # Initial admin password — only used on first boot when no users exist diff --git a/server/.env.example b/server/.env.example index 049468c..0e1f64d 100644 --- a/server/.env.example +++ b/server/.env.example @@ -24,6 +24,7 @@ OIDC_ONLY=true # Disable local password auth entirely (SSO only) OIDC_ADMIN_CLAIM=groups # OIDC claim used to identify admin users OIDC_ADMIN_VALUE=app-trek-admins # Value of the OIDC claim that grants admin role OIDC_DISCOVERY_URL= # Override the auto-constructed OIDC discovery endpoint. Useful for providers (e.g. Authentik) that expose it at a non-standard path. Example: https://auth.example.com/application/o/trek/.well-known/openid-configuration +OIDC_SCOPE=openid email profile groups # Space-separated OIDC scopes to request (must include scopes for any claim used by OIDC_ADMIN_CLAIM) DEMO_MODE=false # Demo mode - resets data hourly diff --git a/server/src/routes/oidc.ts b/server/src/routes/oidc.ts index 8ba8c23..77b9b94 100644 --- a/server/src/routes/oidc.ts +++ b/server/src/routes/oidc.ts @@ -138,7 +138,7 @@ router.get('/login', async (req: Request, res: Response) => { response_type: 'code', client_id: config.clientId, redirect_uri: redirectUri, - scope: 'openid email profile', + scope: process.env.OIDC_SCOPE || 'openid email profile groups', state, }); From 45e0c7e546a7cdc8558875b1d1cc901727593f21 Mon Sep 17 00:00:00 2001 From: jubnl Date: Thu, 2 Apr 2026 13:58:35 +0200 Subject: [PATCH 4/6] fix: replace toast.warn with toast.warning in Immich save handler toast.warn does not exist in the toast library; calling it threw an error that was caught and displayed as "Could not connect to Immich" even when the save succeeded. Fixes #309. --- client/src/pages/SettingsPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/pages/SettingsPage.tsx b/client/src/pages/SettingsPage.tsx index 853b04f..fbc9e8b 100644 --- a/client/src/pages/SettingsPage.tsx +++ b/client/src/pages/SettingsPage.tsx @@ -148,7 +148,7 @@ export default function SettingsPage(): React.ReactElement { setSaving(s => ({ ...s, immich: true })) try { const saveRes = await apiClient.put('/integrations/immich/settings', { immich_url: immichUrl, immich_api_key: immichApiKey || undefined }) - if (saveRes.data.warning) toast.warn(saveRes.data.warning) + if (saveRes.data.warning) toast.warning(saveRes.data.warning) toast.success(t('memories.saved')) const res = await apiClient.get('/integrations/immich/status') setImmichConnected(res.data.connected) From c944a7d1019cd48f90d09415e46947304acb306a Mon Sep 17 00:00:00 2001 From: jubnl Date: Thu, 2 Apr 2026 14:05:15 +0200 Subject: [PATCH 5/6] fix: allow unauthenticated access to public share links Skip loadUser() and exclude /shared/ from the 401 redirect interceptor so unauthenticated users can open shared trip links without being redirected to /login. Fixes #308. --- client/src/App.tsx | 4 +++- client/src/api/client.ts | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/client/src/App.tsx b/client/src/App.tsx index 0235276..46e51a1 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -79,7 +79,9 @@ export default function App() { const { loadSettings } = useSettingsStore() useEffect(() => { - loadUser() + if (!location.pathname.startsWith('/shared/')) { + loadUser() + } authApi.getAppConfig().then(async (config: { demo_mode?: boolean; has_maps_key?: boolean; version?: string; timezone?: string; require_mfa?: boolean; trip_reminders_enabled?: boolean; permissions?: Record }) => { if (config?.demo_mode) setDemoMode(true) if (config?.has_maps_key !== undefined) setHasMapsKey(config.has_maps_key) diff --git a/client/src/api/client.ts b/client/src/api/client.ts index a901a5b..facf652 100644 --- a/client/src/api/client.ts +++ b/client/src/api/client.ts @@ -26,7 +26,7 @@ apiClient.interceptors.response.use( (response) => response, (error) => { if (error.response?.status === 401 && (error.response?.data as { code?: string } | undefined)?.code === 'AUTH_REQUIRED') { - if (!window.location.pathname.includes('/login') && !window.location.pathname.includes('/register')) { + if (!window.location.pathname.includes('/login') && !window.location.pathname.includes('/register') && !window.location.pathname.startsWith('/shared/')) { window.location.href = '/login' } } From a4d6348a799caea31e98fc7b8c8f13e3adbd3b2b Mon Sep 17 00:00:00 2001 From: jubnl Date: Thu, 2 Apr 2026 14:09:55 +0200 Subject: [PATCH 6/6] fix: add raw.githubusercontent.com to CSP connect-src for Atlas map The Atlas feature fetches country GeoJSON from GitHub raw content, which was blocked by the Content Security Policy connect-src directive. Closes #285 --- server/src/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/server/src/index.ts b/server/src/index.ts index c43faf5..5508542 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -66,6 +66,7 @@ app.use(helmet({ "https://*.basemaps.cartocdn.com", "https://*.tile.openstreetmap.org", "https://unpkg.com", "https://open-meteo.com", "https://api.open-meteo.com", "https://geocoding-api.open-meteo.com", "https://api.exchangerate-api.com", + "https://raw.githubusercontent.com/nvkelso/natural-earth-vector/master/geojson/ne_50m_admin_0_countries.geojson" ], fontSrc: ["'self'", "https://fonts.gstatic.com", "data:"], objectSrc: ["'none'"],