13aa5c1af8
BlueMap Forge mod runs in-process on the server for live player position markers (not possible with the standalone renderer alone). Caddy now tries the mod first (via the Docker bridge gateway IP, since the mod binds to the host not a container) and automatically falls back to the always-on standalone bluemap-lan container the instant the game server sleeps - verified via distinct map-ID naming between the two. Flags that the client modpack needs the same BlueMap jar added before building modpack-final.zip (mod-list match).
140 lines
17 KiB
Markdown
140 lines
17 KiB
Markdown
# 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`, with the instance's folder (`LANParty/...`) as the zip's top-level entry.
|
||
|
||
## 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, not a local file server
|
||
|
||
`install.sh`, `install.ps1`, and (once built) `modpack.zip` are committed straight to the `mc-lan-party` repo on Gitea (`git.mivanchenko.de`, already proxied by Caddy and public), served via its raw-file URLs. No separate file-server process on the venue/server box — one less moving part, and it's fine for now per your call; worth revisiting (e.g. Gitea releases/LFS, or an object store) if `modpack.zip` turns out large enough that Gitea's raw-file serving gets awkward.
|
||
|
||
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`
|
||
|
||
Each script 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 + the modpack from Gitea, and launches straight into the instance already connected to `minecraft.mivanchenko.de`.
|
||
|
||
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.
|