fix(migrations): catch-all backfill for all missing tables/columns

Every column + table added inside an already-passed PRAGMA user_version
entry is re-created here with IF NOT EXISTS / addColumnIfNotExists so
existing deploys finally pick them up:

  - kiosks: reported_hostname, network_interfaces_json
  - kiosks: os_update_channel, os_update_target_version, os_update_last_*
  - kiosks: managed_image, managed_config_*
  - displays: active_layout_id
  - os_update_releases table + indices
  - os_update_rollouts table + indices

Rule going forward: NEVER add columns/tables inside existing migration
entries. Always append a NEW entry at the end of the MIGRATIONS array.
This commit is contained in:
Mitchell R 2026-05-21 11:46:20 +02:00
parent 45c7ef26b2
commit 4c1edbd3b2
No known key found for this signature in database

View file

@ -900,4 +900,63 @@ export const MIGRATIONS: readonly MigrationEntry[] = [
) STRICT`, ) STRICT`,
`CREATE INDEX IF NOT EXISTS idx_kiosk_logs_kiosk_received ON kiosk_logs(kiosk_id, received_at DESC)`, `CREATE INDEX IF NOT EXISTS idx_kiosk_logs_kiosk_received ON kiosk_logs(kiosk_id, received_at DESC)`,
`CREATE INDEX IF NOT EXISTS idx_kiosk_logs_level ON kiosk_logs(level, received_at DESC)`, `CREATE INDEX IF NOT EXISTS idx_kiosk_logs_level ON kiosk_logs(level, received_at DESC)`,
// Catch-all backfill for tables/columns that were added inside earlier
// migration entries after existing deploys had already passed those
// indices via PRAGMA user_version. All IF NOT EXISTS / addColumnIfNotExists
// so they're safe to run on fresh DBs too.
(db: DatabaseSync) => {
// --- reported hostname + network interfaces (heartbeat telemetry) ---
addColumnIfNotExists(db, "kiosks", "reported_hostname", "TEXT");
addColumnIfNotExists(db, "kiosks", "network_interfaces_json", "TEXT");
// --- OS update per-kiosk prefs ---
addColumnIfNotExists(db, "kiosks", "os_update_channel", "TEXT NOT NULL DEFAULT 'stable'");
addColumnIfNotExists(db, "kiosks", "os_update_target_version", "TEXT");
addColumnIfNotExists(db, "kiosks", "os_update_last_attempt_at", "TEXT");
addColumnIfNotExists(db, "kiosks", "os_update_last_attempt_version", "TEXT");
addColumnIfNotExists(db, "kiosks", "os_update_last_error", "TEXT");
// --- OS update releases + rollouts tables ---
db.exec(`CREATE TABLE IF NOT EXISTS os_update_releases (
id TEXT PRIMARY KEY,
version TEXT NOT NULL,
channel TEXT NOT NULL CHECK(channel IN ('stable', 'beta', 'dev')),
compatibility TEXT NOT NULL,
artifact_path TEXT NOT NULL,
size_bytes INTEGER NOT NULL,
sha256 TEXT NOT NULL,
bundle_format TEXT NOT NULL DEFAULT 'raucb',
release_notes TEXT,
uploaded_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
uploaded_by INTEGER REFERENCES users(id) ON DELETE SET NULL,
yanked_at TEXT
) STRICT`);
db.exec(`CREATE UNIQUE INDEX IF NOT EXISTS idx_os_update_releases_version_compat ON os_update_releases(version, compatibility)`);
db.exec(`CREATE INDEX IF NOT EXISTS idx_os_update_releases_channel ON os_update_releases(channel, compatibility, uploaded_at DESC)`);
db.exec(`CREATE TABLE IF NOT EXISTS os_update_rollouts (
id TEXT PRIMARY KEY,
release_id TEXT NOT NULL REFERENCES os_update_releases(id) ON DELETE CASCADE,
target_kiosk_ids TEXT NOT NULL DEFAULT '[]',
state TEXT NOT NULL DEFAULT 'queued' CHECK(state IN ('queued', 'active', 'paused', 'complete')),
percentage INTEGER NOT NULL DEFAULT 100 CHECK(percentage BETWEEN 1 AND 100),
started_at TEXT,
finished_at TEXT,
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
created_by INTEGER REFERENCES users(id) ON DELETE SET NULL
) STRICT`);
db.exec(`CREATE INDEX IF NOT EXISTS idx_os_update_rollouts_state ON os_update_rollouts(state)`);
// --- managed-image config columns ---
addColumnIfNotExists(db, "kiosks", "managed_image", "INTEGER NOT NULL DEFAULT 0");
addColumnIfNotExists(db, "kiosks", "managed_config_json", "TEXT");
addColumnIfNotExists(db, "kiosks", "managed_config_version", "INTEGER NOT NULL DEFAULT 0");
addColumnIfNotExists(db, "kiosks", "managed_config_applied_version", "INTEGER NOT NULL DEFAULT 0");
addColumnIfNotExists(db, "kiosks", "managed_config_applied_at", "TEXT");
addColumnIfNotExists(db, "kiosks", "managed_config_error", "TEXT");
// --- display active layout ---
addColumnIfNotExists(db, "displays", "active_layout_id", "INTEGER REFERENCES layouts(id) ON DELETE SET NULL");
},
]; ];