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:
@@ -0,0 +1,135 @@
|
||||
// Server settings: read/write a curated subset of server.properties.
|
||||
// Changes require an MC restart -- Save writes only, Save & restart bounces MC.
|
||||
"use strict";
|
||||
|
||||
import { api } from "./api.js";
|
||||
|
||||
const els = {};
|
||||
|
||||
// Map of input element ID -> server.properties key. Keeps the form ↔ file
|
||||
// translation in one place; new fields can be added by adding a row here +
|
||||
// matching elements in index.html.
|
||||
const FIELDS = [
|
||||
{ id: "ssfMotd", key: "motd", type: "string" },
|
||||
{ id: "ssfGamemode", key: "gamemode", type: "string" },
|
||||
{ id: "ssfDifficulty", key: "difficulty", type: "string" },
|
||||
{ id: "ssfViewDistance", key: "view-distance", type: "int" },
|
||||
{ id: "ssfSimulationDistance", key: "simulation-distance", type: "int" },
|
||||
{ id: "ssfMaxPlayers", key: "max-players", type: "int" },
|
||||
{ id: "ssfSpawnProtection", key: "spawn-protection", type: "int" },
|
||||
{ id: "ssfPvp", key: "pvp", type: "bool" },
|
||||
{ id: "ssfHardcore", key: "hardcore", type: "bool" },
|
||||
{ id: "ssfAllowFlight", key: "allow-flight", type: "bool" },
|
||||
{ id: "ssfWhiteList", key: "white-list", type: "bool" },
|
||||
{ id: "ssfEnforceWhitelist", key: "enforce-whitelist", type: "bool" },
|
||||
{ id: "ssfEnableCommandBlock", key: "enable-command-block", type: "bool" },
|
||||
];
|
||||
|
||||
function readForm() {
|
||||
const out = {};
|
||||
for (const f of FIELDS) {
|
||||
const el = document.getElementById(f.id);
|
||||
if (!el) continue;
|
||||
if (f.type === "bool") out[f.key] = el.checked ? "true" : "false";
|
||||
else if (f.type === "int") {
|
||||
const v = parseInt(el.value, 10);
|
||||
if (Number.isFinite(v)) out[f.key] = String(v);
|
||||
} else {
|
||||
out[f.key] = el.value;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function writeForm(values) {
|
||||
for (const f of FIELDS) {
|
||||
const el = document.getElementById(f.id);
|
||||
if (!el) continue;
|
||||
const v = values[f.key];
|
||||
if (v === undefined) continue;
|
||||
if (f.type === "bool") el.checked = (v === "true");
|
||||
else el.value = v;
|
||||
}
|
||||
}
|
||||
|
||||
function renderSummary(values) {
|
||||
document.getElementById("ssMotd").textContent = values["motd"] ?? "--";
|
||||
document.getElementById("ssDifficulty").textContent = values["difficulty"] ?? "--";
|
||||
document.getElementById("ssDistances").textContent =
|
||||
`${values["view-distance"] ?? "--"} / ${values["simulation-distance"] ?? "--"}`;
|
||||
document.getElementById("ssMaxPlayers").textContent = values["max-players"] ?? "--";
|
||||
const wl = values["white-list"] === "true";
|
||||
const enf = values["enforce-whitelist"] === "true";
|
||||
document.getElementById("ssWhitelist").textContent =
|
||||
wl ? (enf ? "enforced" : "enabled") : "off";
|
||||
}
|
||||
|
||||
async function refresh() {
|
||||
try {
|
||||
const data = await api("/api/server/settings");
|
||||
renderSummary(data.values || {});
|
||||
writeForm(data.values || {});
|
||||
} catch { /* ignore -- panel just shows last-known */ }
|
||||
}
|
||||
|
||||
async function postSettings() {
|
||||
const payload = readForm();
|
||||
const res = await fetch("/api/server/settings", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
return { ok: res.ok, body: await res.json().catch(() => ({})) };
|
||||
}
|
||||
|
||||
function showMsg(text, ok = false) {
|
||||
els.msg.className = ok ? "acct-msg ok" : "acct-msg";
|
||||
els.msg.textContent = text;
|
||||
}
|
||||
|
||||
export function setupSettings() {
|
||||
els.msg = document.getElementById("ssMsg");
|
||||
els.save = document.getElementById("ssSave");
|
||||
els.restart = document.getElementById("ssRestart");
|
||||
if (!els.save) return;
|
||||
|
||||
els.save.addEventListener("click", async () => {
|
||||
showMsg("Saving...");
|
||||
els.save.disabled = true;
|
||||
try {
|
||||
const r = await postSettings();
|
||||
if (!r.ok || r.body.ok === false) {
|
||||
showMsg(r.body.error || `Error ${r.body.status ?? ""}`);
|
||||
return;
|
||||
}
|
||||
showMsg(r.body.restartRequired
|
||||
? "Saved. Restart for changes to take effect."
|
||||
: "Saved.", true);
|
||||
refresh();
|
||||
} catch (e) { showMsg(e.message); }
|
||||
finally { els.save.disabled = false; }
|
||||
});
|
||||
|
||||
els.restart.addEventListener("click", async () => {
|
||||
if (!confirm("Save changes and restart the server now? Players will be disconnected briefly.")) return;
|
||||
showMsg("Saving + restarting...");
|
||||
els.save.disabled = true; els.restart.disabled = true;
|
||||
try {
|
||||
const r = await postSettings();
|
||||
if (!r.ok || r.body.ok === false) {
|
||||
showMsg(r.body.error || `Save failed: ${r.body.status ?? ""}`);
|
||||
return;
|
||||
}
|
||||
const rr = await fetch("/api/server/restart", { method: "POST" });
|
||||
const rb = await rr.json().catch(() => ({}));
|
||||
if (!rr.ok || rb.ok === false) showMsg("Saved, but restart failed: " + (rb.error || rr.status));
|
||||
else showMsg("Saved + restarting. New settings live in ~30s.", true);
|
||||
refresh();
|
||||
} catch (e) { showMsg(e.message); }
|
||||
finally { els.save.disabled = false; els.restart.disabled = false; }
|
||||
});
|
||||
|
||||
refresh();
|
||||
// Light poll: pick up out-of-band edits to server.properties.
|
||||
setInterval(refresh, 30000);
|
||||
}
|
||||
Reference in New Issue
Block a user