# 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 ```bash ./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: ```bash sudo systemctl start minecraft journalctl -u minecraft -f ``` Send the players `https:///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 10 GB (`-X`/`-x` to change it). `-XX:+AlwaysPreTouch` means the JVM commits the whole heap at startup rather than growing into it, so the number you set is the number actually taken. On a ZFS host, cap the ARC to go with it — ZFS defaults to using up to half of RAM for cache, and will otherwise contend with the JVM for the same pages: ```bash 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 that leaves roughly 4 GB for the OS and anything else the host runs, which is comfortable. ## Requirements - Fedora (or any systemd distro; `dnf` calls need swapping otherwise) - A ZFS dataset mounted at `/minecraft` - Java 21 - A web server already publishing `/minecraft` — `/mods` and `/packs` must 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: ```bash ./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: ```bash 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.sh` probes `packwiz --help` at 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.toml` files 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.