The 0.18.0 economy_policy.js named 'numismatics:sundial' which doesn't
exist -- the real registry IDs are spur, bevel, cog, crown, sun. The
KubeJS warning surfaced this at startup:
Failed to read ingredient from numismatics:sundial:
Item with ID numismatics:sundial does not exist!
Updates both economy_policy.js (recipe removal) and jei_hides.js (JEI
hide) to use the correct 5-denomination list. Bumps to 0.18.1.
Adds the currency layer and the first-join experience.
Mods:
- Create: Numismatics 1.0.20 (currency, vendor blocks, piggy banks)
- Patchouli 1.21.1-93 (kept in pack for future rich manual book)
KubeJS scripts (server_scripts/):
- economy_policy.js remove ALL Waystones recipes; remove coin recipes
only (piggy banks etc. stay craftable for now)
- welcome.js first-login grant: 1 waystone + vanilla written-book
"Brass & Sigil Manual v1" (5 pages: welcome,
waystones, money, spawn plots, rules). Per-player
flag bnsWelcomeGranted in persistentData gates it.
KubeJS scripts (client_scripts/):
- jei_hides.js hide all Waystones items + 4 coin denominations from
the JEI ingredient list. Combined with the recipe
removal players never encounter these items via
normal play -- only via the welcome grant or the
bank exchange.
The written-book manual is the MVP placeholder. When more content lands
(plot system, dynamic exchange, etc.) it'll be replaced by a proper
Patchouli book authored as a tweak jar -- Patchouli's already in the
lockfile so no mod-add will be needed at that point.
Adds pack/overrides/kubejs/server_scripts/waystones_policy.js which:
- Caps regular waystones at 1 per player. Per-player count is stored in
player.persistentData and decremented on break (works correctly because
restrictedWaystones = ["PLAYER"] means only owners can break).
- Bans all 16 sharestone variants. They defeat the "build transport"
intent (solo pair-teleport) and can be placed just outside a claim
for stealth proximity access.
- Bans all 16 portstone variants pending separate design review.
- Leaves warp plates and warp scrolls untouched.
Bumps to 0.17.0 so launcher re-syncs.
The previous rule 'pack/overrides/' ignored the whole directory, which
made it impossible to re-include subpaths via ! exception (git's rule).
Narrow the ignore to 'pack/overrides/mods/' -- the only path that
Build-Tweaks.ps1 actually writes -- and drop the broken whitelist
block. Other subdirs under pack/overrides/ (defaultconfigs/, etc.) now
live in git like normal source.
Adds the Waystones override that 0.16.1 attempted (spawnInVillages =
DISABLED). Bumps to 0.16.3 so launcher caches re-sync.
.gitignore previously ignored all of pack/overrides/ because that dir is
the build output of Build-Tweaks.ps1. But defaultconfigs/ inside it holds
hand-written source-of-truth configs that should be in git (otherwise
they don't survive cross-machine deploys). Whitelisting that subdir.
Adds pack/overrides/defaultconfigs/waystones-common.toml -- the same
file the empty 0.16.1 commit was supposed to ship but couldn't because
of the .gitignore rule. Bumps to 0.16.2.
The override changes just one key:
- spawnInVillages: REGULAR -> DISABLED
All other Waystones settings stay at mod default (ACTIVATION visibility,
PLAYER restriction, empty allowedVisibilities) which already provides
the base-intrusion protections we discussed.
Ships an override of waystones-common.toml under pack/overrides/
defaultconfigs/ so first-run installs (clients + fresh servers) get the
intended defaults. NeoForge auto-copies defaultconfigs/<file> into
config/<file> on first run only -- player-tweaked configs after that
are preserved across pack updates.
The only override vs mod-default in this commit:
- spawnInVillages: REGULAR -> DISABLED
Everything else stays at mod default, which already gives the security
posture we want:
- defaultVisibility = ACTIVATION -> each player must physically visit
a waystone before they can teleport TO it. Solves the "player B
teleports into player A's base" concern without a hard per-player
placement cap (which v21.x dropped).
- restrictedWaystones = [PLAYER] -> only owners can edit their own
waystones.
- allowedVisibilities = [] -> no player UI can create global
waystones; OPs use the /waystone command for server-public ones.
Wild waystones (in the open world, not villages) stay on -- they're
public-good fast travel, not tied to bases.
Waystones 21.1.33: fast-travel teleport network.
Balm 21.0.58: required common lib for Waystones (and other BlayTheNinth mods).
Config will be tweaked in a follow-up after the server generates its
default config files. Intended config:
- worldgen village waystones DISABLED
- maxWaystonesPerPlayer = 1
- defaultPrivacy = Private (player-placed)
- OP-placed global waystones via /waystone command
Adds scripts/Check-Deps.ps1 and wires it into Deploy-Brass.ps1 as a
pre-flight gate when Stage includes Pack. The script parses each lockfile
jar's META-INF/neoforge.mods.toml directly -- the same source the loader
reads at startup -- and refuses to deploy if any [[dependencies]] block
with type=required (default) names a modId that nobody in the pack
provides.
Handles three real edge cases discovered during initial run:
1. Jar-in-jar bundled deps: Create ships Ponder, Registrate, Flywheel
embedded under META-INF/jarjar/. The script recurses into those and
counts their modIds as present, since the loader auto-loads them.
2. Language-provider jars (Kotlin for Forge) have no standard mods.toml
-- they declare via a different mechanism. Tiny slugToImplicitModId
override map (currently 1 entry) covers them.
3. Non-mod lockfile entries (shaderpacks under shaderpacks/, configs
under config/, etc.) are skipped explicitly by path prefix check.
Vanilla deps (minecraft, neoforge, forge, fabric, java, javafml, etc.)
are pre-seeded as present.
Jars cached at /tmp/bns-check-deps-cache/<sha1>.jar; second-run cost is
~1.5s. First run downloads ~150MB.
The script would have caught the v0.15.0 -> v0.15.1 incident: Slice &
Dice's jar declares kotlinforforge required in mods.toml even though
Modrinth's dep field only lists Create. The Modrinth-only check (earlier
draft) wouldn't have helped; the mods.toml-based check does.
Hotfix for 0.15.0: Slice & Dice declares 'kotlinforforge >= 5.8' as a
hard dependency and the server crashed on load without it. Adding KFF
satisfies the dep. 6.9 MB, both sides.
Two client-leaning QoL mods.
- AppleSkin 3.0.9: food + saturation tooltips. Modrinth side=optional both;
tagged "both" so server still installs (harmless, ~75KB).
- Mouse Tweaks 2.26.1: shift-drag, scroll-transfer, auto-refill, click-and-
drag stacking in inventory UI. Modrinth lists server=unsupported, so
explicitly tagged side=client -- this is the first new mod that exercises
the server-side filter from the 0.10.0 schema migration: server's sync
should skip downloading it.
Best-in-class MC profiler. Server side: /spark sampler start, /spark
heap, /spark tps. Optional client side too. ~3.5MB, both sides optional
per Modrinth -- tagged "both" so launcher syncs it too.
Preemptive insurance against duplicate-item proliferation when more
metals-producing mods are added (e.g. when Power Grid lands, or any
future Mekanism/Thermal/etc. addition). Today's pack only has TFMG as
the metals provider so AU is largely a no-op now, but ships with sane
default tag preferences that activate automatically as duplicates appear.
Tiny mod (~290 KB jar), client-optional/server-required per Modrinth.
Adds proximity voice chat to the modpack. Server uses UDP/24454 by default
(firewall rule added separately).
Modrinth lists both sides as "optional" -- players without it can still
join, just no voice. Tagged "both" in the lockfile so server + launcher
both install it.
Two small fixes that surface when running the deploy pipeline on a
non-Windows host (we now do this on glados via the Telegram bridge):
- Build-Pack.ps1: fall back to launcher/ModpackLauncher.csproj <Version>
when Get-Item.VersionInfo.FileVersion returns empty. Linux PowerShell
can't parse PE FileVersion from a Windows .exe, so the previous code
threw "set <Version> in ModpackLauncher.csproj and republish" -- but
the csproj <Version> *was* set, just not readable from a foreign PE.
Appends ".0" so the embedded value still matches the 4-part form the
Windows compile bakes in.
- Deploy-Brass.ps1: prefer rsync where available, fall back to robocopy
otherwise. robocopy is Windows-only and bails on Linux/macOS hosts.
Bake each file's "side" (client / server / both) into manifest.json at
build time so both the launcher and the server filter deterministically
and offline. This replaces the launcher's previous "download everything"
default and lets the server short-circuit its runtime Modrinth lookup
when a file already has an authoritative tag.
Schema:
- ManifestFile.Side: "client" | "server" | "both" (null = "both" for
backward compat with manifests pre-dating this field)
- Files marked "both" omit the field entirely to keep the JSON tight
Code changes:
- launcher Manifest.cs + ManifestSyncService: filter out "server" files
in both the prune+download path and FindMissingFiles
- server Manifest.cs + ManifestSync: drop "client" files outright; only
fall back to the existing runtime Modrinth lookup for files with no
explicit side (legacy/un-tagged mods stay protected)
- server LockedMod: side field propagates into manifest at build time
- scripts/Build-Pack.ps1: propagate side from lockfile to manifest
- scripts/Bootstrap-Sides.ps1: one-off populator; queries Modrinth's
client_side/server_side per project, conservatively marks restricted
only when one side is "unsupported", leaves ambiguous cases as "both"
- pack/pack.lock.json: bootstrap-populated sides (3 client, 5 server,
rest both); CurseForge mods default to "both" pending manual review;
version bumped 0.9.3 -> 0.10.0 since clients must re-sync
Compatibility:
- Old launcher + new manifest: ignores unknown "side", downloads all
- New launcher + old manifest: side null -> "both", installs all
- Old server + new manifest: same -- runtime Modrinth lookup still works
- New server + old manifest: side null -> runtime lookup, same behaviour
When ServerSshHost = 'local' the server-binary stage skips scp/ssh and
does an in-place Copy-Item + chmod + Move-Item on the local filesystem.
Pre-flight pgrep also runs locally. Allows running the deploy script on
the server itself (glados in our case) without setting up ssh-to-self.
Other deploy modes (remote scp/ssh) unchanged.
Adds Services/SelfUpdateService.cs implementing the Chrome-style pattern:
1. Download new exe to "<current>.new"
2. Rename running exe to "<current>.old" (NTFS allows MoveFile on a
running exe -- it resolves processes by handle, not by path)
3. Rename ".new" to canonical path
4. Spawn the new exe with our argv
5. Environment.Exit(0)
6. Next start of the new exe runs CleanupAfterUpdate() in App.OFIC()
which deletes the leftover ".old"
UI: the existing "Download" banner button is now "Install update". Click
runs the self-update flow with a percent/MB progress label; any failure
(network, AV write-block, dir not writable) falls back to opening the
URL in the browser so users always have a path forward.
Bumps to 0.4.7. Also fixes a stale fallback URL that pointed at
sijbers.uk/pack/... -- now correctly points at bns.sijbers.uk/launcher/.
The previous check covered version, launcherVersion/Url, and a single
sampled self-hosted URL. Expanded to cover every field that has ever
been a foot-gun:
Required (must always be present and match pack.lock.json):
name, version, minecraft.version, loader.type, loader.version,
launcherVersion, launcherUrl
Optional but consistency-checked: if pack.lock.json declares the
field, the manifest MUST also have it (and defaultServer must include
a non-empty ip). Catches the case where someone removes the field
from pack.lock or Build-Pack drops it silently:
panelUrl, defaultShader, defaultServer
files[]: every entry validated for path/url/sha1/size shape; every
self-hosted (non-CDN) url checked for the expected host. Catches
partial-update bugs where some URLs migrate hosts but others don't.
URL host checks use the configured $ManifestPublicUrl host as the
source of truth; CDN-hosted mods (modrinth/forgecdn/curseforge) are
intentionally exempt.
Two safeguards so a -Stage Pack run never strips launcherVersion/
launcherUrl from the deployed manifest (the original bug that left
old launchers unable to see upgrade prompts):
1. Always pass -LauncherExePath to Build-Pack.ps1 when a local
publish exists, regardless of stage. Previously this only fired
for stages that included Launcher, so Pack-only deploys regressed
the manifest to a version with no launcher metadata.
2. New post-deploy verification step fetches the published manifest
and asserts: pack version matches lockfile, launcherVersion +
launcherUrl present, all self-hosted URLs use the configured
manifest host. Throws on any mismatch.
Adds GET/POST /api/daemon/config (safe subset of server-config.json --
just memoryMB for now) and a "Resources" section in the existing Settings
modal. The Save and Save+restart buttons now POST both /api/server/settings
and /api/daemon/config; restartRequired is OR'd across the two endpoints.
Future fields like backupKeep / backupSchedule / bluemapDir can be added
by extending DAEMON_FIELDS in settings.js + the validation block in
RunCommand.cs. Sensitive fields (passwords, manifestUrl, ports) are
intentionally kept off this endpoint.
When `webPassword` is null and the daemon starts headless (systemd, piped
SSH), no longer auto-generate a random password. Instead:
- Boot normally with the gate denying everything except /api/auth/setup
- Panel UI eagerly probes new /api/auth/state on load and renders a
first-run setup overlay (password + confirm) when needsSetup=true
- POST /api/auth/setup writes the chosen password and issues the auth
cookie in the same response, so the operator lands logged in
Interactive TTY behaviour (prompt at the console) is unchanged. The gate
middleware is now registered unconditionally so first-run mode is still
locked-down instead of wide-open.
Adds three structural assertions that run after the zip is built:
1. No backslash separators in any entry name. NeoForge needs
"META-INF/neoforge.mods.toml" to be at exactly that forward-slash
path; PowerShell 5.1's [ZipFile]::CreateFromDirectory() puts
"META-INF\neoforge.mods.toml" instead, which the loader silently
rejects. The current build code uses CreateEntry with explicit
forward slashes, but this guard fires if anyone reverts to the
simpler-looking CreateFromDirectory.
2. META-INF/neoforge.mods.toml exists at the canonical path. Without
it, NeoForge skips the jar with a bland "not a valid mod file"
warning that's easy to miss in a 500-line game log.
3. The modId declared in the embedded TOML matches the source folder's
modId. Catches the case where a tweak folder is renamed but its
neoforge.mods.toml isn't updated, which would otherwise ship a jar
whose declared identity differs from its filename.
Each tweak jar's build line now tags "[validated]" so a casual reader
of the log sees that the post-build checks ran. Failure raises a
specific exception with the offending entries listed, so the build
fails loudly at the source instead of producing a pack that mysteriously
doesn't apply its tweaks at runtime.
Stage 5 of Deploy-Brass.ps1 was gated only on \$shouldRunLauncher, so a
"-Stage Pack" run regenerated the manifest locally + mirrored
pack/overrides/ to the share, but never copied the new manifest.json
itself. Result: tweak jars/configs landed on the share, but clients
fetching the (still-old) manifest never knew about the new SHA-1s and
skipped the re-sync. Caught when fixing the brassandsigil_tweaks jar:
the public manifest stayed at 0.9.2 even though local was 0.9.3.
Split into two stages -- launcher exe stays gated on \$shouldRunLauncher,
manifest.json now publishes whenever \$shouldRunPack (so any Pack, All,
or Launcher deploy includes it).
NeoForge's mod scanner rejected brassandsigil_tweaks-1.0.0.jar because
PowerShell 5.1's [ZipFile]::CreateFromDirectory() writes Windows-native
path separators into ZIP entry names on Windows. Entries came out as
"META-INF\neoforge.mods.toml" instead of the spec-required forward-slash
form, so the loader couldn't find the manifest and silently dropped the
jar with "not a valid mod file".
Build-Tweaks.ps1 now opens the archive in 'Create' mode and writes each
file as an explicit ZipArchiveEntry whose name is built from the relative
path with backslashes replaced by forward slashes. Verified the rebuilt
jar lists "META-INF/neoforge.mods.toml" etc.
Pack version 0.9.2 -> 0.9.3 so launchers cached at 0.9.2 see "pack
changed" and re-sync the new tweak jar (their bytes differ; SHA-1 in the
manifest will reflect that).