Rendering mods link against LWJGL, which a dedicated server does not have, so
Sodium in mods/ killed the server at boot with NoClassDefFoundError on
org/lwjgl/Version. The kit had nowhere else to put such a mod: it used the
server's own mods/ as the mirror, so reaching clients and not being loaded by
the server were mutually exclusive.
$SHARE/client-mods is now mirrored alongside mods/ and enters the pack with
side = "client", so packwiz-installer delivers it to players while the server
never loads it. packwiz url add always writes side = "both", so the field is
rewritten explicitly after each add — inserted when absent, replaced when
present, and re-asserted on unchanged mods in case it drifted.
Covers packwiz-setup.sh, mc-refresh-restart.sh (both directories synced through
one function), packwiz-http.py (allowlist) and mc-service-setup.sh (creates the
directory). Traversal out of client-mods/ is still refused.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The reverse proxy runs on a different host than the game server, so it cannot
alias the share directly — serving the pack needs an HTTP server on the game
server itself.
packwiz-http.py is a threaded static server over an allowlist: mods/, packs/,
and setup-*.html. Everything else 404s, since the same directory holds
server.properties, ops.json, whitelist.json, logs and the world. Threading
matters because installing a pack is one request per mod and a single-threaded
server serialises a whole lobby behind one download. pack.toml and index.toml
are sent no-cache so a proxy cannot serve a stale pack.
Installed as packwiz-http.service on port 18080 by default, configurable with
-H/-B, skippable with --no-http, and read-only via ReadOnlyPaths.
packwiz-setup.sh gains -S/--scheme, for when the pack is built against a mirror
reachable only over plain HTTP but clients must be handed the public HTTPS URL.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>