Initial commit: Brass & Sigil monorepo

Self-hosted Minecraft modpack distribution + administration system.

- launcher/  Avalonia 12 desktop client; single-file win-x64 publish.
             Microsoft auth via XboxAuthNet, manifest+SHA-1 mod sync,
             portable install path, sidecar settings.
- server/    brass-sigil-server daemon (.NET 8, linux-x64). Wraps the
             MC subprocess, embedded Kestrel admin panel with cookie
             auth + rate limiting, RCON bridge, scheduled backups,
             BlueMap CLI integration with player markers + skin proxy,
             friend-side whitelist request flow, world wipe with seed
             selection (keep current / random / custom).
- pack/      pack.lock.json (Modrinth + manual CurseForge entries),
             data-only tweak source under tweaks/, build outputs in
             overrides/ (gitignored).
- scripts/   Build-Pack / Build-Tweaks / Update-Pack / Check-Updates
             plus Deploy-Brass.ps1 unified one-shot deploy with
             version-bump pre-flight and daemon-state detection.
This commit is contained in:
Matt Sijbers
2026-05-05 00:19:05 +01:00
commit a1331212cb
99 changed files with 12640 additions and 0 deletions
+117
View File
@@ -0,0 +1,117 @@
// Status / players / whitelist sidebar panels. Polled (not streamed) because the
// data they show changes infrequently. Logs use SSE -- see console.js.
"use strict";
import { api, escapeHtml } from "./api.js";
import { state, rebuildKnownPlayers } from "./state.js";
export async function tickStatus() {
const pill = document.getElementById("statusPill");
const text = document.getElementById("statusText");
const memEl = document.getElementById("memUsage");
const memBar = document.getElementById("memBar");
const cpuCur = document.getElementById("cpuCurrent");
const cpuBar = document.getElementById("cpuBar");
const cpuMax = document.getElementById("cpuMax");
const cpuAvg = document.getElementById("cpuAvg");
function renderResources(s) {
if (s.memoryBytes != null) {
const usedGB = s.memoryBytes / (1024 ** 3);
const maxGB = s.memoryMaxMB ? s.memoryMaxMB / 1024 : null;
memEl.textContent = maxGB
? `${usedGB.toFixed(2)} / ${maxGB.toFixed(1)} GB`
: `${usedGB.toFixed(2)} GB`;
memBar.style.width = maxGB ? `${Math.min(100, (usedGB / maxGB) * 100)}%` : "0%";
} else {
memEl.textContent = "--";
memBar.style.width = "0%";
}
if (s.cpu) {
cpuCur.textContent = `${s.cpu.current.toFixed(1)} %`;
cpuBar.style.width = `${Math.min(100, s.cpu.current)}%`;
cpuMax.textContent = `${s.cpu.max.toFixed(1)}%`;
cpuAvg.textContent = `${s.cpu.avg.toFixed(1)}%`;
} else {
cpuCur.textContent = "--";
cpuBar.style.width = "0%";
cpuMax.textContent = "--";
cpuAvg.textContent = "--";
}
}
try {
const s = await api("/api/status");
if (s.running) {
pill.className = "status-pill online";
text.textContent = "Online";
document.getElementById("pid").textContent = s.pid ?? "--";
const secs = Math.floor(s.uptime ?? 0);
const h = Math.floor(secs / 3600), m = Math.floor((secs % 3600) / 60);
document.getElementById("uptime").textContent = `${h}h ${m}m`;
renderResources(s);
} else {
pill.className = "status-pill offline";
text.textContent = "Offline";
document.getElementById("pid").textContent = "--";
document.getElementById("uptime").textContent = "--";
renderResources({ memoryBytes: null, cpu: null, memoryMaxMB: null });
}
const pv = s.packVersion;
document.getElementById("packVersion").textContent = pv?.name ? `${pv.name} v${pv.version}` : "--";
// World size -- even when server is offline we can still report disk usage.
const worldEl = document.getElementById("worldSize");
if (worldEl) {
const b = s.worldSizeBytes;
if (b == null || b === 0) worldEl.textContent = "--";
else if (b < 1024 * 1024) worldEl.textContent = `${(b / 1024).toFixed(0)} KB`;
else if (b < 1024 * 1024 * 1024) worldEl.textContent = `${(b / (1024 * 1024)).toFixed(1)} MB`;
else worldEl.textContent = `${(b / (1024 * 1024 * 1024)).toFixed(2)} GB`;
}
} catch {
pill.className = "status-pill offline";
text.textContent = "Disconnected";
}
}
export async function tickPlayers() {
try {
const p = await api("/api/players");
state.onlinePlayers = (p.players || []).slice();
document.getElementById("playerCount").textContent = p.online >= 0 ? p.online : "?";
const list = document.getElementById("players");
if (state.onlinePlayers.length === 0) {
list.innerHTML = '<li class="empty-state">No-one online</li>';
} else {
list.innerHTML = state.onlinePlayers.map(n => `<li>${escapeHtml(n)}<span></span></li>`).join("");
}
} catch {}
rebuildKnownPlayers();
}
export async function tickWhitelist() {
try {
const w = await api("/api/whitelist");
state.whitelistedPlayers = (w.players || []).slice();
const list = document.getElementById("whitelist");
if (state.whitelistedPlayers.length === 0) {
list.innerHTML = '<li class="empty-state">No players whitelisted yet</li>';
} else {
list.innerHTML = state.whitelistedPlayers.map(n =>
`<li>${escapeHtml(n)}<button data-name="${escapeHtml(n)}" class="wl-remove">Remove</button></li>`
).join("");
}
} catch {}
rebuildKnownPlayers();
}
// MC takes ~1-2 s to look up a UUID via Mojang and write whitelist.json.
// Refresh shortly after a user-triggered add/remove instead of waiting for the
// 30-second polling tick.
let pendingRefresh;
export function refreshWhitelistSoon() {
clearTimeout(pendingRefresh);
pendingRefresh = setTimeout(tickWhitelist, 1500);
setTimeout(tickWhitelist, 4000); // belt-and-braces if Mojang is slow
}