fix(restore): preserve operator-meta settings across restore
Closes the chicken-and-egg where `restore_allow_force` (and its
auto-upgrade tracking flag) got overwritten on every restore by
whatever value happened to be in the backup. Net effect:
1. Admin enables Force Restore (via tonight's default-ON migration
edit, or hand-SQL on older installs).
2. Restore runs successfully.
3. Restored DB has `restore_allow_force = <backup's old value>`.
4. Next restore attempt: "Force restore is not allowed by system
settings" — admin needs the SQL workaround AGAIN.
Cure: snapshot a small list of operator-meta keys BEFORE the DROP
DATABASE (while we still have a working pool against the OLD DB),
then UPSERT them back AFTER the psql restore + migrate.latest.
The preserved set is intentionally narrow — currently just
`restore_allow_force` and `restore_allow_force_auto_upgraded`. These
are about how the operator wants the install to behave, not user-
facing state. Adding more keys is a one-line addition to the
PRESERVED_META_KEYS constant.
Survives both:
- backup is OLDER than the operator's most recent setting change
- backup is NEWER but had a different operator policy
Either way, the post-restore install reflects the LIVE operator
policy, not the backup's snapshot of it.
This commit is contained in:
@@ -829,6 +829,36 @@ class RestoreService {
|
||||
const { host, port, user, password, database } = knexConfig.connection;
|
||||
const env = { ...process.env, PGPASSWORD: password };
|
||||
|
||||
// Snapshot operator-meta settings BEFORE the DROP so we can
|
||||
// restore them after the psql load. These keys are about how
|
||||
// the operator wants the install to behave (force-restore
|
||||
// permission, auto-upgrade tracking), not user-facing state —
|
||||
// they should NOT be overwritten by whatever values the backup
|
||||
// happens to contain.
|
||||
//
|
||||
// Chicken-and-egg this closes: `restore_allow_force` defaults
|
||||
// to true (post tonight's migration 032 edit), but every
|
||||
// restore would overwrite it with whatever the backup carried.
|
||||
// Admin sets it to true → restores → wakes up with the row
|
||||
// back to whatever was in the backup. Two consecutive restores
|
||||
// needed the SQL workaround again. With this snapshot/replay,
|
||||
// the operator's policy persists across restores.
|
||||
const PRESERVED_META_KEYS = [
|
||||
'restore_allow_force',
|
||||
'restore_allow_force_auto_upgraded',
|
||||
];
|
||||
let preservedMeta = [];
|
||||
try {
|
||||
preservedMeta = await db('app_settings')
|
||||
.whereIn('setting_key', PRESERVED_META_KEYS)
|
||||
.select('setting_key', 'setting_value', 'setting_type');
|
||||
this.log('info', `Snapshotted ${preservedMeta.length} restore-meta setting(s) for post-restore replay`, {
|
||||
keys: preservedMeta.map(r => r.setting_key),
|
||||
});
|
||||
} catch (err) {
|
||||
this.log('warn', `Could not snapshot restore-meta settings (continuing): ${err.message}`);
|
||||
}
|
||||
|
||||
// `psql` with no `-d` defaults to a database whose name matches
|
||||
// the connecting user, NOT a maintenance DB. So on installs
|
||||
// where the user's home DB doesn't exist (e.g. user=`picpeak`,
|
||||
@@ -976,6 +1006,33 @@ END $$;`
|
||||
this.log('info', 'Running database migrations...');
|
||||
await db.migrate.latest();
|
||||
|
||||
// Replay the snapshotted operator-meta settings on top of the
|
||||
// restored DB. UPSERT by setting_key — if the backup had the
|
||||
// same key with a different value, we overwrite it; if the row
|
||||
// doesn't exist in the backup, we insert it. Either way the
|
||||
// operator's pre-restore policy survives.
|
||||
if (preservedMeta.length > 0) {
|
||||
try {
|
||||
for (const row of preservedMeta) {
|
||||
await db('app_settings')
|
||||
.insert({
|
||||
setting_key: row.setting_key,
|
||||
setting_value: row.setting_value,
|
||||
setting_type: row.setting_type || 'restore',
|
||||
updated_at: new Date(),
|
||||
})
|
||||
.onConflict('setting_key')
|
||||
.merge({
|
||||
setting_value: row.setting_value,
|
||||
updated_at: new Date(),
|
||||
});
|
||||
}
|
||||
this.log('info', `Replayed ${preservedMeta.length} restore-meta setting(s) post-restore`);
|
||||
} catch (err) {
|
||||
this.log('warn', `Could not replay restore-meta settings (admin may need to re-set them): ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
return { success: true };
|
||||
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user