Server 2.10.3: browse the MSCO mod list in the web panel

Mods -> MSCO mod list: search the approved mods from msco.mbm.wtf/mods and add or
update one with a click. The server lists and downloads only from the official
keymaster (never from a URL in the answer), checks the SHA-256 and stores it in
data/mods like an uploaded mod. Admins only. 6 new tests (154 pass).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-03 18:08:37 +02:00
co-authored by Claude Opus 5.5
parent 266a462fc3
commit d946c95198
7 changed files with 228 additions and 2 deletions
+26
View File
@@ -525,6 +525,32 @@ public sealed class AdminPanel
return await RunText(s => s.ReloadMods(), () => "Mod uploaded - players get it when the server runs");
});
// Mod-Liste des Keymasters (msco.mbm.wtf/mods) durchsuchen und als Server-Mod übernehmen
app.MapGet("/api/mods/catalog", async (HttpContext ctx) =>
{
var user = Require(ctx, PanelRole.Admin, out var denied); if (user == null) return denied;
List<ModCatalog.Entry> list;
try { list = await ModCatalog.ListAsync(); }
catch (Exception e) { return Error(502, "The MSCO mod list is not reachable (" + e.Message + ")"); }
var installed = ModStore.Load(dataDir, null).ToDictionary(m => m.Id, StringComparer.OrdinalIgnoreCase);
return Results.Json(list.Select(m => new
{
id = m.Id, name = m.Name, description = m.Description, author = m.Author, version = m.Version,
size = m.Size, changelog = m.Changelog, downloads = m.Downloads, updated = m.UpdatedUtc,
installed = installed.ContainsKey(m.Id),
upToDate = installed.TryGetValue(m.Id, out var have) && string.Equals(have.Sha256, m.Sha256, StringComparison.OrdinalIgnoreCase),
}));
});
app.MapPost("/api/mods/catalog/{id}/install", async (HttpContext ctx, string id) =>
{
var user = Require(ctx, PanelRole.Admin, out var denied); if (user == null) return denied;
string error = await ModCatalog.InstallAsync(dataDir, id);
if (error != null) return Error(400, error);
GameServer.Log($"[panel] {By(user)} added the mod {id} from the MSCO mod list");
return await RunText(s => s.ReloadMods(), () => "Mod added - players get it when the server runs");
});
app.MapPost("/api/mods/{id}/remove", async (HttpContext ctx, string id) =>
{
var user = Require(ctx, PanelRole.Admin, out var denied); if (user == null) return denied;