feat(avatar): NMSR skin renderer at avatar.${BASE_DOMAIN}, Drasl-backed
Add NMSR-as-a-Service (NickAcPT/nmsr-rs) rendering players' skins/avatars,
sourced from Drasl instead of Mojang.
- docker/nmsr/Dockerfile build from source, pinned to commit 948ba4bc027b
(ears feature, lavapipe software Vulkan)
- nmsr/config.toml.tmpl [mojank] -> 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.
- docker-compose.nmsr.yml nmsr service (build), host bind 127.0.0.1:9898
only, config mount; mounts the caddy avatar snippet
- caddy/conf.d/40-avatar.caddy avatar.${BASE_DOMAIN} -> reverse_proxy nmsr:8080
- nginx-front + LE_SUBDOMAINS public TLS vhost (HSTS) + cert for avatar
- render-config.sh / .gitignore render + ignore nmsr/config.toml
Skin resolution stays internal over mcnet (caddy alias -> drasl), no public
round-trip.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -37,7 +37,7 @@ DISTRIBUTION_WEB_ROOT=/home/oier/projects/mc-mods/ulicraft-group/ulicraft-distri
|
|||||||
# Only needed to issue real TLS certs. DNS-01 needs no inbound ports.
|
# Only needed to issue real TLS certs. DNS-01 needs no inbound ports.
|
||||||
LE_EMAIL=you@example.com
|
LE_EMAIL=you@example.com
|
||||||
# space-separated subdomains; apex ${BASE_DOMAIN} is always included
|
# space-separated subdomains; apex ${BASE_DOMAIN} is always included
|
||||||
LE_SUBDOMAINS=auth pack distribution www
|
LE_SUBDOMAINS=auth pack distribution www avatar
|
||||||
# OVH API creds (DNS zone write). Create at https://eu.api.ovh.com/createToken/
|
# OVH API creds (DNS zone write). Create at https://eu.api.ovh.com/createToken/
|
||||||
# OVH_CK can be blank on first run — acme.sh prints an auth URL, then paste it.
|
# OVH_CK can be blank on first run — acme.sh prints an auth URL, then paste it.
|
||||||
OVH_END_POINT=ovh-eu
|
OVH_END_POINT=ovh-eu
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -5,6 +5,7 @@ runtime/authlib-injector.jar
|
|||||||
|
|
||||||
# rendered configs (from tooling/render-config.sh)
|
# rendered configs (from tooling/render-config.sh)
|
||||||
drasl/config/config.toml
|
drasl/config/config.toml
|
||||||
|
nmsr/config.toml
|
||||||
dnsmasq/dnsmasq.conf
|
dnsmasq/dnsmasq.conf
|
||||||
# packwiz pack files rendered from pack/**/*.tmpl by tooling/render-pack.sh
|
# packwiz pack files rendered from pack/**/*.tmpl by tooling/render-pack.sh
|
||||||
# (domain-dependent build output); source of truth is the .tmpl + custom/ jars.
|
# (domain-dependent build output); source of truth is the .tmpl + custom/ jars.
|
||||||
|
|||||||
5
caddy/conf.d/40-avatar.caddy
Normal file
5
caddy/conf.d/40-avatar.caddy
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# Avatar toggle — NMSR skin/avatar renderer (docker-compose.nmsr.yml).
|
||||||
|
# Mounted only when the nmsr override is present.
|
||||||
|
http://avatar.{$BASE_DOMAIN} {
|
||||||
|
reverse_proxy nmsr:8080
|
||||||
|
}
|
||||||
32
docker-compose.nmsr.yml
Normal file
32
docker-compose.nmsr.yml
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# ──────────────────────────────────────────────────────────────────
|
||||||
|
# NMSR-aas — avatar/skin renderer at avatar.${BASE_DOMAIN}.
|
||||||
|
# ──────────────────────────────────────────────────────────────────
|
||||||
|
# docker compose -f docker-compose.yml -f docker-compose.caddy.yml \
|
||||||
|
# -f docker-compose.nmsr.yml up -d --build
|
||||||
|
#
|
||||||
|
# Built from source (no upstream image) — see docker/nmsr/Dockerfile.
|
||||||
|
# Renders YOUR players' skins by pulling profiles from Drasl over the internal
|
||||||
|
# mcnet network (config: nmsr/config.toml, rendered from .tmpl).
|
||||||
|
#
|
||||||
|
# Caddy reverse-proxies avatar.${BASE_DOMAIN} -> nmsr:8080 internally. The host
|
||||||
|
# port is published on 127.0.0.1:9898 ONLY (local debugging / not reachable from
|
||||||
|
# outside) — public access goes through nginx -> caddy.
|
||||||
|
services:
|
||||||
|
# Mount the avatar vhost snippet into caddy (the caddy override owns the base
|
||||||
|
# config; this adds one more conf.d file like the distribution override does).
|
||||||
|
caddy:
|
||||||
|
volumes:
|
||||||
|
- ./caddy/conf.d/40-avatar.caddy:/etc/caddy/conf.d/40-avatar.caddy:ro
|
||||||
|
|
||||||
|
nmsr:
|
||||||
|
build:
|
||||||
|
context: ./docker/nmsr
|
||||||
|
image: ulicraft/nmsr:local
|
||||||
|
container_name: nmsr
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "127.0.0.1:9898:8080"
|
||||||
|
volumes:
|
||||||
|
- ./nmsr/config.toml:/nmsr/config.toml:ro
|
||||||
|
networks:
|
||||||
|
- mcnet
|
||||||
33
docker/nmsr/Dockerfile
Normal file
33
docker/nmsr/Dockerfile
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# NMSR-as-a-Service (https://github.com/NickAcPT/nmsr-rs) — built from source.
|
||||||
|
# Upstream ships no published image, so we vendor a pinned build. Adapted from
|
||||||
|
# the upstream Dockerfile but: pinned to a commit (reproducible), and the config
|
||||||
|
# is bind-mounted at runtime (not COPYed) so it can change without a rebuild.
|
||||||
|
#
|
||||||
|
# Pin — bump deliberately:
|
||||||
|
ARG NMSR_REF=948ba4bc027b
|
||||||
|
|
||||||
|
FROM rust:slim-bookworm AS builder
|
||||||
|
ARG NMSR_REF
|
||||||
|
WORKDIR /tmp
|
||||||
|
RUN apt-get update -y \
|
||||||
|
&& apt-get --no-install-recommends install -y git libssl-dev pkg-config ca-certificates
|
||||||
|
RUN git clone https://github.com/NickAcPT/nmsr-rs/
|
||||||
|
WORKDIR /tmp/nmsr-rs
|
||||||
|
RUN git checkout "${NMSR_REF}"
|
||||||
|
# target-cpu=native: this image is built on the host that runs it (self-host).
|
||||||
|
RUN RUSTFLAGS="-Ctarget-cpu=native" \
|
||||||
|
cargo build --release --bin nmsr-aas --features ears --package nmsr-aas
|
||||||
|
|
||||||
|
FROM rust:slim-bookworm
|
||||||
|
# mesa-vulkan-drivers provides lavapipe (software Vulkan) for headless rendering.
|
||||||
|
RUN apt-get update -y \
|
||||||
|
&& apt-get --no-install-recommends install -y mesa-vulkan-drivers ca-certificates
|
||||||
|
WORKDIR /nmsr
|
||||||
|
COPY --from=builder /tmp/nmsr-rs/target/release/nmsr-aas /nmsr/nmsr-aas
|
||||||
|
ENV NMSR_USE_SMAA=1 \
|
||||||
|
NMSR_SAMPLE_COUNT=1 \
|
||||||
|
WGPU_BACKEND=vulkan \
|
||||||
|
RUST_BACKTRACE=1
|
||||||
|
EXPOSE 8080
|
||||||
|
# config.toml is bind-mounted by docker-compose.nmsr.yml.
|
||||||
|
CMD ["/nmsr/nmsr-aas", "-c", "/nmsr/config.toml"]
|
||||||
@@ -32,7 +32,7 @@ upstream ulicraft_caddy {
|
|||||||
server {
|
server {
|
||||||
listen 80;
|
listen 80;
|
||||||
listen [::]:80;
|
listen [::]:80;
|
||||||
server_name ${BASE_DOMAIN} www.${BASE_DOMAIN} auth.${BASE_DOMAIN} pack.${BASE_DOMAIN} distribution.${BASE_DOMAIN};
|
server_name ${BASE_DOMAIN} www.${BASE_DOMAIN} auth.${BASE_DOMAIN} pack.${BASE_DOMAIN} distribution.${BASE_DOMAIN} avatar.${BASE_DOMAIN};
|
||||||
return 301 https://$host$request_uri;
|
return 301 https://$host$request_uri;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,3 +127,21 @@ server {
|
|||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 443 ssl http2;
|
||||||
|
listen [::]:443 ssl http2;
|
||||||
|
server_name avatar.${BASE_DOMAIN};
|
||||||
|
|
||||||
|
ssl_certificate ${APP_DIR}/certs/avatar.${BASE_DOMAIN}/cert.pem;
|
||||||
|
ssl_certificate_key ${APP_DIR}/certs/avatar.${BASE_DOMAIN}/key.pem;
|
||||||
|
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://ulicraft_caddy;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
37
nmsr/config.toml.tmpl
Normal file
37
nmsr/config.toml.tmpl
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
# NMSR-aas config — renders to nmsr/config.toml (gitignored) via
|
||||||
|
# tooling/render-config.sh. Only ${BASE_DOMAIN} is substituted.
|
||||||
|
#
|
||||||
|
# Skins come from Drasl, NOT Mojang. Drasl exposes Mojang-compatible routes at
|
||||||
|
# the bare BaseURL:
|
||||||
|
# session_server -> {url}/session/minecraft/profile/{uuid}
|
||||||
|
# mojang_api_server -> {url}/users/profiles/minecraft/{name}
|
||||||
|
# and embeds absolute skin URLs (http://auth.${BASE_DOMAIN}/web/texture/skin/…)
|
||||||
|
# in the profile, which NMSR fetches directly (textures_server is only a
|
||||||
|
# fallback). NMSR resolves auth.${BASE_DOMAIN} internally over the docker
|
||||||
|
# network (caddy holds the mcnet alias → drasl), so no public round-trip.
|
||||||
|
|
||||||
|
[server]
|
||||||
|
address = "0.0.0.0"
|
||||||
|
port = 8080
|
||||||
|
|
||||||
|
[caching]
|
||||||
|
cleanup_interval = "1h"
|
||||||
|
resolve_cache_duration = "15m"
|
||||||
|
texture_cache_duration = "48h"
|
||||||
|
|
||||||
|
[caching.cache_biases]
|
||||||
|
|
||||||
|
[mojank]
|
||||||
|
# Point every Mojang endpoint at Drasl (internal, plain http via the caddy alias).
|
||||||
|
session_server = "http://auth.${BASE_DOMAIN}"
|
||||||
|
textures_server = "http://auth.${BASE_DOMAIN}"
|
||||||
|
mojang_api_server = "http://auth.${BASE_DOMAIN}"
|
||||||
|
session_server_rate_limit = 10
|
||||||
|
# This is an offline-mode (authlib-injector) server — Drasl issues v3 offline
|
||||||
|
# UUIDs, so they must be accepted.
|
||||||
|
allow_offline_mode_uuids = true
|
||||||
|
use_dashless_uuids = false
|
||||||
|
|
||||||
|
[rendering]
|
||||||
|
sample_count = 1
|
||||||
|
use_smaa = true
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
# OVH_CK OVH consumer key (obtained on first run — see below)
|
# OVH_CK OVH consumer key (obtained on first run — see below)
|
||||||
# OVH_END_POINT OVH API endpoint (e.g. ovh-eu)
|
# OVH_END_POINT OVH API endpoint (e.g. ovh-eu)
|
||||||
# optional:
|
# optional:
|
||||||
# LE_SUBDOMAINS space-separated (default: "auth pack distribution www")
|
# LE_SUBDOMAINS space-separated (default: "auth pack distribution www avatar")
|
||||||
# LE_STAGING=1 use the LE staging CA (untrusted, for dry runs)
|
# LE_STAGING=1 use the LE staging CA (untrusted, for dry runs)
|
||||||
#
|
#
|
||||||
# First-time OVH consumer key: set OVH_AK/OVH_AS/OVH_END_POINT, leave OVH_CK
|
# First-time OVH consumer key: set OVH_AK/OVH_AS/OVH_END_POINT, leave OVH_CK
|
||||||
@@ -53,7 +53,7 @@ ACME="${ACME_SH:-$HOME/.acme.sh/acme.sh}"
|
|||||||
then re-run (or set ACME_SH=/path/to/acme.sh)."
|
then re-run (or set ACME_SH=/path/to/acme.sh)."
|
||||||
|
|
||||||
CERT_DIR="${CERT_DIR:-certs}"
|
CERT_DIR="${CERT_DIR:-certs}"
|
||||||
read -r -a SUBDOMAINS <<< "${LE_SUBDOMAINS:-auth pack distribution www}"
|
read -r -a SUBDOMAINS <<< "${LE_SUBDOMAINS:-auth pack distribution www avatar}"
|
||||||
|
|
||||||
if [ "${LE_STAGING:-0}" = "1" ]; then
|
if [ "${LE_STAGING:-0}" = "1" ]; then
|
||||||
SERVER="letsencrypt_test"; warn "STAGING mode — certs will NOT be trusted by browsers"
|
SERVER="letsencrypt_test"; warn "STAGING mode — certs will NOT be trusted by browsers"
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ render() { # $1=template $2=output
|
|||||||
}
|
}
|
||||||
|
|
||||||
render drasl/config/config.toml.tmpl drasl/config/config.toml
|
render drasl/config/config.toml.tmpl drasl/config/config.toml
|
||||||
|
render nmsr/config.toml.tmpl nmsr/config.toml
|
||||||
render dnsmasq/dnsmasq.conf.tmpl dnsmasq/dnsmasq.conf
|
render dnsmasq/dnsmasq.conf.tmpl dnsmasq/dnsmasq.conf
|
||||||
|
|
||||||
# pack templates (mods/*.pw.toml.tmpl, CustomSkinLoader, …) + packwiz index.
|
# pack templates (mods/*.pw.toml.tmpl, CustomSkinLoader, …) + packwiz index.
|
||||||
|
|||||||
Reference in New Issue
Block a user