aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWizardOfYendor1 <WizardOfYendor1@users.noreply.github.com>2026-07-17 12:02:09 -0400
committerWizardOfYendor1 <WizardOfYendor1@users.noreply.github.com>2026-07-17 14:59:46 -0400
commit0c7428f13675d1b63234cdc3ef5c748eb998e8e9 (patch)
tree091ebbf37dd8057fce2ab87a661a8e3889f9be0e
parent97e666c56639c5b1f846e684dee391c3b62c0ac9 (diff)
Added verbose, rambling, log warning to help users with config issues (hoping to reduce false issues reports).
Also added a test to exercise it, which is perhaps silly but convenient.
-rw-r--r--src/Jellyfin.Networking/Manager/NetworkManager.cs44
-rw-r--r--tests/Jellyfin.Networking.Tests/NetworkParseTests.cs89
2 files changed, 133 insertions, 0 deletions
diff --git a/src/Jellyfin.Networking/Manager/NetworkManager.cs b/src/Jellyfin.Networking/Manager/NetworkManager.cs
index 69f36e9bb1..496c108cfd 100644
--- a/src/Jellyfin.Networking/Manager/NetworkManager.cs
+++ b/src/Jellyfin.Networking/Manager/NetworkManager.cs
@@ -491,6 +491,7 @@ public class NetworkManager : INetworkManager, IDisposable
startupOverrideKey,
true,
true));
+ WarnIfPublishedUrlBasePathDiffers(publishedServerUrls, config.BaseUrl);
_publishedServerUrls = publishedServerUrls;
return;
}
@@ -580,10 +581,53 @@ public class NetworkManager : INetworkManager, IDisposable
}
}
+ WarnIfPublishedUrlBasePathDiffers(publishedServerUrls, config.BaseUrl);
_publishedServerUrls = publishedServerUrls;
}
}
+ /// <summary>
+ /// Warns when a full-URL published server override uses a public path that differs from the configured base
+ /// URL. Jellyfin appends the base URL to generated Live TV client URLs in this case, which can conflict with
+ /// reverse proxies that translate public request paths. Bare host/IP overrides are exempt because the base URL
+ /// is appended when the API URL is built from them.
+ /// </summary>
+ /// <param name="publishedServerUrls">The parsed published server URL overrides.</param>
+ /// <param name="baseUrl">The configured base URL, if any.</param>
+ private void WarnIfPublishedUrlBasePathDiffers(List<PublishedServerUriOverride> publishedServerUrls, string baseUrl)
+ {
+ if (string.IsNullOrEmpty(baseUrl))
+ {
+ return;
+ }
+
+ foreach (var overrideUri in publishedServerUrls.Select(x => x.OverrideUri).Distinct(StringComparer.OrdinalIgnoreCase))
+ {
+ if (!overrideUri.StartsWith("http://", StringComparison.OrdinalIgnoreCase)
+ && !overrideUri.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
+ {
+ continue;
+ }
+
+ if (!Uri.TryCreate(overrideUri, UriKind.Absolute, out var uri))
+ {
+ continue;
+ }
+
+ var path = Uri.UnescapeDataString(uri.AbsolutePath).TrimEnd('/');
+ if (path.EndsWith(baseUrl, StringComparison.OrdinalIgnoreCase))
+ {
+ continue;
+ }
+
+ var publishedServerHost = uri.GetComponents(UriComponents.HostAndPort, UriFormat.Unescaped);
+ _logger.LogWarning(
+ "The published server URL for host '{PublishedServerHost}' does not end with the configured base URL '{BaseUrl}'. Jellyfin will append this base URL when generating Live TV client URLs. If your reverse proxy translates public paths, this may cause Live TV playback to fail. Update the Published Server URIs setting on the Networking page of the admin dashboard, the JELLYFIN_PublishedServerUrl environment variable / --published-server-url option, or the reverse proxy path mapping accordingly.",
+ publishedServerHost,
+ baseUrl);
+ }
+ }
+
private void ConfigurationUpdated(object? sender, ConfigurationUpdateEventArgs evt)
{
if (evt.Key.Equals(NetworkConfigurationStore.StoreKey, StringComparison.Ordinal))
diff --git a/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs b/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs
index 5f7a0efe8a..d8cb9e1ac6 100644
--- a/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs
+++ b/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs
@@ -562,6 +562,95 @@ namespace Jellyfin.Networking.Tests
Assert.Null(port);
}
+ [Theory]
+ // Full-URL override with a different public path: warn about the Live TV fallback.
+ [InlineData("all=https://media.example.com", "/jellyfin", true)]
+ // Full-URL override that ends with the base URL (with and without a trailing slash): no warning.
+ [InlineData("all=https://media.example.com/jellyfin", "/jellyfin", false)]
+ [InlineData("all=https://media.example.com/jellyfin/", "/jellyfin", false)]
+ [InlineData("all=https://media.example.com/media/jellyfin", "/jellyfin", false)]
+ [InlineData("all=https://media.example.com/cool%20server", "/cool server", false)]
+ // A similar segment or a path following the base URL is a different public API base.
+ [InlineData("all=https://media.example.com/jellyfinx", "/jellyfin", true)]
+ [InlineData("all=https://media.example.com/jellyfin/media", "/jellyfin", true)]
+ // No base URL configured: there is no path to compare.
+ [InlineData("all=https://media.example.com", "", false)]
+ // Bare host overrides get the base URL appended when the API URL is built: no warning.
+ [InlineData("all=media.example.com", "/jellyfin", false)]
+ [InlineData("internal=http-proxy.lan:8097", "/jellyfin", false)]
+ // Keyword overrides go through the same check as "all".
+ [InlineData("internal=http://10.0.0.5:8096", "/jellyfin", true)]
+ public void InitializeOverrides_FullUrlPublicPathDiffersFromBaseUrl_LogsWarning(string publishedServers, string baseUrl, bool expectWarning)
+ {
+ var conf = new NetworkConfiguration
+ {
+ LocalNetworkSubnets = new[] { "192.168.1.0/24" },
+ LocalNetworkAddresses = new[] { "eth16" },
+ EnableIPv4 = true,
+ PublishedServerUriBySubnet = new[] { publishedServers },
+ BaseUrl = baseUrl
+ };
+
+ var logger = new Mock<ILogger<NetworkManager>>();
+ NetworkManager.MockNetworkSettings = "192.168.1.208/24,-16,eth16";
+ var startupConf = new Mock<IConfiguration>();
+ using var nm = new NetworkManager(NetworkParseTests.GetMockConfig(conf), startupConf.Object, logger.Object);
+ NetworkManager.MockNetworkSettings = string.Empty;
+
+ VerifyBaseUrlWarning(logger, expectWarning ? Times.AtLeastOnce() : Times.Never());
+ }
+
+ /// <summary>
+ /// The JELLYFIN_PublishedServerUrl environment variable / --published-server-url option takes the
+ /// startup-configuration branch of <c>InitializeOverrides</c> and must funnel through the same
+ /// base URL check as the dashboard overrides.
+ /// </summary>
+ [Fact]
+ public void InitializeOverrides_StartupPublishedServerUrlPathDiffersFromBaseUrl_LogsWarningWithoutCredentials()
+ {
+ var conf = new NetworkConfiguration
+ {
+ LocalNetworkSubnets = new[] { "192.168.1.0/24" },
+ LocalNetworkAddresses = new[] { "eth16" },
+ EnableIPv4 = true,
+ BaseUrl = "/jellyfin"
+ };
+
+ var logger = new Mock<ILogger<NetworkManager>>();
+ var startupConf = new Mock<IConfiguration>();
+ startupConf.Setup(x => x[MediaBrowser.Controller.Extensions.ConfigurationExtensions.AddressOverrideKey]).Returns("https://user:password@media.example.com?access_token=secret#fragment");
+
+ NetworkManager.MockNetworkSettings = "192.168.1.208/24,-16,eth16";
+ using var nm = new NetworkManager(NetworkParseTests.GetMockConfig(conf), startupConf.Object, logger.Object);
+ NetworkManager.MockNetworkSettings = string.Empty;
+
+ VerifyBaseUrlWarning(logger, Times.AtLeastOnce());
+ logger.Verify(
+ l => l.Log(
+ LogLevel.Warning,
+ It.IsAny<EventId>(),
+ It.Is<It.IsAnyType>((state, _) => state.ToString()!.Contains("user", StringComparison.Ordinal)
+ || state.ToString()!.Contains("password", StringComparison.Ordinal)
+ || state.ToString()!.Contains("access_token", StringComparison.Ordinal)
+ || state.ToString()!.Contains("secret", StringComparison.Ordinal)
+ || state.ToString()!.Contains("fragment", StringComparison.Ordinal)),
+ It.IsAny<Exception?>(),
+ It.IsAny<Func<It.IsAnyType, Exception?, string>>()),
+ Times.Never());
+ }
+
+ private static void VerifyBaseUrlWarning(Mock<ILogger<NetworkManager>> logger, Times times)
+ {
+ logger.Verify(
+ l => l.Log(
+ LogLevel.Warning,
+ It.IsAny<EventId>(),
+ It.Is<It.IsAnyType>((state, _) => state.ToString()!.Contains("Jellyfin will append this base URL when generating Live TV client URLs", StringComparison.Ordinal)),
+ It.IsAny<Exception?>(),
+ It.IsAny<Func<It.IsAnyType, Exception?, string>>()),
+ times);
+ }
+
/// <summary>
/// <see cref="NetworkManager.GetBindAddress(HttpRequest, out int?)"/> is the piece of request-host
/// normalization that a request-host-aware smart API URL policy relies on: it resolves the bind address