From 4c31a22626db47bbbffadd823ae5129ff079faad Mon Sep 17 00:00:00 2001 From: Luca <102960244+Luca-Timo@users.noreply.github.com> Date: Sat, 30 May 2026 13:06:15 +0200 Subject: [PATCH] fix(restore): DROP/CREATE DATABASE needs explicit -d maintenance DB MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `psql` with no -d connects to a database whose name matches the connecting user. On installs where the user's home DB doesn't exist (common pattern: DB_USER=picpeak, DB_NAME=picpeak_prod, no `picpeak` DB), the restore's DROP DATABASE / CREATE DATABASE statements failed with: FATAL: database "picpeak" does not exist even though the target DB (picpeak_prod) was alive and connectable. And of course you can't connect to the target DB itself for DROP — PostgreSQL refuses while a connection is open to it. Fix: explicitly connect to `postgres` (the maintenance DB every PG cluster ships with) for the DROP/CREATE statements. Override via DB_CHECK_DB env var if the `postgres` DB is restricted to superusers on the cluster — matches the pattern wait-for-db.sh already exposes. Also quote the database name in the SQL so installs whose DB has unusual characters (numbers, hyphens) don't break the statement. Surfaced during Ralf's end-to-end restore validation — yet another "never been tested on a real PG install" latent bug exposed by the Stage A inline-dump path actually being able to produce a restorable manifest for the first time on his install. --- backend/src/services/restoreService.js | 32 +++++++++++++++++++++----- 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/backend/src/services/restoreService.js b/backend/src/services/restoreService.js index 02043e9f..777c5bda 100644 --- a/backend/src/services/restoreService.js +++ b/backend/src/services/restoreService.js @@ -828,15 +828,35 @@ class RestoreService { // PostgreSQL restore const { host, port, user, password, database } = knexConfig.connection; const env = { ...process.env, PGPASSWORD: password }; - + + // `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`, + // target DB=`picpeak_prod`, no `picpeak` DB), the next two + // statements failed with: + // FATAL: database "picpeak" does not exist + // even though the actual target DB was alive and connectable. + // + // Fix: explicitly connect to `postgres` (the maintenance DB + // every PG cluster ships with) for the DROP/CREATE. We can't + // connect to the target DB itself anyway — DROP DATABASE + // refuses to run while a connection is open to it. + // + // Use `DB_CHECK_DB` env var as an override hook (matches the + // pattern wait-for-db.sh already exposes) for installs where + // the `postgres` DB is restricted to superusers. + const maintenanceDb = process.env.DB_CHECK_DB || 'postgres'; + // Drop and recreate database (extremely dangerous!) - this.log('warn', 'Dropping and recreating PostgreSQL database...'); - - await spawnAsync('psql', ['-h', host, '-p', String(port), '-U', user, '-c', `DROP DATABASE IF EXISTS ${database}`], { env }); + this.log('warn', 'Dropping and recreating PostgreSQL database...', { + target: database, via: maintenanceDb, + }); - await spawnAsync('psql', ['-h', host, '-p', String(port), '-U', user, '-c', `CREATE DATABASE ${database}`], { env }); + await spawnAsync('psql', ['-h', host, '-p', String(port), '-U', user, '-d', maintenanceDb, '-c', `DROP DATABASE IF EXISTS "${database}"`], { env }); - // Restore from backup + await spawnAsync('psql', ['-h', host, '-p', String(port), '-U', user, '-d', maintenanceDb, '-c', `CREATE DATABASE "${database}"`], { env }); + + // Restore from backup — this one DOES connect to the target DB. await spawnFromFile('psql', ['-h', host, '-p', String(port), '-U', user, '-d', database], restoreFile, { env }); }