Infrastructure/Data/Indexes runs at every start, after the migrations: unique on Post/DmPost ObjectURI, ForeignAvatar ActorURI, the Follower triple, RootToAvatar, RootUser UserName and ReservedName; plain indexes on the lookups the services actually make (PublicKeyId, author and group post listings, ParticipantsKey, the delivery queue). Migration _001 runs first and removes the duplicates the races could already have left (keeping the newest actor row, the oldest post, the accepted follower), then fills ReservedName from every avatar and group. ReservedName is one username space for personas, groups and the instance: a name is reserved by an insert the unique index arbitrates, before the avatar or group is saved, so two simultaneous sign-ups cannot both get it. A short list of names (admin, support, abuse, postmaster, ...) is never available. EntityMaps.Warm touches every entity's collection one at a time before anything else runs. MongoDB.Entities maps the Entity base class on first touch, and two types mapped at once throw "An item with the same key has already been added" and stay broken for the life of the process; the parallel test run hit it, and the delivery worker racing a request could have too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012CzABvBkbcFqoHdmi8b9WB
167 lines
4.5 KiB
C#
167 lines
4.5 KiB
C#
using Microsoft.AspNetCore.HttpOverrides;
|
|
using Microsoft.Extensions.Options;
|
|
|
|
using MongoDB.Bson;
|
|
using MongoDB.Bson.Serialization;
|
|
using MongoDB.Bson.Serialization.Serializers;
|
|
using MongoDB.Driver;
|
|
using MongoDB.Entities;
|
|
|
|
using Serilog;
|
|
|
|
using PrivaPub.Data;
|
|
using PrivaPub.Extensions;
|
|
using PrivaPub.Infrastructure.Cli;
|
|
using PrivaPub.Infrastructure.Data;
|
|
using PrivaPub.Infrastructure.Http;
|
|
using PrivaPub.Middleware;
|
|
using PrivaPub.Models;
|
|
using PrivaPub.Services;
|
|
using PrivaPub.StaticServices;
|
|
|
|
try
|
|
{
|
|
var builder = WebApplication.CreateBuilder(args);
|
|
builder.WebHost.ConfigureKestrel(serverOptions =>
|
|
{
|
|
if (builder.Environment.IsProduction())
|
|
{
|
|
serverOptions.ListenLocalhost(6970
|
|
//, options =>
|
|
//{
|
|
// options.Protocols = HttpProtocols.Http1AndHttp2AndHttp3;
|
|
//}
|
|
);
|
|
serverOptions.UseSystemd();
|
|
serverOptions.AddServerHeader = false;
|
|
}
|
|
});
|
|
builder.Host.UseSerilog((context, config) =>
|
|
{
|
|
config.ReadFrom.Configuration(context.Configuration);
|
|
});
|
|
|
|
try
|
|
{
|
|
builder.Services.PrivaPubAppSettingsConfiguration(builder.Configuration)
|
|
.PrivaPubWorkersConfiguration()
|
|
.PrivaPubAuthServicesConfiguration(builder.Configuration)
|
|
.PrivaPubInternalizationConfiguration(builder.Configuration)
|
|
.PrivaPubOptimizationConfiguration()
|
|
.PrivaPubDataBaseConfiguration()
|
|
.PrivaPubServicesConfiguration()
|
|
.PrivaPubFederationConfiguration(builder.Configuration)
|
|
.PrivaPubCORSConfiguration()
|
|
.PrivaPubMiddlewareConfiguration();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log.ForContext<Program>().Fatal(ex, "{0}.{1}()", nameof(Program), "ConfigureServices");
|
|
throw;
|
|
}
|
|
|
|
var federationOptions = builder.Configuration.GetSection("Federation").Get<FederationOptions>() ?? new();
|
|
if (builder.Environment.IsProduction() && (federationOptions.AllowPrivateNetworks || federationOptions.AllowPlainHttp))
|
|
throw new InvalidOperationException("Federation:AllowPrivateNetworks and Federation:AllowPlainHttp are for test networks and must stay off in Production.");
|
|
|
|
try
|
|
{
|
|
BsonSerializer.RegisterSerializer(new GuidSerializer(GuidRepresentation.Standard));
|
|
var mongoSettings = builder.Configuration.GetSection(nameof(MongoSettings)).Get<MongoSettings>();
|
|
await DB.InitAsync(mongoSettings.Database, MongoClientSettings.FromConnectionString(mongoSettings.ConnectionString));
|
|
EntityMaps.Warm();
|
|
await DB.Default.MigrateAsync<Program>();
|
|
await Indexes.Create();
|
|
|
|
if (args is ["admin", ..])
|
|
{
|
|
Environment.ExitCode = await AdminCommands.Run(args[1..]);
|
|
return;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log.ForContext<Program>().Fatal(ex, $"{nameof(Program)}.{nameof(Program)}() DB Instantiation");
|
|
throw;
|
|
}
|
|
|
|
var app = default(WebApplication);
|
|
try
|
|
{
|
|
app = builder.Build();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log.ForContext<Program>().Fatal(ex, "{0}.{1}()", nameof(Program), "Build");
|
|
throw;
|
|
}
|
|
|
|
try
|
|
{
|
|
var localizationService = app.Services.GetService<RequestLocalizationOptionsService>();
|
|
if (app.Environment.IsProduction())
|
|
{
|
|
app.UseResponseCompression();
|
|
app.UseForwardedHeaders(new()
|
|
{
|
|
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
|
|
});
|
|
}
|
|
|
|
if (app.Environment.IsDevelopment())
|
|
{
|
|
app.UseSwagger();
|
|
app.UseSwaggerUI();
|
|
}
|
|
|
|
app.UseHttpsRedirection();
|
|
app.UseCors("DefaultCORS");
|
|
|
|
app.UseStaticFiles();
|
|
|
|
app.UseRequestLocalization(await localizationService.Get());
|
|
|
|
app.UseRouting();
|
|
|
|
app.UseAuthentication();
|
|
app.UseAuthorization();
|
|
//app.UseWhen(context => context.Request.Path.StartsWithSegments("/peasants") ||
|
|
// context.Request.Path.StartsWithSegments("/users"),
|
|
// app => app.UseSignatureVerification().UseDigestVerification());
|
|
|
|
app.MapGet("/build.json", () => Results.Json(new
|
|
{
|
|
commit = BuildInfo.Commit,
|
|
buildRef = BuildInfo.Ref,
|
|
builtAt = BuildInfo.BuiltAt,
|
|
}));
|
|
app.MapControllers();
|
|
//app.MapFallbackToFile("index.html");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log.ForContext<Program>().Fatal(ex, "{0}.{1}()", nameof(Program), "Use");
|
|
throw;
|
|
}
|
|
|
|
Log.ForContext<Program>().Information($"Starting collAnon at {nameof(Program)}()");
|
|
try
|
|
{
|
|
var dbClient = app.Services.GetService(typeof(DbEntities)) as DbEntities;
|
|
var passwordHasher = app.Services.GetService(typeof(IPasswordHasher)) as IPasswordHasher;
|
|
await dbClient.Init(passwordHasher);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log.ForContext<Program>().Warning(ex, $"{nameof(Program)}.{nameof(Program)}() DB Init");
|
|
}
|
|
|
|
await app.RunAsync();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log.ForContext<Program>().Fatal(ex, $"{nameof(Program)}.{nameof(Program)}()");
|
|
Environment.ExitCode = 1;
|
|
}
|
|
|