# Ulicraft Server — Project Context

> Self-hosted modded Minecraft (NeoForge 1.21.1) with self-hosted auth/skins
> (Drasl), a packwiz modpack, avatar rendering, a guest landing page, and uptime
> monitoring. Public, internet-present deployment behind the host's nginx + TLS.

**`plan/` is the source of truth for the design.** Start at `plan/00-overview.md`.
This file is a fast orientation + the durable decisions/gotchas; when it disagrees
with `plan/` or the code, those win.

## Goals & Constraints

- **Players**: 4–10 friends
- **Modpack vibe**: Kitchen-sink (tech + magic + exploration + QoL)
- **Pack approach**: Manually curated (~50–100 mods), NOT a forked existing pack
- **Minecraft version**: 1.21.1, **NeoForge** (not classic Forge)
- **Auth/skins**: Drasl (Yggdrasil-compatible), **password login** — no OIDC
- **DNS**: handled OUTSIDE this repo; point `${BASE_DOMAIN}` + subdomains at the host
- **Persistence horizon**: long-term service, not disposable

## Architecture

One unified `docker-compose.yml`. The host's own nginx terminates TLS (Let's
Encrypt) and reverse-proxies every vhost to caddy (published localhost-only) by
Host header. caddy is the internal router; containers reach the stack's own names
via caddy's `mcnet` aliases (`auth.`, `pack.`).

```
Internet ── host nginx (TLS, LE certs, HSTS) ──► caddy (127.0.0.1:8880)
                                                   │ routes by Host:
   ${BASE_DOMAIN}        ─► /srv/www landing + /launcher/ downloads
   auth.${BASE_DOMAIN}   ─► drasl        (Yggdrasil API + web UI)
   pack.${BASE_DOMAIN}   ─► packwiz files + /custom/ jars
   avatar.${BASE_DOMAIN} ─► nmsr         (skin/avatar renderer, Drasl-backed)
   status.${BASE_DOMAIN} ─► uptime-kuma  (status page + monitors)
   distribution.${BASE}  ─► static site from ANOTHER repo (DISTRIBUTION_WEB_ROOT)

minecraft :25565 (+ 24454/udp Simple Voice Chat)
   └ ONLINE_MODE=false, -javaagent authlib-injector → http://auth.${BASE_DOMAIN}/authlib-injector
   └ mods via PACKWIZ_URL → http://pack.${BASE_DOMAIN}/pack.toml
mc-backup ── RCON → minecraft (6h interval, prune 14d, world-only)
```

Bring-up: render configs → build landing → fetch launcher → `docker compose up -d --build`.
See `plan/12-build-order.md` and `plan/14-deploy.md`. Deploy to prod host `cochi`
via the `deploy` skill.

## Key Decisions

- **NeoForge over Forge** — the 1.21.x kitchen-sink ecosystem is overwhelmingly
  NeoForge; the Forge team migrated. `itzg/minecraft-server` via `TYPE=NEOFORGE`.
- **Drasl over Ely.by / vanilla offline** — self-hosted Go service, Yggdrasil
  drop-in, skins + capes, actively maintained. Run with **password login**
  (no OIDC/Keycloak in this stack).
- **Manual curation** (user choice, flagged expensive) — if it gets painful,
  fork-and-trim Leaking Kitchen Sink is still on the table (closest vibe match).
- **itzg/minecraft-server** base image — de-facto standard, auto-installs NeoForge.
- **packwiz** for the modpack — `PACKWIZ_URL` drives server mod install; clients
  import the same `pack.toml`. Jars come from the upstream CDN per `.pw.toml`.

## Critical Gotchas

### Minecraft 1.21+ secure profile
- Server: `ENFORCE_SECURE_PROFILE=FALSE` (`enforce-secure-profile=false`)
- Drasl: `SignPublicKeys = false`
- Linked. Drasl docs: *"Mixed authentication does not work with
  `SignPublicKeys = true` on Minecraft 1.21+."*

### authlib-injector JVM agent
- Syntax: `-javaagent:/extras/authlib-injector.jar=<yggdrasil-api-url>`
- URL after `=` is the **API root** = `<BaseURL>/authlib-injector`. For this
  stack: `http://auth.${BASE_DOMAIN}/authlib-injector` (internal, over mcnet).
- **Must match between server and client** (clients use the public
  `https://auth.${BASE_DOMAIN}/authlib-injector`), else session validation fails
  silently with "Invalid session".
- Releases: https://github.com/yushijinhun/authlib-injector — jar lives in
  `runtime/` (mounted at `/extras/authlib-injector.jar`).

### NeoForge version pinning
Mods are picky about exact minor versions. Compose pins `NEOFORGE_VERSION:
"21.1.209"`. **Verify against https://projects.neoforged.net/neoforged/neoforge
before deploying.** Never `"latest"` for a stable server.

### Mod source preference
Prefer **Modrinth over CurseForge** (`packwiz modrinth add <slug>`). CF needs
`CF_API_KEY` and is flakier. Tag client-only mods `side = "client"` so the server
doesn't pull and crash on them.

### Memory sizing
~50–100 mods, 8 players, 1.21.1: `INIT_MEMORY: 4G`, `MAX_MEMORY: 10G`,
`USE_AIKAR_FLAGS: TRUE`.

## Config (.env)

`BASE_DOMAIN`, `RCON_PASSWORD`, `CADDY_HTTP_PORT`/`CADDY_HTTP_BIND`,
`DISTRIBUTION_WEB_ROOT`, the Let's Encrypt block (`LE_EMAIL`, `LE_SUBDOMAINS`,
`OVH_*`), optional `CF_API_KEY`. Rendered configs (drasl, nmsr, packwiz) come
from `*.tmpl` via `tooling/render-config.sh` (substitutes `${BASE_DOMAIN}` only).

## Client Distribution (guests)

Launcher: **FjordLauncherUnlocked** (Prism fork, first-class authlib-injector),
mirrored at apex `/launcher/`. Per guest:
1. Download from `https://${BASE_DOMAIN}` → `/launcher/`.
2. Add an authlib-injector account, URL
   `https://auth.${BASE_DOMAIN}/authlib-injector` (register/login at
   `https://auth.${BASE_DOMAIN}`).
3. Import the pack `https://pack.${BASE_DOMAIN}/pack.toml`.
4. Connect to `${BASE_DOMAIN}` (`:25565`).

## Open / Pending Work

- [ ] **Mod shortlist** for the kitchen-sink pack: Performance (Embeddium,
  FerriteCore, ModernFix), Tech (Mekanism, Create, IE, AE2), Magic (Ars Nouveau,
  Botania, Iron's Spells), Storage (Sophisticated, Functional), Exploration
  (YUNG's, Repurposed Structures), QoL (JEI, Jade, IPN, AppleSkin), World gen
  (Tectonic, Terralith — verify NeoForge 1.21.1), Compat (Polymorph), Map (Xaero),
  Voice (Simple Voice Chat — 24454/udp already in compose).
- [ ] Server-side perf mods (C2ME, etc.).
- [ ] **Verify NeoForge version** against current stable before first deploy.
- [ ] **Bootstrap Drasl admin** account.

## Reference URLs

- Drasl: https://github.com/unmojang/drasl — config docs:
  https://github.com/unmojang/drasl/blob/master/doc/configuration.md
- authlib-injector: https://github.com/yushijinhun/authlib-injector
- itzg/minecraft-server: https://github.com/itzg/docker-minecraft-server
  (NeoForge: …/docs/types-and-platforms/server-types/forge.md)
- itzg/mc-backup: https://github.com/itzg/docker-mc-backup
- NeoForge versions: https://projects.neoforged.net/neoforged/neoforge
- NMSR: https://github.com/NickAcPT/nmsr-rs
- Uptime Kuma: https://github.com/louislam/uptime-kuma
- FjordLauncherUnlocked: https://github.com/hero-persson/FjordLauncherUnlocked
- Reference packs (inspiration, NOT forking): ATM10, Leaking Kitchen Sink.

## Communication Preferences (carry-over)

- Concise and direct; diagnose + resolve without demanding repro steps.
- Prefer iterative single-change updates over large rewrites.
- Cautious scripts that halt on ambiguity rather than proceeding silently.
- Avoid over-engineering when a simple answer suffices.
- Spanish or English both fine.
