fix(server): sanitize DB_URL for pg_dumpall to remove unknown query params (#23333)

Co-authored-by: Greg Lutostanski <greg.lutostanski@mobilityhouse.com>
Co-authored-by: Alex <alex.tran1502@gmail.com>
feat/sync-adjustment-time
Greg Lutostanski 2025-11-24 09:34:21 +07:00 committed by GitHub
parent 57be3ff8c7
commit aecf064ec9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 42 additions and 3 deletions

@ -93,7 +93,7 @@ Information on the current workers can be found [here](/administration/jobs-work
All `DB_` variables must be provided to all Immich workers, including `api` and `microservices`.
`DB_URL` must be in the format `postgresql://immichdbusername:immichdbpassword@postgreshost:postgresport/immichdatabasename`.
You can require SSL by adding `?sslmode=require` to the end of the `DB_URL` string, or require SSL and skip certificate verification by adding `?sslmode=require&sslmode=no-verify`.
You can require SSL by adding `?sslmode=require` to the end of the `DB_URL` string, or require SSL and skip certificate verification by adding `?sslmode=require&uselibpqcompat=true`. This allows both immich and `pg_dumpall` (the utility used for database backups) to [properly connect](https://github.com/brianc/node-postgres/tree/master/packages/pg-connection-string#tcp-connections) to your database.
When `DB_URL` is defined, the `DB_HOSTNAME`, `DB_PORT`, `DB_USERNAME`, `DB_PASSWORD` and `DB_DATABASE_NAME` database variables are ignored.

@ -153,6 +153,37 @@ describe(BackupService.name, () => {
mocks.storage.createWriteStream.mockReturnValue(new PassThrough());
});
it('should sanitize DB_URL (remove uselibpqcompat) before calling pg_dumpall', async () => {
// create a service instance with a URL connection that includes libpqcompat
const dbUrl = 'postgresql://postgres:pwd@host:5432/immich?sslmode=require&uselibpqcompat=true';
const configMock = {
getEnv: () => ({ database: { config: { connectionType: 'url', url: dbUrl }, skipMigrations: false } }),
getWorker: () => ImmichWorker.Api,
isDev: () => false,
} as unknown as any;
({ sut, mocks } = newTestService(BackupService, { config: configMock }));
mocks.storage.readdir.mockResolvedValue([]);
mocks.process.spawn.mockReturnValue(mockSpawn(0, 'data', ''));
mocks.storage.rename.mockResolvedValue();
mocks.storage.unlink.mockResolvedValue();
mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.backupEnabled);
mocks.storage.createWriteStream.mockReturnValue(new PassThrough());
mocks.database.getPostgresVersion.mockResolvedValue('14.10');
await sut.handleBackupDatabase();
expect(mocks.process.spawn).toHaveBeenCalled();
const call = mocks.process.spawn.mock.calls[0];
const args = call[1] as string[];
// ['--dbname', '<url>', '--clean', '--if-exists']
expect(args[0]).toBe('--dbname');
const passedUrl = args[1];
expect(passedUrl).not.toContain('uselibpqcompat');
expect(passedUrl).toContain('sslmode=require');
});
it('should run a database backup successfully', async () => {
const result = await sut.handleBackupDatabase();
expect(result).toBe(JobStatus.Success);

@ -81,8 +81,16 @@ export class BackupService extends BaseService {
const isUrlConnection = config.connectionType === 'url';
let connectionUrl: string = isUrlConnection ? config.url : '';
if (URL.canParse(connectionUrl)) {
// remove known bad url parameters for pg_dumpall
const url = new URL(connectionUrl);
url.searchParams.delete('uselibpqcompat');
connectionUrl = url.toString();
}
const databaseParams = isUrlConnection
? ['--dbname', config.url]
? ['--dbname', connectionUrl]
: [
'--username',
config.username,
@ -118,7 +126,7 @@ export class BackupService extends BaseService {
{
env: {
PATH: process.env.PATH,
PGPASSWORD: isUrlConnection ? new URL(config.url).password : config.password,
PGPASSWORD: isUrlConnection ? new URL(connectionUrl).password : config.password,
},
},
);