The wake loop's daily-times branch was firing one missed slot per minute
on service restart. With a schedule like "00:00,04:00,08:00,12:00,16:00,
20:00" and a restart at 19:00, the wrapper would catch up by firing 5
backups at 1-minute intervals -- each a full-world zip of the same
in-memory world. Pure waste: the live world hasn't diverged across the
missed clock slots, the snapshots would be near-identical, and the rapid
save-off/save-on churn impacts player experience.
Source comment already documented the intended behaviour ("Doesn't catch
up if the server was off when a slot passed -- daily/interval backups
don't need replay logic.") but the code didn't implement it.
Fix: in Start(), pre-populate _firedToday with all times <= now so the
wake loop only fires future slots for the rest of today.
Tested live on glados: switched schedule to clock-aligned daily-times
after deploying the fix, zero catch-up backups fired in the 90s window
after restart (vs 5+ before the fix).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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
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.