From 8236cde95482ca1b796359880e00a310c0868d04 Mon Sep 17 00:00:00 2001 From: Daniel Hiller Date: Thu, 17 Sep 2026 00:48:08 +0200 Subject: [PATCH] fix: Keep the seed from refusing to start an instance that already ran The validation added with the admin user checks reached further than intended. The seed runs on every container start, not only on the first, and it threw on three conditions that are normal on a second start. The worst was the check for an existing user: after the first start the default admin exists by definition, so every restart after that ended in a container that would not come up. The insert is wrapped in a try and falls back to an update precisely because it is meant to run again, and that path was never reached. The other two were narrower but the same shape. `validator.isEmail` rejects `admin@localhost`, which is a common value in self-hosted setups, so an instance that had been running for a year would stop coming up after the upgrade. None of it throws now. Bad input is reported on stderr and skipped, the way an invalid username already was, and a username that belongs to someone else is left alone rather than taken. `db:create-admin-user` keeps the strict validation, which is where a person is there to read it and type again. --- server/db/seeds/default.js | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/server/db/seeds/default.js b/server/db/seeds/default.js index 82828cdf..fd441d8b 100644 --- a/server/db/seeds/default.js +++ b/server/db/seeds/default.js @@ -62,34 +62,36 @@ exports.seed = async (knex) => { const defaultAdminEmail = process.env.DEFAULT_ADMIN_EMAIL && process.env.DEFAULT_ADMIN_EMAIL.toLowerCase(); - if (defaultAdminEmail) { - if (!validator.isEmail(defaultAdminEmail)) { - throw new Error( - `DEFAULT_ADMIN_EMAIL "${process.env.DEFAULT_ADMIN_EMAIL}" is not a valid e-mail address.`, - ); - } + // Nothing in here may throw. The seed runs on every start, not only on the + // first one, and a throw here leaves the container in a restart loop with an + // instance that used to come up fine. Bad input is reported and skipped; the + // interactive `db:create-admin-user` is where invalid input is refused. + const isDefaultAdminEmailUsable = + defaultAdminEmail && validator.isEmail(defaultAdminEmail) && defaultAdminEmail.length <= 256; - if (defaultAdminEmail.length > 256) { - throw new Error( - `DEFAULT_ADMIN_EMAIL "${process.env.DEFAULT_ADMIN_EMAIL}" exceeds 256 characters.`, - ); - } - - const existingEmailUser = await knex('user_account').where('email', defaultAdminEmail).first(); - - if (existingEmailUser) { - throw new Error(`User with DEFAULT_ADMIN_EMAIL "${defaultAdminEmail}" already exists.`); - } + if (defaultAdminEmail && !isDefaultAdminEmailUsable) { + console.warn( + `Warning: DEFAULT_ADMIN_EMAIL "${process.env.DEFAULT_ADMIN_EMAIL}" is not a usable e-mail address; skipping the default admin user.`, + ); + } + if (isDefaultAdminEmailUsable) { const userData = buildUserData(); if (userData.username) { const existingUsernameUser = await knex('user_account') .where('username', userData.username) + .whereNot('email', defaultAdminEmail) .first(); + // Taken by somebody else, so it cannot be applied. The account itself is + // still created or refreshed, it simply keeps the username it has. if (existingUsernameUser) { - throw new Error(`User with DEFAULT_ADMIN_USERNAME "${userData.username}" already exists.`); + console.warn( + `Warning: DEFAULT_ADMIN_USERNAME "${userData.username}" belongs to another user; leaving the username unchanged.`, + ); + + delete userData.username; } }