mirror of
https://github.com/samsonjs/immich.git
synced 2026-04-27 15:07:45 +00:00
fix(server): use provided database username for restore & ensure name is not mangled (#25679)
* fix(server): use provided database name/username for restore & ensure name is not mangled fixes #25633 Signed-off-by: izzy <me@insrt.uk> * chore: add db switch back but with comments Signed-off-by: izzy <me@insrt.uk> * refactor: no need to restore database since it's not technically possible chore: late fallback for username in parameter builder Signed-off-by: izzy <me@insrt.uk> * chore: type fix Signed-off-by: izzy <me@insrt.uk> * chore: re-use the username we just pulled out --------- Signed-off-by: izzy <me@insrt.uk>
This commit is contained in:
parent
ac9f6921cc
commit
ed4d9abdae
1 changed files with 23 additions and 35 deletions
|
|
@ -59,6 +59,7 @@ export async function buildPostgresLaunchArguments(
|
||||||
): Promise<{
|
): Promise<{
|
||||||
bin: string;
|
bin: string;
|
||||||
args: string[];
|
args: string[];
|
||||||
|
databaseUsername: string;
|
||||||
databasePassword: string;
|
databasePassword: string;
|
||||||
databaseVersion: string;
|
databaseVersion: string;
|
||||||
databaseMajorVersion?: number;
|
databaseMajorVersion?: number;
|
||||||
|
|
@ -73,6 +74,7 @@ export async function buildPostgresLaunchArguments(
|
||||||
const databaseMajorVersion = databaseSemver?.major;
|
const databaseMajorVersion = databaseSemver?.major;
|
||||||
|
|
||||||
const args: string[] = [];
|
const args: string[] = [];
|
||||||
|
let databaseUsername;
|
||||||
|
|
||||||
if (isUrlConnection) {
|
if (isUrlConnection) {
|
||||||
if (bin !== 'pg_dump') {
|
if (bin !== 'pg_dump') {
|
||||||
|
|
@ -85,23 +87,18 @@ export async function buildPostgresLaunchArguments(
|
||||||
// remove known bad parameters
|
// remove known bad parameters
|
||||||
parsedUrl.searchParams.delete('uselibpqcompat');
|
parsedUrl.searchParams.delete('uselibpqcompat');
|
||||||
|
|
||||||
if (options.username) {
|
databaseUsername = parsedUrl.username;
|
||||||
parsedUrl.username = options.username;
|
|
||||||
}
|
|
||||||
|
|
||||||
url = parsedUrl.toString();
|
url = parsedUrl.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// assume typical values if we can't parse URL or not present
|
||||||
|
databaseUsername ??= 'postgres';
|
||||||
|
|
||||||
args.push(url);
|
args.push(url);
|
||||||
} else {
|
} else {
|
||||||
args.push(
|
databaseUsername = databaseConfig.username;
|
||||||
'--username',
|
|
||||||
options.username ?? databaseConfig.username,
|
args.push('--username', databaseUsername, '--host', databaseConfig.host, '--port', databaseConfig.port.toString());
|
||||||
'--host',
|
|
||||||
databaseConfig.host,
|
|
||||||
'--port',
|
|
||||||
databaseConfig.port.toString(),
|
|
||||||
);
|
|
||||||
|
|
||||||
switch (bin) {
|
switch (bin) {
|
||||||
case 'pg_dumpall': {
|
case 'pg_dumpall': {
|
||||||
|
|
@ -151,6 +148,7 @@ export async function buildPostgresLaunchArguments(
|
||||||
return {
|
return {
|
||||||
bin: `/usr/lib/postgresql/${databaseMajorVersion}/bin/${bin}`,
|
bin: `/usr/lib/postgresql/${databaseMajorVersion}/bin/${bin}`,
|
||||||
args,
|
args,
|
||||||
|
databaseUsername,
|
||||||
databasePassword: isUrlConnection ? new URL(databaseConfig.url).password : databaseConfig.password,
|
databasePassword: isUrlConnection ? new URL(databaseConfig.url).password : databaseConfig.password,
|
||||||
databaseVersion,
|
databaseVersion,
|
||||||
databaseMajorVersion,
|
databaseMajorVersion,
|
||||||
|
|
@ -207,44 +205,35 @@ const SQL_DROP_CONNECTIONS = `
|
||||||
AND pid <> pg_backend_pid();
|
AND pid <> pg_backend_pid();
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const SQL_RESET_SCHEMA = `
|
const SQL_RESET_SCHEMA = (username: string) => `
|
||||||
-- re-create the default schema
|
-- re-create the default schema
|
||||||
DROP SCHEMA public CASCADE;
|
DROP SCHEMA public CASCADE;
|
||||||
CREATE SCHEMA public;
|
CREATE SCHEMA public;
|
||||||
|
|
||||||
-- restore access to schema
|
-- 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;
|
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 SQL_DROP_CONNECTIONS;
|
||||||
yield isPgClusterDump
|
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
|
\c postgres
|
||||||
`
|
`
|
||||||
: SQL_RESET_SCHEMA;
|
: SQL_RESET_SCHEMA(databaseUsername);
|
||||||
|
|
||||||
for await (const chunk of inputStream) {
|
for await (const chunk of inputStream) {
|
||||||
yield chunk;
|
yield chunk;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function* sqlRollback(inputStream: Readable, isPgClusterDump: boolean) {
|
async function* sqlRollback(inputStream: Readable, databaseUsername: string) {
|
||||||
yield SQL_DROP_CONNECTIONS;
|
yield SQL_DROP_CONNECTIONS;
|
||||||
|
yield SQL_RESET_SCHEMA(databaseUsername);
|
||||||
if (isPgClusterDump) {
|
|
||||||
yield String.raw`
|
|
||||||
-- try to create database
|
|
||||||
-- may fail but script will continue running
|
|
||||||
CREATE DATABASE immich;
|
|
||||||
|
|
||||||
-- switch to database / newly created database
|
|
||||||
\c immich
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
yield SQL_RESET_SCHEMA;
|
|
||||||
|
|
||||||
for await (const chunk of inputStream) {
|
for await (const chunk of inputStream) {
|
||||||
yield chunk;
|
yield chunk;
|
||||||
|
|
@ -273,12 +262,11 @@ export async function restoreDatabaseBackup(
|
||||||
isPgClusterDump = true;
|
isPgClusterDump = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { bin, args, databasePassword, databaseMajorVersion } = await buildPostgresLaunchArguments(
|
const { bin, args, databaseUsername, databasePassword, databaseMajorVersion } = await buildPostgresLaunchArguments(
|
||||||
{ logger, database: databaseRepository, ...pgRepos },
|
{ logger, database: databaseRepository, ...pgRepos },
|
||||||
'psql',
|
'psql',
|
||||||
{
|
{
|
||||||
singleTransaction: !isPgClusterDump,
|
singleTransaction: !isPgClusterDump,
|
||||||
username: isPgClusterDump ? 'postgres' : undefined,
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -301,7 +289,7 @@ export async function restoreDatabaseBackup(
|
||||||
inputStream = storage.createPlainReadStream(backupFilePath);
|
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, {
|
const psql = processRepository.spawnDuplexStream(bin, args, {
|
||||||
env: {
|
env: {
|
||||||
PATH: process.env.PATH,
|
PATH: process.env.PATH,
|
||||||
|
|
@ -332,7 +320,7 @@ export async function restoreDatabaseBackup(
|
||||||
fileStream.pipe(gunzip);
|
fileStream.pipe(gunzip);
|
||||||
inputStream = gunzip;
|
inputStream = gunzip;
|
||||||
|
|
||||||
const sqlStream = Readable.from(sqlRollback(inputStream, isPgClusterDump));
|
const sqlStream = Readable.from(sqlRollback(inputStream, databaseUsername));
|
||||||
const psql = processRepository.spawnDuplexStream(bin, args, {
|
const psql = processRepository.spawnDuplexStream(bin, args, {
|
||||||
env: {
|
env: {
|
||||||
PATH: process.env.PATH,
|
PATH: process.env.PATH,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue