Skip to main content

Minecraft Worlds

Quickstart: Bring a World Up

  1. Allocate IP, SNI Route, and DNS:

    sudo ipctl create <name> --dns
    

    (Or /data/minecraft/ipctl create <name> --dns. Allocates backend IPv6 fd77:7777:5010::1337:3c:d00d:<hex>/64, configures HAProxy SNI routing, adds <name>.drheck.dev in Unbound on danknasty, and syncs public A/AAAA records to deSEC).

  2. Configure the backend server: In your backend server's server.properties:

    server-ip=fd77:7777:5010::1337:3c:d00d:<hex>
    server-port=25565
    online-mode=true
    

    (Backend servers run stock vanilla Minecraft, online-mode=true, listening on standard port 25565).

  3. Start the server: Launch your Minecraft server on UL. It binds to its dedicated IPv6 address.

  4. Connect: Players connect using <name>.drheck.dev (standard port 25565). Microsoft account authentication takes place directly between the client and your backend server.


Quickstart: Take a World Down

  1. Stop the backend server: Run stop in the Minecraft server console or terminate its process.

  2. What Players See When Offline: HAProxy monitors backend health checks. If the server process is stopped:

    • In the Multiplayer Server List: The ping MOTD displays: [Server Name] is currently offline. Ping Luke
    • When Clicking "Join Server": The client immediately disconnects with a clean red Minecraft disconnect screen: "[Server Name] is currently offline. Ping Luke" (No connection timeouts, hanging, or generic connection refused errors).
  3. Disable or remove router mapping & DNS (Teardown):

    • Temporary takedown (disable in router only):
      sudo ipctl route disable <name>
      
    • Complete teardown (release IP, remove HAProxy route, purge DNS):
      sudo ipctl delete <name> --dns
      

Architecture & Design Details

1. Unified Endpoint Model

All public and LAN client connections resolve to the single Minecraft Router endpoint pair:

  • IPv4: 10.0.0.113
  • IPv6: fd77:7777:5010::1337:f1ac:d00d:3c

Whether connecting via pokelovers.drheck.dev or any other server subdomain, players connect to this exact IP pair on standard port 25565.

2. Transparent TCP SNI Handshake Routing & Offline Fallback (HAProxy)

Rather than using an application-level proxy (which breaks vanilla Diffie-Hellman encryption and forces online-mode=false), HAProxy performs transparent pre-handshake packet inspection:

  1. HAProxy listens dual-stack on 10.0.0.113:25565 and [fd77:7777:5010::1337:f1ac:d00d:3c]:25565.
  2. When a player connects, HAProxy peeks at the initial unencrypted Minecraft handshake packet (0x00) via /etc/haproxy/minecraft.lua and extracts the requested hostname (e.g. pokelovers.drheck.dev) and connection state (Status vs Login).
  3. If the backend server is UP: It splices the raw TCP stream directly to that server's dedicated internal IPv6 address on port 25565. The client and backend perform native Microsoft/Mojang online-mode=true authentication directly over the spliced TCP connection.
  4. If the backend server is DOWN: HAProxy immediately handles the stream with Lua applet mc_offline which crafts and sends a valid Minecraft protocol packet:
    • Status Ping: JSON MOTD with red text: <name> is currently offline. Ping Luke
    • Login Join: Disconnect packet 0x00 rendering a red bold error screen: <name> is currently offline. Ping Luke

3. Backend IPv6 Address Scheme

Each backend Minecraft server is isolated on a dedicated Unique Local Address (ULA) on interface eth77 of ul:

  • Prefix: fd77:7777:5010::1337:3c:d00d:<hex>/64
  • Port: Each server binds to standard port 25565 on its assigned IPv6 address.
  • Security: Backend IPv6 addresses are completely internal and hidden behind the proxy router.

4. Web Map (BlueMap) & Authentik Gated HTTPS

Interactive 3D web mapping for Minecraft 26.3 is powered by BlueMap running in standalone watch mode:

  • Service: bluemap-pokelovers.service running from /data/minecraft/poke lovers/bluemap.
    • Automatically updates the rendered 3D map as chunks change in /data/minecraft/poke lovers/world.
    • Serves live map data & Server-Sent Events (SSE) updates on internal port 8100.
  • Reverse Proxy & Authentication: NGINX terminates SSL on port 443 at /etc/nginx/sites-available/pokelovers and gates access behind Authentik ForwardAuth (/outpost.goauthentik.io/auth/nginx).
    • Serves directly at https://pokelovers.drheck.dev.
    • Unauthenticated visitors are automatically redirected to auth.drheck.dev to log in before viewing the 3D map.

5. Network & DNS Automation (ipctl)

The tool /data/minecraft/ipctl (symlinked in /home/luke/bin/ipctl) automates the entire lifecycle:

  • Local Network (ul): Appends or removes # <name> and Address=fd77:7777:5010::1337:3c:d00d:<hex>/64 in /etc/systemd/network/20-wired.network and triggers networkctl reload.
  • Router Configuration (ul): Updates /etc/haproxy/minecraft_hosts.map and backend stanzas (with health checks and offline responder) in /etc/haproxy/haproxy.cfg, then reloads haproxy.service.
  • SSH Key Verification: Verifies that key SHA256:7Xr0vCxgfpPhbKE3hN9fE2IC9y4x3Kj+kYmXwzgJY3s (ultra-arikui) is loaded in the agent before running remote router commands.
  • Router DNS (danknasty): When --dns is passed:
    1. Updates the git working tree at /home/luke/git/router_config/unbound.conf.
    2. Verifies syntax using doas unbound-checkconf.
    3. Deploys the configuration to /var/unbound/etc/unbound.conf.
    4. Restarts Unbound via doas rcctl restart unbound.
    5. Verifies local resolution via dig @10.0.0.1.
    6. Executes python3 /home/luke/git/router_config/dns_danger_sync.py --force to synchronize public A and AAAA records on deSEC.io.

Backups & Disaster Recovery

The router configurations, Lua scripts, mapping tables, network configurations, and the ipctl tool are integrated into the automated backup infrastructure:

  • Backup Script: /home/luke/git/backups/ul/scripts/backup/minecraft_router
    • Creates timestamped, ACL/xattr-preserved, par2-verified archives in /data/backups/services/current/minecraft_router/.
    • Automatically invoked during the global daily backup run (run_all_backups).
  • Restore Script: /home/luke/git/backups/ul/scripts/restore/minecraft_router [timestamp]
    • Verifies PAR2 checksums/parity.
    • Extracts configurations to / and triggers live reloads of systemd-networkd and haproxy.

6. Minecraft Server Manager & On-Demand Architecture (Design Specification)

Design Goal: Provide a unified web control plane (https://mc.drheck.dev, gated by Authentik) and daemon that formalizes what a Minecraft server is on ul, automates ipctl networking and systemd lifecycle management, wakes sleeping servers on-demand via HAProxy when players join, and provides an interactive seed-preview and modpack studio when creating new worlds.

6.1 Canonical Definition of a "Server"

Every managed Minecraft world on ul is a self-contained unit consisting of five standardized components:

$$\text{Server}(\text{slug}) = \left{\text{Directory}, \quad \text{Manifest } (\texttt{server.json}), \quad \text{Network } (\texttt{ipctl}), \quad \text{Units } (\texttt{systemd}), \quad \text{Lifecycle Policy}\right}$$

Component Path / Identifier Description
1. Dedicated Directory /data/minecraft/<dir_name>/ Holds server.json, server .jar / libraries, mods/ or plugins/, server.properties, whitelist.json, ops.json, world/, and optional bluemap/. Existing worlds in /data/minecraft/* are adopted in-place without moving files.
2. Declarative Manifest /data/minecraft/<dir_name>/server.json Stores the human display name, DNS slug (<slug>), loader type (paper, vanilla, fabric, neoforge, forge, custom), Minecraft version, JVM path (/usr/lib/jvm/java-26-openjdk/bin/java), heap size (-Xms/-Xmx), custom modpack reference, seed, and lifecycle mode (manual, ondemand, always).
3. Network & DNS (ipctl) fd77:7777:5010::1337:3c:d00d:<hex>/64 Dedicated internal IPv6 address on eth77, bound in server.properties (server-ip= and server-port=25565, plus internal RCON on port 25575), mapped in /etc/haproxy/minecraft_hosts.map, and published as <slug>.drheck.dev on danknasty Unbound and deSEC.io.
4. Dedicated systemd Service minecraft@<slug>.service (+ optional bluemap@<slug>.service) Runs the server under systemd via /usr/local/bin/mc-launch <slug>. Selects the per-server JVM binary directly (never mutating the global archlinux-java symlink) and performs graceful RCON save-all + stop on shutdown.
5. Lifecycle Policy manual | ondemand | always Controls when the server consumes RAM on ul (see Section 6.2).
Click to expand example /data/minecraft/poke lovers/server.json manifest
{
  "name": "Poke Lovers",
  "slug": "pokelovers",
  "directory": "/data/minecraft/poke lovers",
  "domain": "pokelovers.drheck.dev",
  "bind_ipv6": "fd77:7777:5010::1337:3c:d00d:1",
  "game_port": 25565,
  "rcon_port": 25575,
  "loader": "vanilla",
  "mc_version": "26.3",
  "jar_file": "server_26.3.jar",
  "java_bin": "/usr/lib/jvm/java-26-openjdk/bin/java",
  "memory_min_gb": 20,
  "memory_max_gb": 20,
  "use_aikars_flags": true,
  "modpack": null,
  "seed": "-41829401829401",
  "lifecycle_mode": "ondemand",
  "idle_shutdown_minutes": 20,
  "bluemap_enabled": true,
  "bluemap_port": 8100
}

6.2 Resource Management & Server Lifecycle Modes

Because each active Minecraft JVM allocates $8\text{--}20\text{ GiB}$ of pinned heap memory (-Xms = -Xmx with +AlwaysPreTouch), running all $25+$ worlds in /data/minecraft simultaneously is impossible. Each server is assigned one of three lifecycle modes:

  1. manual (Cold / Archived):
    • minecraft@<slug>.service is disabled and stopped ($0\text{ GiB}$ RAM).
    • Only starts when explicitly clicked in the Web UI.
    • When stopped, HAProxy returns: <name> is currently offline. Ping Luke.
  2. ondemand (Wake-on-Join + Idle Auto-Stop):
    • Stopped by default ($0\text{ GiB}$ RAM).
    • Automatically started when a player clicks Join Server in Minecraft (intercepted by HAProxy Lua).
    • Automatically stopped (save-all $\longrightarrow$ stop) once $0$ players have been online continuously for $T_{\text{idle}}$ minutes (default $20\text{ min}$).
  3. always (Pinned Production):
    • minecraft@<slug>.service is enabled and active across host reboots (systemctl enable --now).
    • Used for primary worlds that should remain online $24/7$ regardless of player count.

6.3 HAProxy On-Demand Wake-on-Join Architecture

The Minecraft protocol sends a Handshake packet (0x00) at the start of every TCP connection containing a Next State VarInt:

  • $\text{Next State} = 1$ (Status Ping): Sent when a client refreshes their Multiplayer Server List (or when internet bots scan port 25565).
  • $\text{Next State} = 2$ (Login Join): Sent only when a player clicks Join Server.

/etc/haproxy/minecraft.lua already extracts both txn.mc_host and txn.mc_state. Extending mc_offline enables zero-overhead on-demand activation without waking servers on server-list pings:

$$\text{Client Packet } (\texttt{0x00}) \longrightarrow \begin{cases} \text{state} = 1 \text{ (Status Ping)} & \longrightarrow \text{Return JSON MOTD } (\text{Sleeping / Starting / Offline}) \text{ without waking} \\ \text{state} = 2 \text{ (Login Join)} & \longrightarrow \text{If } \texttt{ondemand}\text{: trigger } \texttt{mc-manager} \text{ wake socket + return countdown packet} \end{cases}$$

State Machine for ondemand Worlds

Server State Multiplayer List Ping (state = 1) MOTD Player Clicks "Join Server" (state = 2)
SLEEPING §e⚡ <name> is sleeping (On-Demand) §7— §aClick Join to wake! HAProxy Lua sends WAKE <slug> to /run/mc-manager/wake.sock, transitions server to STARTING, and disconnects client with: "⚡ Waking up <name>... Server will be ready in ~20 seconds!"
STARTING §6⏳ <name> is booting up... §7Please wait ~15s Disconnects client with live elapsed timer: "⏳ <name> is currently booting (12s elapsed)... Rejoin in a few seconds!"
ONLINE Native backend MOTD + live player count (spliced directly to backend JVM) Native TCP splice to [fd77:7777:5010::1337:3c:d00d:<hex>]:25565 with full Microsoft online-mode=true auth
OFFLINE (manual) §c<name> is currently offline. Ping Luke Disconnects client with: "<name> is currently offline. Ping Luke"

Idle Shutdown Timer

For any running server in ondemand mode, mc-manager tracks the time since the last player disconnected ($N_{\text{players}} = 0$) after an initial post-boot grace period ($T_{\text{grace}} = 5\text{ min}$ so players have plenty of time to connect after waking the server). When:

$$t_{\text{empty}} \ge T_{\text{idle}} \quad (\text{default } 20\text{ min})$$

mc-manager issues save-all and stop via RCON and stops minecraft@<slug>.service, freeing the JVM heap until the next join request.


6.4 Web Control Plane (https://mc.drheck.dev)

The web frontend is served over HTTPS behind Nginx + Authentik ForwardAuth and provides two primary workspaces:

1. Existing Servers Dashboard

  • Server Fleet Cards:
    • Displays every registered server with its live state badge (ONLINE, STARTING, SLEEPING (ON-DEMAND), OFFLINE), online player list, RAM/CPU usage, Minecraft version, loader type (Paper, Vanilla, Fabric, Forge), and <slug>.drheck.dev endpoint.
    • One-Click Controls: Start, Stop, Restart, and a dropdown to switch the server between Manual, On-Demand, and Always On.
    • Live Console & Config Drawer: WebSocket log stream (journalctl -u minecraft@<slug>.service -f) with RCON command input, visual server.properties / whitelist.json / ops.json editor, and BlueMap 3D map toggle.
  • Legacy Directory Adoption:
    • Scans /data/minecraft/* for directories without a server.json manifest (e.g., kochan 26.2, hermitcraft 9, Project Sekai, superworld 1.20.1) and provides a one-click Adopt & Productionize wizard that generates server.json, provisions an ipctl IPv6 route + DNS entry, and wires up minecraft@<slug>.service.

2. Create New Server Studio

Creating a new world follows a 3-step guided workflow in the Web UI:

  1. Step 1 — Identity, Networking & Resource Profile:
    • Display Name & Subdomain: Entering Create World auto-suggests slug createworld (createworld.drheck.dev).
    • Lifecycle Policy: Select On-Demand (default), Always On, or Manual, plus heap size (4 GiB – 24 GiB) and optional BlueMap 3D Web Map provisioning.
  2. Step 2 — Server Binary & Custom Modpacks:
    • Server Engine Selector:
      • Paper / Purpur / Vanilla / Fabric / NeoForge / Forge: Queries live upstream release APIs (or local cached jars in /data/minecraft/jars/) so you can pick the exact Minecraft version and build from a dropdown.
      • Automatically pairs the required JVM runtime (/usr/lib/jvm/java-26-openjdk, java-21-openjdk, java-17-openjdk, or java-8-openjdk).
    • Custom Modpack & Mod Library:
      • Local Custom Modpacks (/data/minecraft/modpacks/): Choose from your curated custom modpacks (.zip server packs, .mrpack Modrinth packs, or reusable mods/ + config/ bundles) or upload a new custom pack directly in the browser.
      • Individual Mods & Plugins: Upload .jar files or search Modrinth / Paper Hangar filtered by the selected Minecraft version and loader to drop mods/plugins straight into mods/ or plugins/.
  3. Step 3 — Interactive Seed Preview & Reroll Studio:
    • Seed Input or Reroll:
      • Enter a specific seed (integer or text string, converted using standard Java String.hashCode() rules) or click 🎲 Roll Random Seed to cycle through candidate seeds.
    • Instant 2D Biome & Structure Map Preview (cubiomes Engine):
      • Uses a headless native cubiomes C renderer on ul to generate a high-resolution 2D biome colour map ($\pm 2048$ blocks around $(0,0)$) in $< 50\text{ ms}$ per seed, overlaid with exact structure icons (Spawn point, Villages, Ancient Cities, Woodland Mansions, Ocean Monuments, Pillager Outposts, Strongholds, and Nether Fortresses/Bastions).
      • Lets you click 🎲 Roll Random Seed repeatedly until you spot an ideal spawn island, continent, or structure cluster, or bookmark candidate seeds side-by-side before creating the server.
    • Post-Creation Seed Swap ("Reroll World"):
      • On any newly created server (for example, if you log in or check its 3D BlueMap render and want a different terrain layout), clicking Swap Seed & Regenerate World in the server dashboard stops minecraft@<slug>.service, writes the new level-seed to server.properties and server.json, clears world/, world_nether/, and world_the_end/, and restarts the server on the new seed.

6.5 End-to-End Automated Provisioning Pipeline

When Create Server is clicked in the Web UI, the backend executes the following atomic pipeline:

  1. Directory & Manifest Creation: Creates /data/minecraft/<slug>/, writes server.json and eula.txt (eula=true), downloads the selected Paper/Vanilla/Fabric/Forge binary, and extracts any selected custom modpack into mods/, plugins/, and config/.
  2. Network & DNS Allocation (ipctl): Calls ipctl create <slug> --dns to assign the next available fd77:7777:5010::1337:3c:d00d:<hex>/64 address on eth77, register the HAProxy SNI backend (with mc_offline on-demand wake support), and publish <slug>.drheck.dev on danknasty Unbound and deSEC.io.
  3. server.properties Generation: Writes server-ip=fd77:7777:5010::1337:3c:d00d:<hex>, server-port=25565, level-seed=<seed>, online-mode=true, and RCON credentials bound exclusively to the internal ULA IPv6 address.
  4. Systemd Activation: Registers minecraft@<slug>.service (and bluemap@<slug>.service if enabled) and either starts the server immediately or places it into SLEEPING (ON-DEMAND) ready for the first player join.