etherpad-lite/bin/migrateDB.ts
John McLear 9d2dae1b63
fix(bin): close DBs in migrateDB so it flushes and exits (#7982)
`bin/migrateDB.ts` opens a source and target ueberdb2 Database, copies all
keys, then resolves without closing either connection or calling
process.exit(). Two problems with ueberdb2 6.1.x:

- 6.1.x keeps an internal keep-alive timer running until close() is called,
  so the migration process hangs forever after "Done syncing dbs" instead
  of exiting. (Pre-6.1.x it exited on its own once the work was done.)
- Target writes are buffered and only guaranteed flushed to disk on close()
  /flush(), so an operator who Ctrl-Cs the apparently-finished process could
  end up with an incomplete migration.

Close the target then the source on both the success and error paths (which
flushes buffered writes and clears the keep-alive timer) and exit with an
explicit status code, matching the pattern already used in
migrateDirtyDBtoRealDB.ts.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 11:48:39 +01:00

93 lines
2.7 KiB
TypeScript

// DB migration
import {readFileSync} from 'node:fs'
import {Database, DatabaseType} from "ueberdb2";
import path from "node:path";
import settings from 'ep_etherpad-lite/node/utils/Settings';
// file1 = source, file2 = target
// pnpm run --filter bin migrateDB --file1 <db1.json> --file2 <db2.json>
const arg = process.argv.slice(2);
if (arg.length != 4) {
console.error('Wrong number of arguments!. Call with pnpm run --filter bin migrateDB --file1 source.json target.json')
process.exit(1)
}
type SettingsConfig = {
dbType: string,
dbSettings: any
}
/*
{
"dbType": "<your-db-type>",
"dbSettings": {
<your-db-settings>
}
}
*/
let firstDBSettingsFile: string
let secondDBSettingsFile: string
if (arg[0] == "--file1") {
firstDBSettingsFile = arg[1]
} else if (arg[0] === "--file2") {
secondDBSettingsFile = arg[1]
}
if (arg[2] == "--file1") {
firstDBSettingsFile = arg[3]
} else if (arg[2] === "--file2") {
secondDBSettingsFile = arg[3]
}
const settingsfile = JSON.parse(readFileSync(path.join(settings.root,firstDBSettingsFile!)).toString()) as SettingsConfig
const settingsfile2 = JSON.parse(readFileSync(path.join(settings.root,secondDBSettingsFile!)).toString()) as SettingsConfig
console.log(settingsfile2)
if ("filename" in settingsfile.dbSettings) {
settingsfile.dbSettings.filename = path.join(settings.root, settingsfile.dbSettings.filename)
console.log(settingsfile.dbType + " location is "+ settingsfile.dbSettings.filename)
}
if ("filename" in settingsfile2.dbSettings) {
settingsfile2.dbSettings.filename = path.join(settings.root, settingsfile2.dbSettings.filename)
console.log(settingsfile2.dbType + " location is "+ settingsfile2.dbSettings.filename)
}
const ueberdb1 = new Database(settingsfile.dbType as DatabaseType, settingsfile.dbSettings)
const ueberdb2 = new Database(settingsfile2.dbType as DatabaseType, settingsfile2.dbSettings)
const handleSync = async ()=>{
await ueberdb1.init()
await ueberdb2.init()
const allKeys = await ueberdb1.findKeys('*','')
for (const key of allKeys) {
const foundVal = await ueberdb1.get(key)!
await ueberdb2.set(key, foundVal)
}
}
handleSync().then(async ()=>{
// Closing flushes any buffered writes to the target DB and clears
// ueberdb2's keep-alive timer (added in 6.1.x). Without this the migrated
// data may not be fully persisted and the process would hang forever
// instead of exiting once the sync is done.
await ueberdb2.close()
await ueberdb1.close()
console.log("Done syncing dbs")
process.exit(0)
}).catch(async e=>{
console.log(`Error syncing db ${e}`)
await ueberdb2.close().catch(()=>{})
await ueberdb1.close().catch(()=>{})
process.exit(1)
})