# 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.

### Simulate the DNS via /etc/hosts (single machine, testing)

No DNS server? Append the records straight to `/etc/hosts` (marker-wrapped):

```sh
tooling/dns-records.sh online hosts | sudo tee -a /etc/hosts   # services only
tooling/dns-records.sh airgap hosts | sudo tee -a /etc/hosts   # + Mojang spoofs
```

Remove them later:

```sh
sudo sed -i '/# >>> ulicraft/,/# <<< ulicraft/d' /etc/hosts
```

Notes:
- `/etc/hosts` has **no wildcard** — only the listed names resolve.
- `airgap` adds the Mojang/launcher/NeoForge hostnames pointing at the LAN; this
  **breaks normal internet access to those domains** while present. Use it only
  while offline; for an online box use `online`.
- `mc.<domain>` works (client defaults to :25565); SRV isn't used via `/etc/hosts`.

## 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.
