Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c9f57dae9e | |||
| 83949d63b5 | |||
| 68aae5c917 | |||
| 0818fe8eab | |||
| f269cc4861 | |||
| ae183f27de | |||
| ec8f2b83cf | |||
| 28c816990a | |||
| 73f55768c3 | |||
| 48a70326d2 | |||
| 5f6a5aacdd | |||
| 8d9c04dc05 | |||
| ea9326819d | |||
| f2c9cabefe | |||
| ad9a261627 | |||
| bae44d0178 |
@@ -0,0 +1,50 @@
|
|||||||
|
#Easy NPC Security Configuration
|
||||||
|
#
|
||||||
|
#Permission values use enum names: ALL, MODERATORS, GAMEMASTERS, ADMINS, OWNERS.
|
||||||
|
#Legacy numeric Minecraft command permission levels are accepted and rewritten to enum names.
|
||||||
|
#
|
||||||
|
#normalPlayerCommandLevel: Max command level for normal player-owned imports (default: ALL)
|
||||||
|
#creativePlayerCommandLevel: Max command level for creative non-admin imports (default: MODERATORS)
|
||||||
|
#maxAdminImportedCommandLevel: Max command level for admin imports (default: ADMINS)
|
||||||
|
#serverTrustedCommandLevel: Max command level for server-side trusted imports without player context (default: ADMINS)
|
||||||
|
#blockUnsafeNpcCommands: Blocks critical server management commands from NPC command execution (default: true)
|
||||||
|
#npcSpawnRateLimitCreative: Max new NPCs a creative (non-admin) player may spawn via browser per minute (default: 5)
|
||||||
|
#npcSpawnRateLimitAdmin: Max new NPCs an admin player may spawn via browser per minute (default: 20)
|
||||||
|
#Feature values use enum names: NORMAL_PLAYER, CREATIVE_PLAYER, ADMIN, SERVER_TRUSTED.
|
||||||
|
#feature.SPAWN_NPC: Minimum role required to spawn new NPCs from the preset browser (default: CREATIVE_PLAYER)
|
||||||
|
#feature.WORLD_PRESET: Minimum role required to import/export world presets (default: CREATIVE_PLAYER)
|
||||||
|
#feature.CUSTOM_PRESET: Minimum role required to import/export custom server presets (default: CREATIVE_PLAYER)
|
||||||
|
#feature.LOCAL_PRESET: Minimum role required to export presets locally to the client (default: NORMAL_PLAYER)
|
||||||
|
#feature.DEFAULT_PRESET_IMPORT: Minimum role required to import built-in default presets (default: CREATIVE_PLAYER)
|
||||||
|
#feature.URL_RESOURCE: Minimum role required to use URL-based skin loading (default: CREATIVE_PLAYER)
|
||||||
|
#Sat Jun 06 23:34:43 UTC 2026
|
||||||
|
blockUnsafeNpcCommands=true
|
||||||
|
creativePlayerCommandLevel=MODERATORS
|
||||||
|
executeAsUserCommandAllowList.ADMINS=
|
||||||
|
executeAsUserCommandAllowList.ALL=bns
|
||||||
|
executeAsUserCommandAllowList.GAMEMASTERS=
|
||||||
|
executeAsUserCommandAllowList.MODERATORS=
|
||||||
|
executeAsUserCommandAllowList.OWNERS=
|
||||||
|
feature.BASE_ATTRIBUTE=CREATIVE_PLAYER
|
||||||
|
feature.COMBAT_ATTRIBUTE=CREATIVE_PLAYER
|
||||||
|
feature.COMMAND_ACTION=CREATIVE_PLAYER
|
||||||
|
feature.CUSTOM_PRESET=CREATIVE_PLAYER
|
||||||
|
feature.DEFAULT_PRESET_IMPORT=CREATIVE_PLAYER
|
||||||
|
feature.DIALOG=NORMAL_PLAYER
|
||||||
|
feature.INTERACT_BLOCK_ACTION=CREATIVE_PLAYER
|
||||||
|
feature.LOCAL_PRESET=NORMAL_PLAYER
|
||||||
|
feature.MOVEMENT=CREATIVE_PLAYER
|
||||||
|
feature.OBJECTIVE=CREATIVE_PLAYER
|
||||||
|
feature.OPEN_TRADING_ACTION=CREATIVE_PLAYER
|
||||||
|
feature.POSITION=CREATIVE_PLAYER
|
||||||
|
feature.SCOREBOARD_ACTION=CREATIVE_PLAYER
|
||||||
|
feature.SPAWN_NPC=CREATIVE_PLAYER
|
||||||
|
feature.TRADING=CREATIVE_PLAYER
|
||||||
|
feature.URL_RESOURCE=CREATIVE_PLAYER
|
||||||
|
feature.WORLD_PRESET=CREATIVE_PLAYER
|
||||||
|
maxAdminImportedCommandLevel=ADMINS
|
||||||
|
normalPlayerCommandLevel=ALL
|
||||||
|
npcSpawnRateLimitAdmin=20
|
||||||
|
npcSpawnRateLimitCreative=5
|
||||||
|
serverTrustedCommandLevel=ADMINS
|
||||||
|
unsafeNpcCommands=ban,ban-ip,banlist,debug,deop,difficulty,forceload,function,gamerule,kick,op,pardon,reload,save-all,save-off,save-on,schedule,setidletimeout,setworldspawn,spawnpoint,spreadplayers,stop,whitelist
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+35
-13
@@ -1,13 +1,13 @@
|
|||||||
{
|
{
|
||||||
"$schema": "Brass-and-Sigil pack.lock.json - generated, do not edit by hand unless you know what you are doing",
|
"$schema": "Brass-and-Sigil pack.lock.json - generated, do not edit by hand unless you know what you are doing",
|
||||||
"name": "Brass and Sigil",
|
"name": "Brass and Sigil",
|
||||||
"version": "0.35.0",
|
"version": "0.39.2",
|
||||||
"minecraft": "1.21.1",
|
"minecraft": "1.21.1",
|
||||||
"loader": {
|
"loader": {
|
||||||
"type": "neoforge",
|
"type": "neoforge",
|
||||||
"version": "21.1.228"
|
"version": "21.1.228"
|
||||||
},
|
},
|
||||||
"lockedAt": "2026-06-07T02:33:07Z",
|
"lockedAt": "2026-06-08T16:19:08Z",
|
||||||
"mods": [
|
"mods": [
|
||||||
{
|
{
|
||||||
"source": "modrinth",
|
"source": "modrinth",
|
||||||
@@ -130,17 +130,6 @@
|
|||||||
"size": 38629,
|
"size": 38629,
|
||||||
"side": "both"
|
"side": "both"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"source": "modrinth",
|
|
||||||
"slug": "create-encased",
|
|
||||||
"versionId": "3sUW27Ho",
|
|
||||||
"version": "1.8.1-ht1",
|
|
||||||
"path": "mods/Create Encased-1.21.1-1.8.1-ht1.jar",
|
|
||||||
"url": "https://cdn.modrinth.com/data/hSSqdyU1/versions/3sUW27Ho/Create%20Encased-1.21.1-1.8.1-ht1.jar",
|
|
||||||
"sha1": "5ca3d7674676dda5dd472b0433505a54d5e920d0",
|
|
||||||
"size": 2574837,
|
|
||||||
"side": "both"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"source": "modrinth",
|
"source": "modrinth",
|
||||||
"slug": "bellsandwhistles",
|
"slug": "bellsandwhistles",
|
||||||
@@ -1130,6 +1119,39 @@
|
|||||||
"sha1": "07fdfa3a75c3481770ce0118090a347d89e3a560",
|
"sha1": "07fdfa3a75c3481770ce0118090a347d89e3a560",
|
||||||
"size": 88382,
|
"size": 88382,
|
||||||
"side": "both"
|
"side": "both"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "modrinth",
|
||||||
|
"slug": "structory",
|
||||||
|
"versionId": "Nvh2sSPX",
|
||||||
|
"version": "1.3.16",
|
||||||
|
"path": "mods/Structory_26.1_v1.3.16.jar",
|
||||||
|
"url": "https://cdn.modrinth.com/data/aKCwCJlY/versions/Nvh2sSPX/Structory_26.1_v1.3.16.jar",
|
||||||
|
"sha1": "ee48f145a3a09fdb3f492576cbc21ed26abc74bb",
|
||||||
|
"size": 1261336,
|
||||||
|
"side": "both"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "modrinth",
|
||||||
|
"slug": "structory-towers",
|
||||||
|
"versionId": "ioKwCTuD",
|
||||||
|
"version": "1.0.16",
|
||||||
|
"path": "mods/Structory_Towers_26.1_v1.0.16.jar",
|
||||||
|
"url": "https://cdn.modrinth.com/data/j3FONRYr/versions/ioKwCTuD/Structory_Towers_26.1_v1.0.16.jar",
|
||||||
|
"sha1": "ed22fe659cb3020f2e86b7619ce721714b9546be",
|
||||||
|
"size": 491545,
|
||||||
|
"side": "both"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "modrinth",
|
||||||
|
"slug": "create-tracks+",
|
||||||
|
"versionId": "cju2ayQC",
|
||||||
|
"version": "1.0.5",
|
||||||
|
"path": "mods/tracks_plus-1.0.5.jar",
|
||||||
|
"url": "https://cdn.modrinth.com/data/E8eHF2Yl/versions/cju2ayQC/tracks_plus-1.0.5.jar",
|
||||||
|
"sha1": "87f72a49f53d7cf4202a11e4fc68f9bd18ab2c38",
|
||||||
|
"size": 610174,
|
||||||
|
"side": "both"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"defaultServer": {
|
"defaultServer": {
|
||||||
|
|||||||
@@ -0,0 +1,169 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Linux-side port of Build-Pack.ps1 + Deploy-Brass.ps1 (publish step).
|
||||||
|
|
||||||
|
Reads pack/pack.lock.json + walks pack/overrides/, rebuilds manifest.json,
|
||||||
|
mirrors every override file to the live web root, and republishes the
|
||||||
|
manifest. The launcher will then see fresh hashes on its next sync.
|
||||||
|
|
||||||
|
Run on glados with passwordless sudo. Idempotent.
|
||||||
|
|
||||||
|
Web root: /srv/services/sites/bns (mounted into Caddy as /srv/sites/bns).
|
||||||
|
"""
|
||||||
|
import hashlib
|
||||||
|
import json
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
REPO = Path("/home/matt/dev/brass-and-sigil")
|
||||||
|
LOCK_PATH = REPO / "pack" / "pack.lock.json"
|
||||||
|
OVERRIDES = REPO / "pack" / "overrides"
|
||||||
|
WEB_ROOT = Path("/srv/services/sites/bns/pack")
|
||||||
|
WEB_FILES = WEB_ROOT / "files"
|
||||||
|
WEB_MANIFEST = WEB_ROOT / "manifest.json"
|
||||||
|
SELF_HOST_BASE = "https://bns.sijbers.uk/pack/files"
|
||||||
|
|
||||||
|
MANAGED_ROOTS = ["mods", "config", "resourcepacks", "shaderpacks", "kubejs", "defaultconfigs", "patchouli_books"]
|
||||||
|
|
||||||
|
# bnstoolkit ships client+server surface (screens, HUD, network bridge),
|
||||||
|
# so it must NOT inherit the default "server-only" tag that Build-Pack.ps1
|
||||||
|
# stamps on every pack/overrides/mods/*.jar. brassandsigil_tweaks is
|
||||||
|
# data-only (worldgen modifiers / recipe overrides) so server-side is
|
||||||
|
# correct for it.
|
||||||
|
CLIENT_AND_SERVER_LOCAL_MODS = {"bnstoolkit"}
|
||||||
|
|
||||||
|
|
||||||
|
def sha1_of(p: Path) -> str:
|
||||||
|
h = hashlib.sha1()
|
||||||
|
with p.open("rb") as f:
|
||||||
|
for chunk in iter(lambda: f.read(1 << 16), b""):
|
||||||
|
h.update(chunk)
|
||||||
|
return h.hexdigest()
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
with LOCK_PATH.open() as f:
|
||||||
|
lock = json.load(f)
|
||||||
|
|
||||||
|
files = []
|
||||||
|
total_bytes = 0
|
||||||
|
|
||||||
|
# 1. Mods from the lockfile (Modrinth-hosted)
|
||||||
|
for m in lock["mods"]:
|
||||||
|
entry = {
|
||||||
|
"path": m["path"],
|
||||||
|
"url": m["url"],
|
||||||
|
"sha1": m["sha1"],
|
||||||
|
"size": m["size"],
|
||||||
|
}
|
||||||
|
side = m.get("side")
|
||||||
|
if side and side != "both":
|
||||||
|
entry["side"] = side
|
||||||
|
files.append(entry)
|
||||||
|
total_bytes += m["size"]
|
||||||
|
|
||||||
|
# 2. Local overrides
|
||||||
|
for root in MANAGED_ROOTS:
|
||||||
|
root_dir = OVERRIDES / root
|
||||||
|
if not root_dir.exists():
|
||||||
|
continue
|
||||||
|
for p in sorted(root_dir.rglob("*")):
|
||||||
|
if not p.is_file():
|
||||||
|
continue
|
||||||
|
rel = p.relative_to(OVERRIDES).as_posix()
|
||||||
|
sha1 = sha1_of(p)
|
||||||
|
size = p.stat().st_size
|
||||||
|
entry = {
|
||||||
|
"path": rel,
|
||||||
|
"url": f"{SELF_HOST_BASE}/{rel}",
|
||||||
|
"sha1": sha1,
|
||||||
|
"size": size,
|
||||||
|
}
|
||||||
|
if root == "mods":
|
||||||
|
# See CLIENT_AND_SERVER_LOCAL_MODS — bnstoolkit must be loaded
|
||||||
|
# on both sides; the other locally-built mods are server-only.
|
||||||
|
stem = p.name.split("-")[0].split("_")[0] # before first - or _
|
||||||
|
if not any(p.name.startswith(prefix) for prefix in CLIENT_AND_SERVER_LOCAL_MODS):
|
||||||
|
entry["side"] = "server"
|
||||||
|
files.append(entry)
|
||||||
|
total_bytes += size
|
||||||
|
|
||||||
|
# 3. Compose manifest matching the existing schema
|
||||||
|
manifest = {
|
||||||
|
"name": lock["name"],
|
||||||
|
"version": lock["version"],
|
||||||
|
"minecraft": {"version": lock["minecraft"]},
|
||||||
|
"loader": {"type": lock["loader"]["type"], "version": lock["loader"]["version"]},
|
||||||
|
"files": files,
|
||||||
|
}
|
||||||
|
for opt in ("defaultServer", "defaultShader", "panelUrl"):
|
||||||
|
if opt in lock:
|
||||||
|
manifest[opt] = lock[opt]
|
||||||
|
|
||||||
|
# Carry the launcher metadata from the existing published manifest
|
||||||
|
# (we can't read a Windows .exe's FileVersion from Linux easily, and
|
||||||
|
# the launcher version isn't related to pack content).
|
||||||
|
try:
|
||||||
|
with WEB_MANIFEST.open() as f:
|
||||||
|
existing = json.load(f)
|
||||||
|
for k in ("launcherVersion", "launcherUrl"):
|
||||||
|
if k in existing:
|
||||||
|
manifest[k] = existing[k]
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 4. Mirror override files to web root (uses sudo + cp -a). Only update
|
||||||
|
# files whose hash differs from what's already published to keep the
|
||||||
|
# rsync footprint tiny.
|
||||||
|
print(f"\nMirroring {sum(1 for r in MANAGED_ROOTS if (OVERRIDES / r).exists())} override roots to {WEB_FILES}\n")
|
||||||
|
changed = 0
|
||||||
|
skipped = 0
|
||||||
|
for root in MANAGED_ROOTS:
|
||||||
|
root_dir = OVERRIDES / root
|
||||||
|
if not root_dir.exists():
|
||||||
|
continue
|
||||||
|
target_root = WEB_FILES / root
|
||||||
|
for p in root_dir.rglob("*"):
|
||||||
|
if not p.is_file():
|
||||||
|
continue
|
||||||
|
rel = p.relative_to(OVERRIDES)
|
||||||
|
target = WEB_FILES / rel
|
||||||
|
try:
|
||||||
|
if target.exists() and sha1_of(target) == sha1_of(p):
|
||||||
|
skipped += 1
|
||||||
|
continue
|
||||||
|
except PermissionError:
|
||||||
|
# Need sudo to read
|
||||||
|
pass
|
||||||
|
target.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
shutil.copy2(p, target)
|
||||||
|
changed += 1
|
||||||
|
print(f" changed: {changed}, skipped (matching hash): {skipped}\n")
|
||||||
|
|
||||||
|
# 5. Write the manifest atomically (write to .new, then move)
|
||||||
|
new_manifest = WEB_MANIFEST.with_suffix(".json.new")
|
||||||
|
bak_manifest = WEB_MANIFEST.with_suffix(".json.bak")
|
||||||
|
if WEB_MANIFEST.exists():
|
||||||
|
shutil.copy2(WEB_MANIFEST, bak_manifest)
|
||||||
|
with new_manifest.open("w") as f:
|
||||||
|
json.dump(manifest, f, indent=2, ensure_ascii=False)
|
||||||
|
f.write("\n")
|
||||||
|
new_manifest.replace(WEB_MANIFEST)
|
||||||
|
|
||||||
|
print(f"Manifest written: {WEB_MANIFEST}")
|
||||||
|
print(f" Version: {lock['version']}")
|
||||||
|
print(f" Files: {len(files)}")
|
||||||
|
print(f" Total: {total_bytes / 1024 / 1024:.1f} MB download")
|
||||||
|
print()
|
||||||
|
# Quick verify
|
||||||
|
rel = "kubejs/server_scripts/economy/00_tier_system.js"
|
||||||
|
entry = next((f for f in files if f["path"] == rel), None)
|
||||||
|
if entry:
|
||||||
|
print(f" {rel} -> sha1={entry['sha1'][:12]}... size={entry['size']}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user