diff --git a/README.md b/README.md index c171a9e..2a33810 100644 --- a/README.md +++ b/README.md @@ -14,8 +14,13 @@ Mojang/launcher/NeoForge assets so guest laptops need no internet. | `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. +Optional layers (each its own `docker-compose..yml`): +- `avahi` (mDNS `.local`), `dnsmasq` (turnkey DNS) — only if you don't run your + own DNS. +- `nmsr` (NickAcPT/nmsr-rs, built from source) — skin/avatar renderer at + `avatar.${BASE_DOMAIN}`, sourced from Drasl. See **Avatar renderer** below. +- `distribution` — extra static vhost at `distribution.${BASE_DOMAIN}` whose web + root (`DISTRIBUTION_WEB_ROOT`) lives in another repo. Config lives in `.env`: `BASE_DOMAIN` (default `ulicraft.lan`), `HOST_LAN_IP`, `RCON_PASSWORD`, `ENABLE_MDNS`. Services are subdomains: `auth.`, `packwiz.`, @@ -84,12 +89,14 @@ tooling/build-stack.sh --up core # base only (debugging) The LAN path above runs caddy on `:80`. To expose the stack publicly with real certs, the machine's own nginx terminates TLS and reverse-proxies every vhost to -caddy by Host header. Caddy stays the single router (apex landing, `auth.` -→ drasl, `pack.` packwiz, `distribution.` static). +caddy by Host header. Caddy stays the single router (apex landing, `www.` → +apex, `auth.` → drasl, `pack.` packwiz, `distribution.` static, `avatar.` → +nmsr). HTTPS-only: HTTP 301s to HTTPS and every TLS vhost sends HSTS. 1. **Issue certs** (OVH DNS-01, no inbound ports needed): ```sh - # .env: BASE_DOMAIN, LE_EMAIL, OVH_* creds, LE_SUBDOMAINS="auth pack distribution" + # .env: BASE_DOMAIN, LE_EMAIL, OVH_* creds, + # LE_SUBDOMAINS="auth pack distribution www avatar" tooling/issue-letsencrypt.sh # → certs//{cert,key}.pem ``` @@ -99,10 +106,11 @@ caddy by Host header. Caddy stays the single router (apex landing, `auth.` CADDY_HTTP_BIND=127.0.0.1 ``` -3. **Bring up the caddy ingress** (+ static, + distribution if used): +3. **Bring up the caddy ingress** (+ static, + distribution/nmsr if used): ```sh docker compose -f docker-compose.yml -f docker-compose.caddy.yml \ - -f docker-compose.static.yml -f docker-compose.distribution.yml up -d + -f docker-compose.static.yml -f docker-compose.distribution.yml \ + -f docker-compose.nmsr.yml up -d --build ``` 4. **Render + install the nginx vhost** with `tooling/render-nginx.sh`. It pulls @@ -118,6 +126,29 @@ caddy by Host header. Caddy stays the single router (apex landing, `auth.` Re-run step 4 whenever you add a subdomain so its server block is rendered. +## Avatar renderer (NMSR) + +`avatar.${BASE_DOMAIN}` renders players' skins/avatars via +[NMSR-aas](https://github.com/NickAcPT/nmsr-rs), sourced from **Drasl** (not +Mojang). No upstream image exists, so it's built from source — `docker/nmsr/` +pins a commit; bump `ARG NMSR_REF` to update. + +- **Config**: `nmsr/config.toml.tmpl` → rendered to `nmsr/config.toml` by + `tooling/render-config.sh`. `[mojank]` points every Mojang endpoint at + `http://auth.${BASE_DOMAIN}`; Drasl serves the Mojang-compatible routes at its + bare BaseURL and embeds absolute skin URLs that NMSR fetches directly. + `allow_offline_mode_uuids = true` (Drasl issues offline v3 UUIDs). +- **Network**: skin resolution stays internal over `mcnet` (the caddy alias + `auth.${BASE_DOMAIN}` → drasl) — no public round-trip. caddy reverse-proxies + `avatar.` → `nmsr:8080`. The host port is published on `127.0.0.1:9898` only + (local debugging; not reachable from outside). +- **Endpoints** (NMSR): e.g. `https://avatar.${BASE_DOMAIN}/skin/`, + `/face/`, `/fullbody/` — by username or UUID. + +Bring up: add `-f docker-compose.nmsr.yml` (with `--build`) as shown above. The +first build is heavy (Rust compile). Headless rendering uses lavapipe (software +Vulkan); if renders fail, check `docker logs nmsr` for Vulkan device init. + `distribution.${BASE_DOMAIN}` serves a static site whose web root lives in another repo: set `DISTRIBUTION_WEB_ROOT` (absolute host path) in `.env`; it's bind-mounted read-only via `docker-compose.distribution.yml`. @@ -137,11 +168,15 @@ bind-mounted read-only via `docker-compose.distribution.yml`. ``` docker-compose.yml # core: drasl, minecraft, mc-backup, caddy .static.yml .mirror.yml # online/airgap layers + .caddy.yml .nginx.yml # ingress paths (caddy-front vs nginx static) + .nmsr.yml .distribution.yml # avatar renderer / extra static vhost .avahi.yml .dnsmasq.yml # optional DNS layers -caddy/Caddyfile + conf.d/*.caddy # ingress vhost snippets (core/static/mirror) +caddy/Caddyfile + conf.d/*.caddy # ingress vhost snippets (core/static/mirror/ + # distribution/avatar) drasl/config/config.toml.tmpl # auth config (rendered) +nmsr/config.toml.tmpl # avatar renderer config, Drasl-backed (rendered) dnsmasq/dnsmasq.conf.tmpl # optional DNS config (rendered) -docker/avahi/ # mDNS responder image +docker/avahi/ docker/nmsr/ # built-from-source images (mDNS, NMSR) pack/ # packwiz modpack source landing/ # Astro site → www/ tooling/ # build-stack, render-config, render-nginx,