docs(readme): document NMSR avatar renderer + ingress vhosts

Add an Avatar renderer (NMSR) section: Drasl-backed config, internal mcnet
resolution, localhost-only host port, endpoints, lavapipe note. List nmsr +
distribution as optional layers, refresh the Public TLS vhost list (www/avatar,
HTTPS-only), and update the Layout tree.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-09 01:42:00 +02:00
parent 6fac1647aa
commit 6df885eb13

View File

@@ -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.<name>.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/<name>/{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/<player>`,
`/face/<player>`, `/fullbody/<player>` — 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,