Files
mc-lan-party/HOSTING.md
T
Mykhailo Ivanchenko 238aa7809c install.ps1: ask which drive to install to
Lists local fixed drives with free space and lets the player pick one by
number or letter. Enter keeps the previous location on the system drive
(%USERPROFILE%\mc-lan-party), so existing installs are still found;
other drives install to <drive>:\mc-lan-party. Warns under 3 GB free.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 13:12:05 +02:00

151 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# How to run this at the LAN party
## 1. Prep the modpack instance once, yourself, beforehand
1. Install [Fjord Launcher Unlocked](https://github.com/hero-persson/FjordLauncherUnlocked) normally on your own machine.
2. Create a new instance: Minecraft **1.20.1**, **Forge**. Name it exactly `LANParty` (or change `INSTANCE_NAME`/`$InstanceName` in both scripts to match).
3. Add the mods from `modpack.md` (use the launcher's built-in Modrinth/CurseForge browser, or drop jars straight into the instance's `mods/` folder).
4. **Set up the server first** — do the "The server" and "Pre-generate terrain before the party starts (Distant Horizons Full-mode)" steps below *before* continuing here.
5. Launch the client, go to **Multiplayer → Add Server**, enter `minecraft.mivanchenko.de:25565`, and join for real. Set your preferred video settings (these land in `options.txt`). No manual flying pass needed — Distant Horizons is installed server-side (same build both sides) and streams accurate LOD data directly to any connected client with Distant Generation enabled, so coverage is automatic for every participant, not just something baked in by the host ahead of time.
6. Quit the launcher, then export the instance via **right-click the instance → Export Instance** (don't blindly zip the raw folder — the exporter lets you pick what's included). Check: `mods/`, `config/`, `options.txt`, `servers.dat`. Uncheck: `logs/`, `crash-reports/`, `screenshots/`, `Distant_Horizons_server_data/` (not needed — each client builds its own via the server-side streaming), and any asset/library caches — those regenerate automatically on first launch and just bloat the zip. Name the result `modpack.zip`.
**Important, learned the hard way:** Prism/Fjord's exporter zips the instance's contents (`instance.cfg`, `mmc-pack.json`, `minecraft/`, etc.) **directly at the zip root** — it does *not* wrap them in a folder named after the instance. If you just `Expand-Archive`/`unzip` that straight into `instances/`, Fjord won't recognize anything (no `instances/LANParty/instance.cfg` to find, just loose files sitting in `instances/` itself) and the instance silently never appears in the launcher. Before hosting it, open the zip and confirm the top-level entries are `LANParty/instance.cfg`, `LANParty/mmc-pack.json`, `LANParty/minecraft/...` — if instead you see `instance.cfg`/`mmc-pack.json`/`minecraft/` sitting at the root with no `LANParty/` prefix, re-zip with everything nested one level deeper under a folder literally named `LANParty` first.
## 2. The server: `mivanchenko.de` (always-on, not a box you carry to the venue)
This setup runs on the existing homelab box at `mivanchenko.de` (SSH: `ssh server`, i.e. port 222, same machine already running Gitea/n8n/the SMB stack/etc) rather than a machine physically present at the party. That means **no DNS repoint, no file-server migration, no "moving to the venue" step** — `minecraft.mivanchenko.de` already resolves here and stays pointed here.
**Tradeoff to be aware of:** since the server isn't on the venue's local network, players connect over the internet, not LAN — latency depends on the venue's internet connection, not your switch. For a 3-day modded-Minecraft LAN party this is usually fine (Minecraft is forgiving of 20–60ms), but if the venue's internet is bad, this is the thing that bites. Worth testing from the venue ahead of time if you can.
Server location: `~/lan-minecraft/server/` on the box. Setup:
```bash
ssh server
cd ~/lan-minecraft
# copy server/setup-server.sh here (or git clone the mc-lan-party repo), then:
cd server && ./setup-server.sh
# then copy your client modpack's mod jars into mcserver/mods/, and:
sudo systemctl restart mc-lazymc
```
`setup-server.sh` installs the Forge 1.20.1 server and wraps it in **lazymc**, which sleeps the real server after 5 idle minutes and transparently wakes it the instant someone connects — no manual start/stop for the whole 3 days. It does **not** handle file distribution (see section 3 — that's Gitea now, not this script).
Since port 25565 is reached directly (Minecraft is raw TCP, not HTTP — Caddy on this box only proxies HTTP(S) on 80/443 and has no layer4/TCP module, so it genuinely can't front Minecraft traffic), there's no Caddy entry to add for `minecraft.mivanchenko.de`. lazymc just binds `0.0.0.0:25565` on the host directly, same way `crafty_container` already exposes its own Minecraft ports on this box.
Services temporarily stopped on this box to free up resources for the party (`docker stop`, not removed — bring back with `docker start <name>` after): `smb-crm`, `smb-db`, `breathemate-license`, `crafty_container`, `n8n-n8n-1`, `open-webui`, `mcpo`.
Useful commands during the party:
- `sudo systemctl status mc-lazymc` — check if the MC server is asleep/awake
- `sudo journalctl -u mc-lazymc -f` — watch it wake/sleep live
- `sudo systemctl restart mc-lazymc` — pick up new mods or a config change
### Running server commands (op, gamemode, whitelist, etc.)
The server has RCON enabled (`rcon.password` auto-generated in `mcserver/server.properties`), but there's no interactive console since lazymc/systemd run the Java process in the background. Use `mcserver/rcon.py` (plain Python3, no install needed — `mcrcon` isn't packaged for Debian 13):
```bash
ssh server
cd ~/lan-minecraft/repo/server/mcserver
python3 rcon.py "op <name>"
python3 rcon.py "gamemode creative <name>"
python3 rcon.py "op <name>" "gamemode creative <name>" # multiple commands in one call
```
Works for any server command — `list`, `whitelist add <name>`, `tp`, `weather clear`, `/chunky`-era commands if relevant, etc. The script reads the password straight from `server.properties`, nothing to copy/paste. Note it only works while the real server is awake — if lazymc has it asleep, wake it first (connect once, or `python3 rcon.py "list"` will just fail to connect).
### Live web map (BlueMap) — two renderers, automatic failover
`minecraft.mivanchenko.de` serves a browser-viewable map over HTTPS — same hostname the game client connects to, but a completely different port/protocol (80/443 via Caddy for the map, raw TCP 25565 direct to lazymc for gameplay), so the two never conflict.
There are **two** BlueMap renderers, and Caddy automatically picks whichever is actually up:
1. **BlueMap Forge mod** (`mcserver/mods/BlueMap-5.12-mc1.20-6-forge.jar`) — runs *inside* the live Minecraft server process. Shows **live player position markers** (requires the mod running server-side with access to real player session data — the standalone renderer below can't do this, it only ever sees static world files). Only up while the game server itself is awake. Webserver binds to the host directly on port 8100 (config at `mcserver/config/bluemap/`).
2. **Standalone Docker renderer** (`~/lan-minecraft/bluemap/`, container `bluemap-lan`) — always-on regardless of whether the game server is asleep, reads `mcserver/world/` read-only, renders + watches for changes continuously (`-r -u -w` flags). No live player markers (it has no connection to the running server), but the map itself stays current and always reachable even when nobody's playing.
Caddy tries the mod first (`172.20.0.1:8100` — the Docker bridge gateway IP, since the mod binds to the host, not a container on the `proxy` network) and falls back to `bluemap-lan:8100` the moment the mod's webserver stops responding (health-checked, `lb_policy first`, ~10s detection window) — i.e. the instant lazymc puts the server to sleep. Verified end-to-end: map IDs are `world`/`world_the_end`/`world_the_nether` when the mod is serving, `overworld`/`nether`/`end` when it's fallen back to the standalone container (distinct naming between the two made this easy to confirm via `/settings.json`).
```bash
ssh server
# standalone renderer
cd ~/lan-minecraft/bluemap && docker compose ps / logs -f / restart
# live mod — check via the game server's own logs/RCON, not a separate service
cd ~/lan-minecraft/repo/server/mcserver && python3 rcon.py "bluemap reload"
```
Caddy config lives in `~/caddy/Caddyfile` (shared with every other site on this box — edited in place, reload via `docker exec caddy caddy reload --config /etc/caddy/Caddyfile --adapter caddyfile`, never `docker compose up -d --force-recreate` for a config-only change, that'd bounce every other site too). None of this (BlueMap configs, Caddyfile) is committed to the `mc-lan-party` Gitea repo — it's server-local infra. If this box is ever rebuilt from scratch, recreate it from this section.
### Resource sizing for the venue box (5–15 players)
Create is the dominant cost here — it's heavier on the server **tick thread (CPU)** than on RAM, because kinetic networks and contraptions get simulated every tick regardless of how many players are nearby. Farmer's Delight/its addons are cheap by comparison. Numbers below assume this modpack (~10 mods) and are *total host box* numbers, not just the JVM heap:
| Concurrent players | JVM heap (`-Xmx`, set in `setup-server.sh`) | Host RAM (total, incl. OS + other running services) | CPU | Free disk | Network |
|---|---|---|---|---|---|
| 5–8 | 6G | 10–12 GB | 4 cores, 3.5 GHz+ single-core perf matters more than core count | ~10 GB (world + mods + Forge libs + logs) | Gigabit LAN recommended, Wi-Fi workable |
| 9–12 | 8G | 12–16 GB | 4–6 cores, 3.5 GHz+ | ~15 GB | Gigabit LAN recommended |
| 13–15 | 10–12G | 16 GB+ | 6+ cores, 3.5 GHz+ | ~20 GB | Gigabit LAN, wire the host box if possible |
Practical knobs to turn if the box is lighter than the table:
- Lower `server.properties` **`view-distance`** to 6–8 — this is the single biggest CPU/RAM lever on a modded server, and the Distant Horizons client mod (see `modpack.md`) makes a short server view-distance look fine anyway.
- Set `simulation-distance` a notch below `view-distance` if things chug — it controls how far Create's kinetic networks/entities get ticked, independent of render range.
- `MIN_RAM`/`MAX_RAM` in `setup-server.sh` default to `2G`/`4G` — bump `MAX_RAM` to match the table above for your headcount before running the script (or edit `lazymc.toml`'s `command` line afterward and `sudo systemctl restart mc-lazymc`).
- An SSD matters more than raw disk size here — Create's block-entity-heavy contraptions do a lot of small, frequent chunk saves; a spinning HDD will show up as tick-time spikes.
### Pre-generate terrain before the party starts (Distant Horizons Full-mode)
Distant Horizons (same build, both client and server `mods/`) handles pregeneration itself now — Chunky is not used (see `modpack.md`'s Performance section for why). Edit `mcserver/config/DistantHorizons.toml`, under `[common.worldGenerator]`:
```toml
chunkGeneratorMode = "INTERNAL_SERVER" # default is "FEATURES" (LOD-only, doesn't save real chunks) — change to this
generationCenterChunkX = 0 # or your build area's center, in chunk coords (block coords / 16)
generationCenterChunkZ = 0
generationMaxChunkRadius = 50 # 50 chunks ≈ 800 blocks. 0 (the default) means unbounded/follows render distance instead
```
`INTERNAL_SERVER` mode is the one that matters — unlike the other modes, it actually generates and saves real chunks to the region files (not just LOD approximation), which is the same job Chunky would've done.
This only runs while the actual Java process is up, and **lazymc will stop it again after the idle timeout if nobody's connected** — so for this one-time pregen session, bypass lazymc and run the server directly instead of waiting on the sleep/wake cycle:
```bash
sudo systemctl stop mc-lazymc
cd mcserver && bash run.sh nogui
```
Watch the console for `DH-Request Module Ticker` / world-gen-queue log lines — generation runs automatically in the background once the world is loaded, no command needed to kick it off (unlike Chunky's `/chunky start`). Let it run until the console goes quiet / chunks-per-second drops to near zero, then `stop` from the console (or Ctrl+C) and bring lazymc back: `sudo systemctl start mc-lazymc`.
Repeat for Nether/End build areas if you want those pregenerated too (useful since the roadmap sends people there for Nether's/End's Delight dishes) — DH generates per-dimension automatically as each is loaded; no separate per-dimension command needed, just make sure you (or a connected client) actually visit those dimensions once during the pregen session to trigger their world-gen queues.
## 3. File distribution: Gitea for scripts, Caddy static file for the modpack
`install.sh` and `install.ps1` (small text files) are committed to the `mc-lan-party` repo on Gitea (`git.mivanchenko.de`, already proxied by Caddy and public), served via its raw-file URLs.
`modpack.zip` is **not** — Gitea rejects uploads over 50MB (hit this directly: `modpack-final.zip` is 78MB), and that's not something worth fighting Gitea's config for. Instead it's served as a plain static file directly by the same Caddy instance already fronting `minecraft.mivanchenko.de`, using the `static/` folder Caddy already had mounted (`~/caddy/static/`, previously just `maintenance.html`) — no new container, no new mount, no restart of anything shared:
```bash
# on the server, after you've built/scp'd modpack-final.zip to ~/lan-minecraft/
cp ~/lan-minecraft/modpack-final.zip ~/caddy/static/modpack.zip
```
The `minecraft.mivanchenko.de` Caddy block path-routes: `/modpack.zip` → static file, everything else → the BlueMap mod/standalone failover (see above). Updating the zip later is just overwriting that file in place — no Caddy reload needed, `file_server` reads it fresh each request.
Give participants:
- **Windows:** `irm https://git.mivanchenko.de/mivanchenko/mc-lan-party/raw/branch/main/install.ps1 | iex`
- **Linux / macOS:** `curl -fsSL https://git.mivanchenko.de/mivanchenko/mc-lan-party/raw/branch/main/install.sh | bash`
On Windows, `install.ps1` first asks which local drive to install to (Enter keeps the old default, `%USERPROFILE%\mc-lan-party`; any other drive installs to `<drive>:\mc-lan-party`). Each script then asks for a nickname, derives a stable offline-mode UUID from it (so the same name = same player data across the whole 3-day party, even if they re-run the script), seeds that into a fresh `accounts.json`, downloads Fjord Launcher Unlocked from GitHub and the modpack from `https://minecraft.mivanchenko.de/modpack.zip`, and launches straight into the instance already connected to the same server.
Make sure your Minecraft server itself is running with `online-mode=false` in `server.properties` — offline accounts only work against offline-mode servers.
## Notes / things that can break
- The scripts pull Fjord Launcher Unlocked's **latest GitHub release** dynamically, so they stay correct even after version bumps — but if `hero-persson/FjordLauncherUnlocked` ever disappears, swap the repo slug at the top of the scripts for a current fork.
- `install.sh` needs `curl`, `unzip`/`find` (standard on any Linux/macOS box) and relies on FUSE for running the AppImage directly; if a participant's distro lacks FUSE, the AppImage will fail to launch — tell them to run `./FjordLauncher.AppImage --appimage-extract` once and point `LAUNCHER_BIN` at the extracted `AppRun`.
- Re-running either script is safe/idempotent: it skips re-downloading the modpack if the instance folder already exists, and just overwrites `accounts.json` with the (same, deterministic) account if the nickname is unchanged.
- lazymc's first wake after a sleep takes as long as the Forge server normally does to boot (expect 20–60s depending on the box) — the client will show a "starting" MOTD/kick message during that window; just reconnect after a few seconds.
- **lazymc is pinned to v0.2.10, not "latest."** v0.2.11 has a confirmed, still-open upstream regression ([timvisee/lazymc#65](https://github.com/timvisee/lazymc/issues/65), [#86](https://github.com/timvisee/lazymc/issues/86)) where it never detects a Forge server as ready — it just perpetually shows the "still starting" kick screen even when the real server has been up and warm for minutes, no matter how long you wait or how many times you reconnect. `forge = true` alone does not fix it. If `setup-server.sh` is ever changed back to pulling "latest," expect this to come back.
- `setup-server.sh` pins a Forge build (`47.4.28` for `1.20.1`) — check [files.minecraftforge.net](https://files.minecraftforge.net/net/minecraftforge/forge/index_1.20.1.html) if it's gone stale by the time you run this.
- Double-check the server's mod list is byte-for-byte the same mods as the client instance (same versions) — Forge is strict about mod-list mismatches between client and server and will refuse to connect otherwise. The cleanest way to guarantee this is pulling server-side jars straight out of the exported client zip (`unzip -j modpack.zip "minecraft/mods/*.jar" -d mcserver/mods`), not re-downloading them independently.
- **`unzip` can extract files with `0000` permissions from zips built on Windows** (seen when extracting a Prism/Fjord-exported instance zip — the jars came out as `----------`, unreadable even by their owner). If the server fails to load mods right after extracting a client zip with errors that don't match what actually worked on the client, check `ls -la mcserver/mods/` first — `chmod -R u+rwX mcserver/mods` fixes it.
- Create bundles **Flywheel** and **Ponder** inside its own jar via Forge's JarJar mechanism (visible in the client log as `"Found 11 dependencies adding them to mods collection"`) — don't go looking for separate `flywheel-*.jar`/`Ponder-*.jar` files to add to `mods/` on either side, they don't exist as standalone files in this setup.
- **`lazymc.toml` needs `forge = true` under `[server]`** if the backend is a Forge server (not in the defaults) — without it, lazymc can't correctly parse the status response (Forge's status ping includes an extra embedded mod-list payload vanilla doesn't have) and never recognizes the server as ready, so it just perpetually shows the "still starting" kick screen even once the real server is long since up. `setup-server.sh` sets this correctly now.
- **The very first connection after a cold sleep can still lose the race and show "Server is starting... try to reconnect."** — this is expected, not a bug: lazymc's hold method is hard-capped at ~25–30s by the Minecraft client's own connection timeout, and this modpack's Forge boot (~17–22s) eats most of that budget, especially cold. If you see that screen, just wait a few seconds and reconnect once — the server is already warm by then and the second attempt connects almost instantly. Worth mentioning to participants up front so nobody assumes it's broken.