Commit Graph

14 Commits (24d890c9c90387c9ad10b2fb168cd96d2ee4f4e6)

Author SHA1 Message Date
Amir Alexander Abdelbaki 361d3ed82a Support client-only mods via a separate side=client directory
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>
2026-08-06 01:10:13 +02:00
Amir Alexander Abdelbaki f1731698ed Require Java 21 exactly and pin the unit to it
NeoForge 21.1.x targets Java 21. Fedora 43 ships an early-access JDK 26 as the
default java, which satisfied the ">= 21" check and then failed inside
ModLauncher — the server started, logged "JVM identified as ... 26-ea+32", and
died with no obvious cause.

mc-service-setup.sh now locates a real Java 21 under /usr/lib/jvm, refuses to
continue if there is none, and writes its absolute path into ExecStart so the
service does not follow whatever /usr/bin/java points at later.

install.sh checks for Java 21 specifically rather than for any java, so the
prerequisite step actually installs it on a host that already has a newer JDK.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 00:55:14 +02:00
Amir Alexander Abdelbaki c4074e8dbc Repair prior state on redeploy instead of tripping over it
A redeploy onto a host where earlier runs failed partway had no way to recover:
the obsolete minecraft.socket stayed, a hand-made `systemctl edit` drop-in kept
overriding ExecStart, units sat in a failed state, and a stale FIFO lingered.

mc-service-setup.sh now clears that up front: stops both services, removes the
socket unit, moves any minecraft.service.d / packwiz-http.service.d drop-in
aside with a timestamp (moved, not deleted, in case one was deliberate), removes
stale FIFOs at both the old and current paths, resets failed state and reloads.

It also gives the pack and the generated guides back to the admin user, undoing
the blanket chown an earlier version applied on every run.

START_SERVER now defaults to yes, so unpacking the zip and running install.sh
ends with a running server.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 00:42:42 +02:00
Amir Alexander Abdelbaki 6c85e30cc8 Fix console FIFO blocked by SELinux
minecraft.socket had systemd open the FIFO, which SELinux refuses:

  avc: denied { read write } for pid=1 comm="systemd" name="minecraft-console"
  scontext=...:init_t:s0 tcontext=...:var_run_t:s0 tclass=fifo_file

init_t has no read/write on a var_run_t fifo_file under the stock Fedora
targeted policy, so the socket unit never started and the service failed with
"a dependency job for minecraft.service failed".

The socket unit is gone. The service now creates the FIFO in its own
RuntimeDirectory and opens it as fd 3 read-write before exec'ing the JVM, so
the open never blocks, the reader never sees EOF when a writer disconnects, and
MAINPID stays the JVM. Nothing goes through PID 1, so the policy gap does not
apply. An obsolete minecraft.socket is removed on upgrade.

Console path moves to /run/minecraft/console.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 00:37:50 +02:00
Amir Alexander Abdelbaki a141a31547 Fix ordering cycle between minecraft.service and minecraft.socket
The socket declared Service=minecraft.service and PartOf=minecraft.service
while the service declared Requires=minecraft.socket. That is a cycle, which
systemd breaks by failing a job — surfacing as "a dependency job for
minecraft.service failed" with no obvious cause.

The socket now only listens; the service owns the relationship via
Requires=/After=/Sockets=. Also stop enabling minecraft.socket directly: it has
no [Install] section, and the service pulls it in.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 00:21:59 +02:00
Amir Alexander Abdelbaki ed85ef98b3 Stop re-runs from locking the author out of their own files
mc-service-setup.sh ran chown -R minecraft:minecraft over the whole share on
every invocation. That is right for first-time setup and wrong afterwards: it
takes the generated guides and the pack away from the user who authors them, so
the next packwiz-setup.sh run could not overwrite files it had written itself.
The directory stayed writable, so the up-front -w check passed and the failure
surfaced as a bare "Permission denied" mid-write.

The blanket chown now runs only when the service user is created in that same
run. libraries/ is still chowned unconditionally, since the NeoForge installer
writes it and the service must own it.

packwiz-setup.sh also removes a guide it cannot write before regenerating it,
rather than truncating in place, so a share left in the old state repairs itself
instead of needing manual chown.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 00:09:14 +02:00
Amir Alexander Abdelbaki 908c4ecbb6 Do not fail when the mods directory is absent or empty
On a first deploy the server has not run yet, so /minecraft/mods does not
exist and packwiz-setup.sh died before creating anything. It now creates the
directory, and mc-service-setup.sh also creates it up front with setgid so jars
dropped in by either the service user or the admin stay group-readable.

An empty mods directory is likewise no longer fatal. A pack with no mods yet is
a valid starting point: the pack, the index and the player guides are all
generated, and jars registered later with mc-refresh-restart.sh.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 23:36:37 +02:00
Amir Alexander Abdelbaki b32b47f020 Add packwiz-http service and --scheme flag
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>
2026-08-05 23:27:40 +02:00
Amir Alexander Abdelbaki b4320303d3 Make deploy.sh safely re-runnable
Re-running previously died at step 2, because packwiz-setup.sh refused to
overwrite an existing pack.toml — so a run that failed partway, or any later
change to port or heap, could not be applied by simply running it again.

deploy.sh now snapshots the dataset, stops the server if it was running,
redeploys, and restarts it only if it had been up. packwiz-setup.sh gains
--force, which moves an existing pack to <pack>.bak.<timestamp> rather than
deleting it, since the directory may hold hand-edited .pw.toml files or git
history. deploy.sh passes --force unless --keep-pack is given.

World data and the server's mods directory are untouched throughout.

Also replaces the hardcoded sed line ranges in every usage() with an awk that
prints the leading comment block, so help text stops drifting out of sync when
the header changes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 23:02:46 +02:00
Amir Alexander Abdelbaki debada2b56 Add -p/--port for the Minecraft server port
Threads a port through all three scripts. mc-service-setup.sh writes
server-port into server.properties (updating in place so a generated file keeps
its other settings) and opens the port in firewalld. packwiz-setup.sh renders it
into the player guides.

The guides print a bare hostname on 25565 and host:port otherwise — Minecraft
assumes the default, and a needless ":25565" in the address reliably confuses
players.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 23:00:12 +02:00
Amir Alexander Abdelbaki 87b280cc91 Lower default heap to 10G
13G left roughly 1G for the OS once the ZFS ARC cap is accounted for. 10G plus
a 2G ARC leaves comfortable headroom on a 16G host.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 22:38:50 +02:00
Amir Alexander Abdelbaki c9da65d7ce Fix NeoForge installer failing under runuser
mktemp -d creates a 0700 directory owned by root, so handing the downloaded
installer jar to the minecraft user via runuser failed with "Unable to access
jarfile". Relax the temp path to 0755/0644 — the installer is a public
download and holds nothing sensitive.

Also set HOME explicitly for the runuser invocation, since it otherwise
inherits root's and the installer writes a cache alongside it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 22:33:08 +02:00
Amir Alexander Abdelbaki 1f50667d36 Resolve NeoForge version automatically, default heap to 13G
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>
2026-08-05 22:31:23 +02:00
Amir Alexander Abdelbaki e738422dbe Initial commit: packwiz + NeoForge hosting kit
Deploys a self-hosted NeoForge server whose modpack clients pull automatically
on launch, with mods served from an HTTP mirror of the server's own mods dir
rather than Modrinth or CurseForge.

- mc-service-setup.sh: minecraft user, NeoForge install, systemd service paired
  with a console FIFO socket so stops are graceful and the world saves
- packwiz-setup.sh: packwiz install, pack init, mod index built from the
  mirror, and per-OS player setup guides generated with real URLs baked in
- mc-refresh-restart.sh: re-sync after mod changes, ZFS snapshot, restart
- deploy.sh: runs the above in order
- build.sh: release zip, refusing to package scripts that don't parse

Every entry point refuses to run when /minecraft is not a mountpoint, so an
unmounted dataset cannot silently fill the root filesystem.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 22:23:34 +02:00