Compare commits

..

2 Commits

Author SHA1 Message Date
izzy
ab2036f289 chore: add db switch back but with comments
Signed-off-by: izzy <me@insrt.uk>
2026-01-29 16:10:52 +00:00
izzy
9208512648 fix(server): use provided database name/username for restore & ensure name is not mangled
fixes #25633

Signed-off-by: izzy <me@insrt.uk>
2026-01-29 15:59:32 +00:00
2 changed files with 42 additions and 24 deletions

View File

@@ -59,6 +59,8 @@ export async function buildPostgresLaunchArguments(
): Promise<{
bin: string;
args: string[];
databaseName: string;
databaseUsername: string;
databasePassword: string;
databaseVersion: string;
databaseMajorVersion?: number;
@@ -73,6 +75,8 @@ export async function buildPostgresLaunchArguments(
const databaseMajorVersion = databaseSemver?.major;
const args: string[] = [];
let databaseUsername;
let databaseName;
if (isUrlConnection) {
if (bin !== 'pg_dump') {
@@ -85,18 +89,23 @@ export async function buildPostgresLaunchArguments(
// remove known bad parameters
parsedUrl.searchParams.delete('uselibpqcompat');
if (options.username) {
parsedUrl.username = options.username;
}
databaseUsername = parsedUrl.username;
databaseName = parsedUrl.pathname.replace(/^\//, '');
url = parsedUrl.toString();
} else {
// assume typical values if we can't parse URL
databaseUsername = 'postgres';
databaseName = 'immich';
}
args.push(url);
} else {
databaseUsername = databaseConfig.username;
databaseName = databaseConfig.database;
args.push(
'--username',
options.username ?? databaseConfig.username,
databaseConfig.username,
'--host',
databaseConfig.host,
'--port',
@@ -151,6 +160,8 @@ export async function buildPostgresLaunchArguments(
return {
bin: `/usr/lib/postgresql/${databaseMajorVersion}/bin/${bin}`,
args,
databaseUsername,
databaseName,
databasePassword: isUrlConnection ? new URL(databaseConfig.url).password : databaseConfig.password,
databaseVersion,
databaseMajorVersion,
@@ -207,44 +218,53 @@ const SQL_DROP_CONNECTIONS = `
AND pid <> pg_backend_pid();
`;
const SQL_RESET_SCHEMA = `
const SQL_RESET_SCHEMA = (username: string) => `
-- re-create the default schema
DROP SCHEMA public CASCADE;
CREATE SCHEMA public;
-- restore access to schema
GRANT ALL ON SCHEMA public TO postgres;
GRANT ALL ON SCHEMA public TO "${username}";
GRANT ALL ON SCHEMA public TO public;
`;
async function* sql(inputStream: Readable, isPgClusterDump: boolean) {
async function* sql(inputStream: Readable, databaseUsername: string, isPgClusterDump: boolean) {
yield SQL_DROP_CONNECTIONS;
yield isPgClusterDump
? String.raw`
? // it is likely the dump contains SQL to try to drop the currently active
// database to ensure we have a fresh slate; if the `postgres` database exists
// then prefer to switch before continuing otherwise this will just silently fail
String.raw`
\c postgres
`
: SQL_RESET_SCHEMA;
: SQL_RESET_SCHEMA(databaseUsername);
for await (const chunk of inputStream) {
yield chunk;
}
}
async function* sqlRollback(inputStream: Readable, isPgClusterDump: boolean) {
async function* sqlRollback(
inputStream: Readable,
databaseUsername: string,
databaseName: string,
isPgClusterDump: boolean,
) {
yield SQL_DROP_CONNECTIONS;
if (isPgClusterDump) {
yield String.raw`
-- try to create database
-- try to re-create expected database
-- may fail but script will continue running
CREATE DATABASE immich;
CREATE DATABASE "${databaseName}";
-- switch to database / newly created database
\c immich
-- if it exists
\c "${databaseName}"
`;
}
yield SQL_RESET_SCHEMA;
yield SQL_RESET_SCHEMA(databaseUsername);
for await (const chunk of inputStream) {
yield chunk;
@@ -273,14 +293,10 @@ export async function restoreDatabaseBackup(
isPgClusterDump = true;
}
const { bin, args, databasePassword, databaseMajorVersion } = await buildPostgresLaunchArguments(
{ logger, database: databaseRepository, ...pgRepos },
'psql',
{
const { bin, args, databaseUsername, databaseName, databasePassword, databaseMajorVersion } =
await buildPostgresLaunchArguments({ logger, database: databaseRepository, ...pgRepos }, 'psql', {
singleTransaction: !isPgClusterDump,
username: isPgClusterDump ? 'postgres' : undefined,
},
);
});
progressCb?.('backup', 0.05);
@@ -301,7 +317,7 @@ export async function restoreDatabaseBackup(
inputStream = storage.createPlainReadStream(backupFilePath);
}
const sqlStream = Readable.from(sql(inputStream, isPgClusterDump));
const sqlStream = Readable.from(sql(inputStream, databaseUsername, isPgClusterDump));
const psql = processRepository.spawnDuplexStream(bin, args, {
env: {
PATH: process.env.PATH,
@@ -332,7 +348,7 @@ export async function restoreDatabaseBackup(
fileStream.pipe(gunzip);
inputStream = gunzip;
const sqlStream = Readable.from(sqlRollback(inputStream, isPgClusterDump));
const sqlStream = Readable.from(sqlRollback(inputStream, databaseUsername, databaseName, isPgClusterDump));
const psql = processRepository.spawnDuplexStream(bin, args, {
env: {
PATH: process.env.PATH,

View File

@@ -22,3 +22,5 @@
return assetViewerManager.on(events);
});
</script>
const event = name.slice(2) as keyof Events;