Fix migration runner crashing on a genuinely fresh database
Build and push server image / build-and-push (push) Successful in 39s

_migration_1() is Base.metadata.create_all() -- it already builds
today's full schema straight from models.py. Every migration after it
is an incremental ALTER/UPDATE meant to bring an *existing* install
forward from an older version; replaying them against a brand-new
database collided with columns create_all had already added ("duplicate
column name"), crashing on first boot.

Found while testing the device-status-bar change against a scratch DB.
Every real deployment has been migrating forward incrementally since
before this bug existed, so it never showed up in practice -- but any
brand-new install would have hit it. Fresh databases now jump straight
to the latest schema_version after create_all; existing databases keep
applying whichever migrations are still pending, same as before.
This commit is contained in:
2026-07-22 10:48:36 -04:00
parent 60fcfca4a0
commit 55b53d5bb2
+17 -11
View File
@@ -94,17 +94,23 @@ def run_migrations() -> None:
with engine.begin() as conn:
conn.execute(text("CREATE TABLE IF NOT EXISTS schema_version (version INTEGER NOT NULL)"))
row = conn.execute(text("SELECT version FROM schema_version")).fetchone()
current = row[0] if row else 0
for version, fn in MIGRATIONS:
if version > current:
logger.info("Applying schema migration %d", version)
fn(conn)
if row is None:
conn.execute(
text("INSERT INTO schema_version (version) VALUES (:v)"), {"v": version}
)
row = (version,)
else:
if row is None:
# Brand new database: _migration_1's create_all() already
# produces today's full schema straight from models.py.
# Every migration after it is an incremental ALTER/UPDATE
# meant to bring an *existing* install forward -- replaying
# those here would just collide with columns create_all
# already added (e.g. "duplicate column name"). Jump
# straight to the latest version instead.
_migration_1(conn)
latest = MIGRATIONS[-1][0]
conn.execute(text("INSERT INTO schema_version (version) VALUES (:v)"), {"v": latest})
else:
current = row[0]
for version, fn in MIGRATIONS:
if version > current:
logger.info("Applying schema migration %d", version)
fn(conn)
conn.execute(text("UPDATE schema_version SET version = :v"), {"v": version})
_ensure_frame_one()
_ensure_server_settings()