deploy.sh previously required -N and failed outright without it. It now passes -N latest by default, and mc-service-setup.sh resolves the newest stable build in the series matching the Minecraft version from the NeoForged maven (1.21.1 -> 21.1.x, 1.21 -> 21.0.x), excluding betas. Pin a version with -N to opt out. Heap defaults raised to 13G for both Xms and Xmx. README documents capping the ZFS ARC alongside it, since ARC defaults to half of RAM and would otherwise contend with a heap this size on the same box. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|---|---|---|
| .gitignore | ||
| README.md | ||
| build.sh | ||
| deploy.sh | ||
| mc-refresh-restart.sh | ||
| mc-service-setup.sh | ||
| packwiz-setup.sh | ||
README.md
mc-packwiz-deploy
Self-hosted NeoForge Minecraft server with a packwiz modpack that clients pull automatically on every launch. Mods are served from an HTTP mirror of the server's own mods directory — no Modrinth or CurseForge dependency.
Everything lives on a ZFS dataset mounted at /minecraft.
Quick start
./deploy.sh -n "Opengames" -a themiro \
-u https://games.abdelbaki.eu/mods \
--accept-eula
The NeoForge version is resolved automatically — the newest stable build in the
series matching your Minecraft version, from the NeoForged maven. Pin one with
-N 21.1.248 if you'd rather not track latest.
Then:
sudo systemctl start minecraft
journalctl -u minecraft -f
Send the players https://<your-host>/setup-windows-packwiz.html or
setup-linux-packwiz.html. Those are generated with your real pack URL and
server address already filled in.
What's in here
| Script | Runs as | Does |
|---|---|---|
deploy.sh |
you | Runs the two setup scripts in order |
mc-service-setup.sh |
root | minecraft user, NeoForge server, systemd units |
packwiz-setup.sh |
you | packwiz install, pack init, mod index, player guides |
mc-refresh-restart.sh |
you | Re-sync the pack after changing mods, then restart |
build.sh |
you | Builds the release zip |
Memory
The heap defaults to 13 GB (-X/-x to change it). -XX:+AlwaysPreTouch means
the JVM commits the whole heap at startup rather than growing into it.
On a ZFS host, cap the ARC before running a heap this large — ZFS defaults to using up to half of RAM for cache, which will fight the JVM for the same pages:
echo "options zfs zfs_arc_max=2147483648" | sudo tee /etc/modprobe.d/zfs.conf
sudo dracut --force && sudo reboot
That pins the ARC to 2 GB. On a 16 GB box, 13 GB heap + 2 GB ARC leaves ~1 GB for everything else, which is tight — drop the heap to 10–11 GB if the host does anything besides run the server.
Requirements
- Fedora (or any systemd distro;
dnfcalls need swapping otherwise) - A ZFS dataset mounted at
/minecraft - Java 21
- A web server already publishing
/minecraft—/modsand/packsmust be reachable over HTTP
Every script refuses to run if /minecraft isn't a mountpoint. An unmounted
dataset leaves it as an empty directory on the root filesystem, and writing a
world or a mod mirror there fills the root disk instead of the pool.
Day-to-day
Add or remove a jar in /minecraft/mods, then:
./mc-refresh-restart.sh
That registers new mods, re-pins any whose file changed, drops metadata for deleted ones, takes a ZFS snapshot, restarts the server, and prunes to the 8 most recent snapshots. Players get the change on their next launch.
It refuses to restart if any mod failed to register, so the server never comes
up on a half-synced pack. Edit the config block at the top first — PACK_DIR,
BASE_URL, and PACK_URL need to match your deployment.
The server unit
minecraft.service pairs with minecraft.socket, a console FIFO at
/run/minecraft-console. That's what makes a clean shutdown possible:
sudo systemctl stop minecraft # sends "stop", world saves properly
echo "say hello" | sudo tee /run/minecraft-console
journalctl -u minecraft -f # console output
Without the FIFO, systemd would SIGTERM the JVM and risk cutting a world save in half.
Web server
/minecraft needs to be served over HTTP. Serve pack.toml and index.toml
with Cache-Control: no-cache — a caching proxy will otherwise hand clients a
stale pack while the server runs the new one. mc-refresh-restart.sh checks for
exactly that and stops before restarting if it finds it.
Caveats
packwiz-setup.shprobespackwiz --helpat runtime rather than assuming flag names, since the NeoForge options have moved between builds. If it bails with an interface complaint, the installed packwiz differs from what's expected.- Mod names come from filenames with the trailing version stripped
(
create-1.21.1-6.0.4.jar→create). It's a heuristic; rename the generated.pw.tomlfiles if you don't like the result. - Hashes are pinned. Replacing a jar in place under the same filename requires a re-sync, or clients fail the hash check.