From 50424fc57480ec86a0648c7389d2261984ecd09f Mon Sep 17 00:00:00 2001 From: jubnl Date: Wed, 1 Apr 2026 23:09:57 +0200 Subject: [PATCH] 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');