fix(ha): read transcode store config from the variable form deployments set
The startup configuration only reads JELLYFIN_ prefixed environment variables, so the bare Jellyfin__TranscodeStore__* form used by the chart, the manifests and the README is dropped and the Redis store is never registered. Nothing logs the selected store, so the fallback is invisible. - Read bare Jellyfin__* variables into the Jellyfin:* configuration root - Keep an explicit JELLYFIN_ variable winning over the bare form - Log the selected transcode session store at startup - Ping Redis once at startup and log an unreachable store at Error - Log endpoints only, never the connection string
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace Jellyfin.Server.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// Extensions for building the application configuration.
|
||||
/// </summary>
|
||||
public static class ConfigurationBuilderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// The environment variable prefix that maps onto this fork's own <c>Jellyfin:*</c> configuration
|
||||
/// keys, for example <c>Jellyfin__TranscodeStore__RedisConnectionString</c>.
|
||||
/// </summary>
|
||||
public const string JellyfinSectionEnvironmentPrefix = "Jellyfin__";
|
||||
|
||||
/// <summary>
|
||||
/// The configuration section root this fork keeps its own settings under.
|
||||
/// </summary>
|
||||
public const string JellyfinSectionRoot = "Jellyfin";
|
||||
|
||||
/// <summary>
|
||||
/// Adds environment variables named <c>Jellyfin__Section__Key</c> as the configuration keys
|
||||
/// <c>Jellyfin:Section:Key</c>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The base configuration only reads <c>JELLYFIN_</c> prefixed environment variables, so the
|
||||
/// unprefixed form every manifest, chart and document uses would otherwise be dropped and the
|
||||
/// feature it configures would stay off with no error.
|
||||
/// </remarks>
|
||||
/// <param name="builder">The configuration builder.</param>
|
||||
/// <returns>The updated configuration builder.</returns>
|
||||
public static IConfigurationBuilder AddJellyfinSectionEnvironmentVariables(this IConfigurationBuilder builder)
|
||||
{
|
||||
// Read through the framework provider so "__" to ":" normalisation and case handling match the
|
||||
// prefixed form exactly; the prefix it strips is then put back as the section root.
|
||||
var scoped = new ConfigurationBuilder()
|
||||
.AddEnvironmentVariables(JellyfinSectionEnvironmentPrefix)
|
||||
.Build();
|
||||
|
||||
var entries = scoped.AsEnumerable()
|
||||
.Where(entry => entry.Value is not null)
|
||||
.Select(entry => new KeyValuePair<string, string?>(
|
||||
ConfigurationPath.Combine(JellyfinSectionRoot, entry.Key),
|
||||
entry.Value))
|
||||
.ToList();
|
||||
|
||||
return builder.AddInMemoryCollection(entries);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Emby.Server.Implementations.MediaEncoding;
|
||||
using MediaBrowser.Controller.MediaEncoding;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using StackExchange.Redis;
|
||||
|
||||
namespace Jellyfin.Server.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// Extensions for registering the transcode session store.
|
||||
/// </summary>
|
||||
public static class TranscodeStoreServiceCollectionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Registers the transcode session store, Redis-backed when a connection string is configured and
|
||||
/// no-op otherwise, and reports the selected store at <see cref="LogLevel.Information"/>.
|
||||
/// </summary>
|
||||
/// <param name="serviceCollection">The service collection.</param>
|
||||
/// <param name="configuration">The configuration to read <c>Jellyfin:TranscodeStore</c> from.</param>
|
||||
/// <param name="logger">The logger to report the selected store on.</param>
|
||||
/// <returns>The updated service collection.</returns>
|
||||
public static IServiceCollection AddTranscodeSessionStore(
|
||||
this IServiceCollection serviceCollection,
|
||||
IConfiguration configuration,
|
||||
ILogger logger)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(configuration);
|
||||
ArgumentNullException.ThrowIfNull(logger);
|
||||
|
||||
serviceCollection.Configure<TranscodeStoreOptions>(configuration.GetSection(TranscodeStoreOptions.ConfigurationSection));
|
||||
|
||||
var redisConnectionString = configuration[TranscodeStoreOptions.RedisConnectionStringKey];
|
||||
if (string.IsNullOrEmpty(redisConnectionString))
|
||||
{
|
||||
logger.LogInformation(
|
||||
"Transcode session store: {Store}. Cross-pod transcode takeover is off; set {Key} to enable it.",
|
||||
nameof(NullTranscodeSessionStore),
|
||||
TranscodeStoreOptions.RedisConnectionStringKey);
|
||||
|
||||
return serviceCollection.AddSingleton<ITranscodeSessionStore, NullTranscodeSessionStore>();
|
||||
}
|
||||
|
||||
logger.LogInformation(
|
||||
"Transcode session store: {Store} on {Endpoints}.",
|
||||
nameof(RedisTranscodeSessionStore),
|
||||
DescribeEndpoints(redisConnectionString));
|
||||
|
||||
serviceCollection.AddSingleton<IConnectionMultiplexer>(sp =>
|
||||
{
|
||||
try
|
||||
{
|
||||
return ConnectionMultiplexer.Connect(redisConnectionString);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
sp.GetRequiredService<ILogger<CoreAppHost>>()
|
||||
.LogError(ex, "Failed to connect to Redis. Check the {Key} configuration.", TranscodeStoreOptions.RedisConnectionStringKey);
|
||||
throw;
|
||||
}
|
||||
});
|
||||
serviceCollection.AddSingleton<ITranscodeSessionStore, RedisTranscodeSessionStore>();
|
||||
serviceCollection.AddHostedService<TranscodeStoreConnectivityProbe>();
|
||||
|
||||
return serviceCollection;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Renders the endpoints of a connection string for logging. The connection string itself is never
|
||||
/// logged because it can carry a password.
|
||||
/// </summary>
|
||||
private static string DescribeEndpoints(string redisConnectionString)
|
||||
{
|
||||
try
|
||||
{
|
||||
return string.Join(
|
||||
',',
|
||||
ConfigurationOptions.Parse(redisConnectionString).EndPoints.Select(endpoint => endpoint.ToString()));
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
return "(unparsable connection string)";
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user