Make admin seed idempotent-upsert so a corrupted password can be fixed by restart
Build and Deploy / version (push) Successful in 1s
Build and Deploy / build (push) Successful in 6s
Build and Deploy / deploy (push) Successful in 1s

Grund: $ in SEED_ADMIN_PASSWORD wird von Docker-Compose/Portainer als
Variablen-Referenz interpretiert und kann dadurch verstümmelt beim Container
ankommen. Seed synct das Passwort jetzt bei jedem Start neu, solange die
Variable gesetzt ist, statt nur einmalig anzulegen. README um Warnhinweis
ergänzt.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Fidelis Huber
2026-08-21 18:33:36 +02:00
co-authored by Claude Sonnet 5
parent 11a3753d76
commit ca5d240cf6
2 changed files with 23 additions and 13 deletions
+13 -6
View File
@@ -102,14 +102,21 @@ Benutzername anmelden". Damit das funktioniert, muss im Portainer-Stack `JWT_SEC
gesetzt sein (Schritt 4). gesetzt sein (Schritt 4).
Ein erstes Konto legt der Server **automatisch beim Start** an, wenn Ein erstes Konto legt der Server **automatisch beim Start** an, wenn
`SEED_ADMIN_USERNAME` und `SEED_ADMIN_PASSWORD` als Stack-Env gesetzt sind (idempotent `SEED_ADMIN_USERNAME` und `SEED_ADMIN_PASSWORD` als Stack-Env gesetzt sind (Upsert
läuft bei jedem Neustart mit, legt aber nur an, was noch nicht existiert). Das läuft bei jedem Neustart mit und synchronisiert das Passwort neu, solange die Variable
Klartext-Passwort steht dabei nur in der Portainer-Stack-Konfiguration, nie im Git-Repo gesetzt ist ein falsch übernommenes Passwort lässt sich also einfach per Neustart
der Server speichert ausschließlich einen bcrypt-Hash in der Datenbank. korrigieren). Das Klartext-Passwort steht dabei nur in der Portainer-Stack-Konfiguration,
nie im Git-Repo der Server speichert ausschließlich einen bcrypt-Hash in der Datenbank.
**Vorsicht bei `$` im Passwort:** Docker-Compose/Portainer interpretieren `$` in
Env-Werten als Beginn einer Variablen-Referenz (`$xyz` wird als `${xyz}` gelesen und bei
fehlender Definition oft still zu leer aufgelöst) ein Passwort mit `$` kann dadurch
unbemerkt anders ankommen, als eingetippt. Entweder jedes `$` im Passwortwert als `$$`
escapen, oder einfacher ein Passwort ohne `$` verwenden.
Optional danach `SEED_ADMIN_PASSWORD` aus dem Stack wieder entfernen (Härtung), sobald Optional danach `SEED_ADMIN_PASSWORD` aus dem Stack wieder entfernen (Härtung), sobald
das Konto einmal erfolgreich angelegt wurde `SEED_ADMIN_USERNAME` kann stehen bleiben, das Konto wie gewünscht funktioniert `SEED_ADMIN_USERNAME` kann stehen bleiben, ohne
ohne Passwort passiert dann nichts mehr. Weitere lokale Konten aktuell nur direkt in der Passwort-Variable wird nichts mehr verändert. Weitere lokale Konten aktuell nur direkt in der
Datenbank anlegbar (`INSERT INTO users …` mit einem bcrypt-Hash) ein Verwaltungs-UI Datenbank anlegbar (`INSERT INTO users …` mit einem bcrypt-Hash) ein Verwaltungs-UI
dafür gibt es noch nicht. dafür gibt es noch nicht.
+10 -7
View File
@@ -31,18 +31,21 @@ app.get(/^(?!\/api\/).*/, (_req, res) => {
res.sendFile(path.join(PUBLIC_DIR, 'index.html')); res.sendFile(path.join(PUBLIC_DIR, 'index.html'));
}); });
// Legt beim ersten Start ein lokales Fallback-Konto an, falls SEED_ADMIN_USERNAME/ // Legt beim Start ein lokales Fallback-Konto an bzw. hält es synchron, solange
// SEED_ADMIN_PASSWORD gesetzt sind und noch kein Konto mit diesem Namen existiert. // SEED_ADMIN_USERNAME/SEED_ADMIN_PASSWORD gesetzt sind (Upsert so lässt sich ein
// Idempotent kann bei jedem Neustart stehen bleiben, legt nichts doppelt an. // falsch übernommenes Passwort einfach per Neustart korrigieren). Danach die
// SEED_ADMIN_PASSWORD-Variable aus dem Stack entfernen, um das Konto "einzufrieren".
async function seedAdminIfConfigured() { async function seedAdminIfConfigured() {
const username = process.env.SEED_ADMIN_USERNAME; const username = process.env.SEED_ADMIN_USERNAME;
const password = process.env.SEED_ADMIN_PASSWORD; const password = process.env.SEED_ADMIN_PASSWORD;
if (!username || !password) return; if (!username || !password) return;
const existing = await prisma.user.findUnique({ where: { username } });
if (existing) return;
const passwordHash = await bcrypt.hash(password, 12); const passwordHash = await bcrypt.hash(password, 12);
await prisma.user.create({ data: { username, passwordHash, name: username } }); await prisma.user.upsert({
console.log(`Lokales Konto "${username}" angelegt.`); where: { username },
update: { passwordHash },
create: { username, passwordHash, name: username }
});
console.log(`Lokales Konto "${username}" angelegt/aktualisiert.`);
} }
seedAdminIfConfigured() seedAdminIfConfigured()