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:
@@ -149,6 +149,23 @@ public static class ServiceCollectionExtensions
|
|||||||
"No PostgreSQL connection string found. Set the POSTGRES_CONNECTION_STRING environment variable, " +
|
"No PostgreSQL connection string found. Set the POSTGRES_CONNECTION_STRING environment variable, " +
|
||||||
"or provide it via CustomProviderOptions.Options[\"ConnectionString\"] or CustomProviderOptions.ConnectionString.");
|
"or provide it via CustomProviderOptions.Options[\"ConnectionString\"] or CustomProviderOptions.ConnectionString.");
|
||||||
|
|
||||||
|
// Support postgresql:// / postgres:// URI format (e.g. DATABASE_URL convention).
|
||||||
|
// NpgsqlDataSourceBuilder requires ADO.NET key=value format; convert if needed.
|
||||||
|
if (connectionString.StartsWith("postgresql://", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| connectionString.StartsWith("postgres://", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
var uri = new Uri(connectionString);
|
||||||
|
var userInfoParts = uri.UserInfo.Split(':', 2);
|
||||||
|
connectionString = new NpgsqlConnectionStringBuilder
|
||||||
|
{
|
||||||
|
Host = uri.Host,
|
||||||
|
Port = uri.Port > 0 ? uri.Port : 5432,
|
||||||
|
Database = uri.AbsolutePath.TrimStart('/'),
|
||||||
|
Username = userInfoParts.Length > 0 ? Uri.UnescapeDataString(userInfoParts[0]) : null,
|
||||||
|
Password = userInfoParts.Length > 1 ? Uri.UnescapeDataString(userInfoParts[1]) : null,
|
||||||
|
}.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
var dataSourceBuilder = new NpgsqlDataSourceBuilder(connectionString);
|
var dataSourceBuilder = new NpgsqlDataSourceBuilder(connectionString);
|
||||||
|
|
||||||
dataSourceBuilder.ConnectionStringBuilder.MinPoolSize = GetPoolOption(options, "MinPoolSize", 2);
|
dataSourceBuilder.ConnectionStringBuilder.MinPoolSize = GetPoolOption(options, "MinPoolSize", 2);
|
||||||
|
|||||||
+11
-5
@@ -15,8 +15,9 @@ namespace Jellyfin.Database.Providers.PostgreSQL;
|
|||||||
[JellyfinDatabaseProviderKey("Jellyfin-PostgreSQL")]
|
[JellyfinDatabaseProviderKey("Jellyfin-PostgreSQL")]
|
||||||
public sealed class PostgreSqlDatabaseProvider : IJellyfinDatabaseProvider
|
public sealed class PostgreSqlDatabaseProvider : IJellyfinDatabaseProvider
|
||||||
{
|
{
|
||||||
private const string BackupNotSupportedMessage =
|
// Sentinel returned by MigrationBackupFast to signal that no file backup was
|
||||||
"Automated migration backups are not supported for PostgreSQL. Use the jellyfin-pg-backup CronJob for nightly S3 backups.";
|
// created (PostgreSQL backups are handled externally by jellyfin-pg-backup CronJob).
|
||||||
|
private const string NoAutomatedBackupKey = "postgresql-no-automated-backup";
|
||||||
|
|
||||||
private readonly NpgsqlDataSource _dataSource;
|
private readonly NpgsqlDataSource _dataSource;
|
||||||
|
|
||||||
@@ -69,19 +70,24 @@ public sealed class PostgreSqlDatabaseProvider : IJellyfinDatabaseProvider
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public Task<string> MigrationBackupFast(CancellationToken cancellationToken)
|
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/>
|
/// <inheritdoc/>
|
||||||
public Task RestoreBackupFast(string key, CancellationToken cancellationToken)
|
public Task RestoreBackupFast(string key, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
throw new NotSupportedException(BackupNotSupportedMessage);
|
// No automated backup was taken; nothing to restore.
|
||||||
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public Task DeleteBackup(string key)
|
public Task DeleteBackup(string key)
|
||||||
{
|
{
|
||||||
throw new NotSupportedException(BackupNotSupportedMessage);
|
// No automated backup was taken; nothing to delete.
|
||||||
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
|||||||
Reference in New Issue
Block a user