From c4cf5af7f1ca311a3e7bba98dcdf0a448573409f Mon Sep 17 00:00:00 2001 From: XShine555 Date: Thu, 24 Sep 2026 12:40:18 +0200 Subject: [PATCH] Make the Worker validate its service container and align Worker and Gateway hosts The Worker now registers the Application layer, observability and stream tickets, validates the container on build and ships a complete appsettings. The Gateway drops underscore fields and uses a placeholder bucket in its default YARP transform. --- .../MassTransitDependencyInjection.cs | 3 +- .../Services/ServicesDependencyInjection.cs | 7 ---- .../Authentication/TicketValidator.cs | 27 +++++++------- .../Hosts/Musify.StreamingGateway/Program.cs | 11 +++--- .../appsettings.Development.json | 9 +++++ .../Musify.StreamingGateway/appsettings.json | 2 +- backend/Hosts/Musify.Worker/Program.cs | 33 ++++++++--------- .../appsettings.Development.json | 9 ++--- backend/Hosts/Musify.Worker/appsettings.json | 35 +++++++++++++++++++ deploy/compose.yml | 8 +++++ 10 files changed, 92 insertions(+), 52 deletions(-) diff --git a/backend/Core/Musify.Infrastructure/MassTransit/DependencyInjection/MassTransitDependencyInjection.cs b/backend/Core/Musify.Infrastructure/MassTransit/DependencyInjection/MassTransitDependencyInjection.cs index 8ed3d83..f5ec69a 100644 --- a/backend/Core/Musify.Infrastructure/MassTransit/DependencyInjection/MassTransitDependencyInjection.cs +++ b/backend/Core/Musify.Infrastructure/MassTransit/DependencyInjection/MassTransitDependencyInjection.cs @@ -14,8 +14,8 @@ public static partial class MassTransitDependencyInjection public static IServiceCollection AddMassTransitClient(this IServiceCollection services, IConfiguration configuration) { services.AddValidatedOptions(configuration); - services.AddScoped(); + services.AddMassTransit(options => { AddOutbox(options); @@ -34,6 +34,7 @@ public static partial class MassTransitDependencyInjection services.AddValidatedOptions(configuration); services.AddValidatedOptions(configuration); + services.AddScoped(); RegisterRoutingSlipBuilders(services); diff --git a/backend/Core/Musify.Infrastructure/Services/ServicesDependencyInjection.cs b/backend/Core/Musify.Infrastructure/Services/ServicesDependencyInjection.cs index 0ad4da5..47e9c43 100644 --- a/backend/Core/Musify.Infrastructure/Services/ServicesDependencyInjection.cs +++ b/backend/Core/Musify.Infrastructure/Services/ServicesDependencyInjection.cs @@ -38,13 +38,6 @@ public static class ServicesDependencyInjection return services; } - public static IServiceCollection AddUploadIntentConfiguration(this IServiceCollection services, IConfiguration configuration) - { - services.AddValidatedOptions(configuration); - - return services; - } - public static IServiceCollection AddJobs(this IServiceCollection services) { services.AddHangfire((serviceProvider, hangfireConfiguration) => diff --git a/backend/Hosts/Musify.StreamingGateway/Authentication/TicketValidator.cs b/backend/Hosts/Musify.StreamingGateway/Authentication/TicketValidator.cs index 5782c4b..3a27213 100644 --- a/backend/Hosts/Musify.StreamingGateway/Authentication/TicketValidator.cs +++ b/backend/Hosts/Musify.StreamingGateway/Authentication/TicketValidator.cs @@ -14,27 +14,24 @@ public sealed record TicketValidationResult(string Prefix, long? MaxBytes) public sealed class TicketValidator : IDisposable { - private readonly StreamTicketValidationConfiguration _options; - private readonly RSA _rsa; - private readonly TokenValidationParameters _validationParameters; - private readonly JsonWebTokenHandler _handler = new(); + private readonly RSA rsa; + private readonly TokenValidationParameters validationParameters; + private readonly JsonWebTokenHandler handler = new(); - public TicketValidator(StreamTicketValidationConfiguration options) + public TicketValidator(StreamTicketValidationConfiguration configuration) { - _options = options; + rsa = RSA.Create(); + rsa.ImportFromPem(File.ReadAllText(configuration.PublicKeyPath)); - _rsa = RSA.Create(); - _rsa.ImportFromPem(File.ReadAllText(_options.PublicKeyPath)); - - _validationParameters = new TokenValidationParameters + validationParameters = new TokenValidationParameters { ValidateIssuer = true, - ValidIssuer = _options.Issuer, + ValidIssuer = configuration.Issuer, ValidateAudience = true, - ValidAudience = _options.Audience, + ValidAudience = configuration.Audience, ValidateLifetime = true, ValidateIssuerSigningKey = true, - IssuerSigningKey = new RsaSecurityKey(_rsa), + IssuerSigningKey = new RsaSecurityKey(rsa), ValidAlgorithms = [SecurityAlgorithms.RsaSha256], ClockSkew = TimeSpan.FromSeconds(30), }; @@ -45,7 +42,7 @@ public sealed class TicketValidator : IDisposable if (string.IsNullOrWhiteSpace(token)) return TicketValidationResult.Invalid; - var result = await _handler.ValidateTokenAsync(token, _validationParameters); + var result = await handler.ValidateTokenAsync(token, validationParameters); if (!result.IsValid) return TicketValidationResult.Invalid; @@ -59,5 +56,5 @@ public sealed class TicketValidator : IDisposable return new TicketValidationResult(prefix, maxBytes); } - public void Dispose() => _rsa.Dispose(); + public void Dispose() => rsa.Dispose(); } diff --git a/backend/Hosts/Musify.StreamingGateway/Program.cs b/backend/Hosts/Musify.StreamingGateway/Program.cs index 2997e5d..c127bd7 100644 --- a/backend/Hosts/Musify.StreamingGateway/Program.cs +++ b/backend/Hosts/Musify.StreamingGateway/Program.cs @@ -3,7 +3,9 @@ using Musify.StreamingGateway.Authentication; using Musify.StreamingGateway.Configuration; using Musify.StreamingGateway.Middleware; -var builder = WebApplication.CreateBuilder(); +const string CorsPolicy = "media-clients"; + +var builder = WebApplication.CreateBuilder(args); builder.Services .AddOptions() @@ -16,9 +18,8 @@ builder.Services.AddSingleton(serviceProvider => builder.Services.AddSingleton(); -const string corsPolicy = "media-clients"; var allowedOrigins = builder.Configuration.GetSection("Cors:AllowedOrigins").Get() ?? []; -builder.Services.AddCors(options => options.AddPolicy(corsPolicy, policy => +builder.Services.AddCors(options => options.AddPolicy(CorsPolicy, policy => { if (builder.Environment.IsDevelopment()) policy.AllowAnyOrigin(); @@ -37,9 +38,9 @@ builder.Services var app = builder.Build(); app.MapGet("/health", () => Results.Ok("healthy")) - .WithSummary("Health Check Endpoint"); + .WithSummary("Health check endpoint"); -app.UseCors(corsPolicy); +app.UseCors(CorsPolicy); app.UseMiddleware(); app.MapReverseProxy(); diff --git a/backend/Hosts/Musify.StreamingGateway/appsettings.Development.json b/backend/Hosts/Musify.StreamingGateway/appsettings.Development.json index 0bac6c4..9a8d81e 100644 --- a/backend/Hosts/Musify.StreamingGateway/appsettings.Development.json +++ b/backend/Hosts/Musify.StreamingGateway/appsettings.Development.json @@ -1,5 +1,14 @@ { "StreamTicket": { "PublicKeyPath": "../../../deploy/keys/stream_public.pem" + }, + "ReverseProxy": { + "Routes": { + "media": { + "Transforms": [ + { "PathPattern": "/buckets/webapi-storage/{**rest}" } + ] + } + } } } diff --git a/backend/Hosts/Musify.StreamingGateway/appsettings.json b/backend/Hosts/Musify.StreamingGateway/appsettings.json index a2eeaa5..52df1f0 100644 --- a/backend/Hosts/Musify.StreamingGateway/appsettings.json +++ b/backend/Hosts/Musify.StreamingGateway/appsettings.json @@ -26,7 +26,7 @@ "ClusterId": "seaweed-filer", "Match": { "Path": "/media/{**rest}" }, "Transforms": [ - { "PathPattern": "/buckets/webapi-storage/{**rest}" } + { "PathPattern": "/buckets/CHANGE_ME/{**rest}" } ] } }, diff --git a/backend/Hosts/Musify.Worker/Program.cs b/backend/Hosts/Musify.Worker/Program.cs index 0ea3523..f377677 100644 --- a/backend/Hosts/Musify.Worker/Program.cs +++ b/backend/Hosts/Musify.Worker/Program.cs @@ -1,32 +1,27 @@ -using Musify.Application.Configuration; +using Musify.Application; using Musify.Infrastructure.MassTransit; +using Musify.Infrastructure.Observability; using Musify.Infrastructure.Persistence; using Musify.Infrastructure.Services; -var builder = Host.CreateApplicationBuilder(); +var builder = Host.CreateApplicationBuilder(args); builder.ConfigureContainer(new DefaultServiceProviderFactory(new ServiceProviderOptions { - ValidateOnBuild = false, + ValidateOnBuild = true, ValidateScopes = true })); -var configuration = builder.Configuration; - -var services = builder.Services; - -services.AddValidatedOptions(configuration); -services.AddValidatedOptions(configuration); -services.AddValidatedOptions(configuration); -services.AddValidatedOptions(configuration); -services.AddStorageService(configuration); -services.AddAudioTranscoder(configuration); -services.AddPictureService(); -services.AddDatabase(configuration); -services.AddMassTransitConsumers(configuration); -services.AddUploadIntentConfiguration(configuration); -services.AddMediator(); -services.AddJobs(); +builder.Services + .AddApplication(builder.Configuration) + .AddObservability(builder.Configuration) + .AddDatabase(builder.Configuration) + .AddStorageService(builder.Configuration) + .AddAudioTranscoder(builder.Configuration) + .AddStreamTicketService(builder.Configuration) + .AddPictureService() + .AddMassTransitConsumers(builder.Configuration) + .AddJobs(); var host = builder.Build(); diff --git a/backend/Hosts/Musify.Worker/appsettings.Development.json b/backend/Hosts/Musify.Worker/appsettings.Development.json index 3b8dee5..9ff1979 100644 --- a/backend/Hosts/Musify.Worker/appsettings.Development.json +++ b/backend/Hosts/Musify.Worker/appsettings.Development.json @@ -26,10 +26,11 @@ "ApplicationStorage": { "Bucket": "webapi-storage" }, - "AudioTranscoder": { - "Routes": { - "WorkingDirectory": "../../../deploy/.tmp" - } + "StreamGateway": { + "PublicBaseUrl": "http://localhost:8081" + }, + "StreamTicket": { + "PrivateKeyPath": "../../../deploy/keys/stream_private.pem" }, "Workers": { "Routes": { diff --git a/backend/Hosts/Musify.Worker/appsettings.json b/backend/Hosts/Musify.Worker/appsettings.json index b7f7360..d4fccda 100644 --- a/backend/Hosts/Musify.Worker/appsettings.json +++ b/backend/Hosts/Musify.Worker/appsettings.json @@ -26,11 +26,27 @@ "ApplicationStorage": { "Bucket": "" }, + "StreamGateway": { + "PublicBaseUrl": "", + "AudioFileName": "audio.m4a" + }, + "StreamTicket": { + "PrivateKeyPath": "", + "Audience": "media-gateway", + "Issuer": "musify-webapi", + "TicketTtlSeconds": 3600 + }, + "Playback": { + "AllowAnonymousListening": false, + "AnonymousFragmentSeconds": 0, + "EstimatedAudioBytesPerSecond": 16000 + }, "Mix": { "ItemsPerMix": 12 }, "PlayList": { "Routes": { + "UploadsFolder": "uploads", "ParentFolder": "PlayLists", "OriginalPicturesFolder": "OriginalPictures", "SmallPicturesFolder": "SmallPictures", @@ -46,8 +62,27 @@ "LargeHeight": 512 } }, + "Album": { + "Routes": { + "UploadsFolder": "uploads", + "ParentFolder": "Albums", + "OriginalPicturesFolder": "OriginalPictures", + "SmallPicturesFolder": "SmallPictures", + "MediumPicturesFolder": "MediumPictures", + "LargePicturesFolder": "LargePictures" + }, + "PicturesSizes": { + "SmallWidth": 128, + "SmallHeight": 128, + "MediumWidth": 256, + "MediumHeight": 256, + "LargeWidth": 512, + "LargeHeight": 512 + } + }, "Track": { "Routes": { + "UploadsFolder": "uploads", "ParentFolder": "Tracks", "OriginalPicturesFolder": "OriginalPictures", "SmallPicturesFolder": "SmallPictures", diff --git a/deploy/compose.yml b/deploy/compose.yml index 9d7d976..abcb832 100644 --- a/deploy/compose.yml +++ b/deploy/compose.yml @@ -340,10 +340,18 @@ services: depends_on: migrate: condition: service_completed_successfully + keys-init: + condition: service_completed_successfully environment: <<: *backend-env DOTNET_ENVIRONMENT: ${DOTNET_ENVIRONMENT} + # The Application handlers need these to build their dependencies; the + # Worker never issues stream tickets, so the key is never read. + StreamGateway__PublicBaseUrl: ${PUBLIC_STREAM_URL} + StreamTicket__PrivateKeyPath: /keys/stream_private.pem Workers__Routes__TemporaryFilesDirectory: /tmp/musify + volumes: + - ./keys:/keys:ro restart: unless-stopped # YARP in front of the SeaweedFS filer: validates the RS256 stream ticket and