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).