# Ulicraft Server Self-hosted modded Minecraft (NeoForge 1.21.1) for a LAN party. Runs **online** (internet present) or **fully air-gapped** (offline). Self-hosted auth/skins (Drasl), a packwiz modpack, a guest landing page, and a transparent mirror of Mojang/launcher/NeoForge assets so guest laptops need no internet. ## Stack | Service | Image | Role | |---|---|---| | `minecraft` | itzg/minecraft-server | NeoForge 1.21.1 server, `:25565` | | `drasl` | unmojang/drasl | Yggdrasil auth + skins (password login) | | `caddy` | caddy:alpine | ingress: landing, auth/packwiz proxy, asset mirror (`tls internal`) | | `mc-backup` | itzg/mc-backup | world backups every 6h via RCON | Optional layers: `avahi` (mDNS `.local`), `dnsmasq` (turnkey DNS) — only if you don't run your own DNS. Config lives in `.env`: `BASE_DOMAIN` (default `ulicraft.lan`), `HOST_LAN_IP`, `RCON_PASSWORD`, `ENABLE_MDNS`. Services are subdomains: `auth.`, `packwiz.`, `mc.`, plus the apex landing page. ## DNS (pick one) Names must resolve to `HOST_LAN_IP`. Use **your own party DNS server** (recommended): ```sh tooling/dns-records.sh online # service names only (internet present) tooling/dns-records.sh airgap # + Mojang/launcher/NeoForge spoofs (offline) ``` Paste the printed records into your DNS. **Use `.lan`, not `.local`** — `.local` is intercepted by mDNS and won't resolve via unicast DNS. Alternatives: `ENABLE_MDNS=true` + `BASE_DOMAIN=*.local` (zero-config mDNS, macOS/Linux native, Windows needs Bonjour) — but mDNS can't serve the air-gap upstream spoofs. Or layer in `docker-compose.dnsmasq.yml` for a turnkey DNS. ## Prerequisites Docker + Docker Compose; for prep also `pnpm`, `packwiz`, `jq`, `curl`, `envsubst`, `sha1sum`. ## Launch ```sh cp .env.example .env # set BASE_DOMAIN, HOST_LAN_IP, RCON_PASSWORD tooling/build-stack.sh --prep # ONLINE, once: render, build landing, # mirror mods+assets, fetch launcher + # authlib, pre-bake the server volume tooling/build-stack.sh --up online # internet present, no mirror tooling/build-stack.sh --up airgap # offline; full asset mirror on :443 (default) tooling/build-stack.sh --up core # base only (debugging) ``` `--up` prints the DNS records to add for that mode. Down: `docker compose -f docker-compose.yml -f docker-compose.static.yml -f docker-compose.mirror.yml down` ## Join (guests) 1. Open `http://ulicraft.lan`, download FjordLauncherUnlocked for your OS. 2. Add an **authlib-injector** account, URL `http://auth.ulicraft.lan/authlib-injector` (register/login at `http://auth.ulicraft.lan`). 3. **Air-gap only:** trust the local CA — download `http://ulicraft.lan/ca.crt` and add it to your OS trust store (so the HTTPS asset mirror is trusted). 4. Import the pack: `http://packwiz.ulicraft.lan/pack.toml`. 5. Connect to **`mc.ulicraft.lan`** (no port needed). ## Layout ``` docker-compose.yml # core: drasl, minecraft, mc-backup, caddy .static.yml .mirror.yml # online/airgap layers .avahi.yml .dnsmasq.yml # optional DNS layers caddy/Caddyfile + conf.d/*.caddy # ingress vhost snippets (core/static/mirror) drasl/config/config.toml.tmpl # auth config (rendered) dnsmasq/dnsmasq.conf.tmpl # optional DNS config (rendered) docker/avahi/ # mDNS responder image pack/ # packwiz modpack source landing/ # Astro site → www/ tooling/ # build-stack, render-config, dns-records, # mirror-mods, mirror-airgap, fetch-launcher mirror/ # vendored jars + launcher + asset mirror (gitignored) plan/ # full design docs (00–12) ``` See `plan/00-overview.md` for the full design.