From b99ceb26ede753a75a15923d3c3b56823e3f0966 Mon Sep 17 00:00:00 2001 From: maziggy Date: Fri, 24 Apr 2026 11:07:55 +0200 Subject: [PATCH] fix(db): dedupe legacy settings rows and add missing UNIQUE(key) index MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Legacy SQLite installs created the `settings` table without a UNIQUE constraint on `key`. The seed loop's `INSERT OR IGNORE` silently degraded to a plain INSERT, so every `systemctl restart` added another row of `advanced_auth_enabled` / `smtp_auth_enabled`. After a handful of restarts, `scalar_one_or_none()` in is_advanced_auth_enabled() and similar sites blew up with `MultipleResultsFound`, 500'ing the login flow. Run-migrations now deletes dup rows (keeping MIN(id) per key) and creates the missing `ix_settings_key` unique index before the seed loop. Both ops are idempotent — fresh installs and Postgres already have the index, so they no-op. --- backend/app/core/database.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/backend/app/core/database.py b/backend/app/core/database.py index 13600ff64..5cc855907 100644 --- a/backend/app/core/database.py +++ b/backend/app/core/database.py @@ -1530,6 +1530,17 @@ async def run_migrations(conn): "CREATE INDEX IF NOT EXISTS ix_library_files_deleted_at ON library_files(deleted_at)", ) + # Legacy SQLite installs created `settings` without a UNIQUE constraint on `key`, + # so `INSERT OR IGNORE` below silently degrades to a plain INSERT and dupes rows on + # every restart. Dedupe (keep lowest id per key) and add the missing unique index + # before seeding. Safe/idempotent on both dialects — fresh installs already have + # no dupes and `create_all` already emits the index. + await _safe_execute( + conn, + "DELETE FROM settings WHERE id NOT IN (SELECT MIN(id) FROM settings GROUP BY key)", + ) + await _safe_execute(conn, "CREATE UNIQUE INDEX IF NOT EXISTS ix_settings_key ON settings(key)") + # Seed default settings keys that must exist on fresh install default_settings = [ ("advanced_auth_enabled", "false"),