fix(ha): PostgreSQL migration backup no-op + URI connection string support

- MigrationBackupFast/RestoreBackupFast/DeleteBackup return no-ops for
  PostgreSQL; pre-migration backups are handled by jellyfin-pg-backup
  CronJob, not the automated backup path that throws NotSupportedException

- ServiceCollectionExtensions: detect postgresql:// / postgres:// URI
  format in POSTGRES_CONNECTION_STRING and convert to ADO.NET key=value
  format before passing to NpgsqlDataSourceBuilder (which requires it)

Closes startup crash: 'Automated migration backups are not supported for
PostgreSQL' on first boot with a fresh database.
This commit is contained in:
mat
2026-03-05 07:24:28 -05:00
parent c69b8ccc53
commit 1c28df61df
2 changed files with 28 additions and 5 deletions
@@ -15,8 +15,9 @@ namespace Jellyfin.Database.Providers.PostgreSQL;
[JellyfinDatabaseProviderKey("Jellyfin-PostgreSQL")]
public sealed class PostgreSqlDatabaseProvider : IJellyfinDatabaseProvider
{
private const string BackupNotSupportedMessage =
"Automated migration backups are not supported for PostgreSQL. Use the jellyfin-pg-backup CronJob for nightly S3 backups.";
// Sentinel returned by MigrationBackupFast to signal that no file backup was
// created (PostgreSQL backups are handled externally by jellyfin-pg-backup CronJob).
private const string NoAutomatedBackupKey = "postgresql-no-automated-backup";
private readonly NpgsqlDataSource _dataSource;
@@ -69,19 +70,24 @@ public sealed class PostgreSqlDatabaseProvider : IJellyfinDatabaseProvider
/// <inheritdoc/>
public Task<string> MigrationBackupFast(CancellationToken cancellationToken)
{
throw new NotSupportedException(BackupNotSupportedMessage);
// PostgreSQL pre-migration backups are handled externally by the
// jellyfin-pg-backup CronJob. Return a sentinel so callers know no
// file backup was created and the migration can proceed safely.
return Task.FromResult(NoAutomatedBackupKey);
}
/// <inheritdoc/>
public Task RestoreBackupFast(string key, CancellationToken cancellationToken)
{
throw new NotSupportedException(BackupNotSupportedMessage);
// No automated backup was taken; nothing to restore.
return Task.CompletedTask;
}
/// <inheritdoc/>
public Task DeleteBackup(string key)
{
throw new NotSupportedException(BackupNotSupportedMessage);
// No automated backup was taken; nothing to delete.
return Task.CompletedTask;
}
/// <inheritdoc/>