diff --git a/landing/src/i18n/ui.ts b/landing/src/i18n/ui.ts index 7bddc1b..c4d2b90 100644 --- a/landing/src/i18n/ui.ts +++ b/landing/src/i18n/ui.ts @@ -174,7 +174,7 @@ export interface UI { skinUpload: string; skinUploading: string; skinOk: string; - skinPreviewNote: string; // "NMSR takes ~15 min to update" hint + skinPreviewNote: string; // "NMSR takes ~1 min to update" hint resetSkin: string; resetConfirm: string; // window.confirm text resetOk: string; @@ -356,7 +356,7 @@ export const ui: Record = { registerLink: "Register here", playerLabel: "Logged in as", skinTitle: "Your skin", - skinLead: "Upload a Minecraft skin PNG. The 3D preview below updates instantly; the in-game avatar takes ~15 minutes.", + skinLead: "Upload a Minecraft skin PNG. The 3D preview below updates instantly; the in-game avatar takes ~1 minute.", skinChoose: "Choose PNG…", modelLabel: "Model", modelClassic: "Classic", @@ -364,7 +364,7 @@ export const ui: Record = { skinUpload: "Upload skin", skinUploading: "Uploading…", skinOk: "Skin updated!", - skinPreviewNote: "Avatar preview updates in ~15 min.", + skinPreviewNote: "Avatar updates in ~1 min.", resetSkin: "Reset to default", resetConfirm: "Reset your skin to the default? This cannot be undone.", resetOk: "Skin reset to default.", @@ -544,7 +544,7 @@ export const ui: Record = { registerLink: "Regístrate aquí", playerLabel: "Sesión iniciada como", skinTitle: "Tu skin", - skinLead: "Sube un PNG de skin de Minecraft. La vista previa 3D se actualiza al instante; el avatar en el juego tarda ~15 minutos.", + skinLead: "Sube un PNG de skin de Minecraft. La vista previa 3D se actualiza al instante; el avatar en el juego tarda ~1 minuto.", skinChoose: "Elegir PNG…", modelLabel: "Modelo", modelClassic: "Clásico", @@ -552,7 +552,7 @@ export const ui: Record = { skinUpload: "Subir skin", skinUploading: "Subiendo…", skinOk: "¡Skin actualizada!", - skinPreviewNote: "La vista previa del avatar se actualiza en ~15 min.", + skinPreviewNote: "El avatar se actualiza en ~1 min.", resetSkin: "Restablecer skin", resetConfirm: "¿Restablecer tu skin a la predeterminada? Esta acción no se puede deshacer.", resetOk: "Skin restablecida.", @@ -732,7 +732,7 @@ export const ui: Record = { registerLink: "Erregistratu hemen", playerLabel: "Saioa hasita:", skinTitle: "Zure azala", - skinLead: "Igo Minecraft azal PNG bat. 3D aurrebista berehala eguneratzen da; jokoko avatarra ~15 minutuan.", + skinLead: "Igo Minecraft azal PNG bat. 3D aurrebista berehala eguneratzen da; jokoko avatarra ~1 minutuan.", skinChoose: "Aukeratu PNGa…", modelLabel: "Eredua", modelClassic: "Klasikoa", @@ -740,7 +740,7 @@ export const ui: Record = { skinUpload: "Igo azala", skinUploading: "Igotzen…", skinOk: "Azala eguneratuta!", - skinPreviewNote: "Avatararen aurrebista ~15 min barru eguneratzen da.", + skinPreviewNote: "Avatarra ~1 min barru eguneratzen da.", resetSkin: "Berrezarri azala", resetConfirm: "Zure azala lehenetsi nahi duzu? Ezin da desegin.", resetOk: "Azala berrezarrita.", diff --git a/landing/src/pages/[...lang]/account.astro b/landing/src/pages/[...lang]/account.astro index c9cfe94..46c87d5 100644 --- a/landing/src/pages/[...lang]/account.astro +++ b/landing/src/pages/[...lang]/account.astro @@ -304,7 +304,7 @@ const clientCfg = { showMsg(skinMsg, await apiMessage(res, cfg.errNetwork)); return; } - // Show the local file immediately as instant confirmation (NMSR lags ~15m). + // Show the local file immediately as instant confirmation (NMSR lags ~1m). const dataUrl = URL.createObjectURL(file); $("acc-avatar").src = dataUrl; skinMsg.classList.add("ok"); diff --git a/nmsr/config.toml.tmpl b/nmsr/config.toml.tmpl index 6dd7d79..203e416 100644 --- a/nmsr/config.toml.tmpl +++ b/nmsr/config.toml.tmpl @@ -16,7 +16,16 @@ port = 8080 [caching] cleanup_interval = "1h" -resolve_cache_duration = "15m" +# resolve_cache_duration caches the uuid -> profile (incl. skin URL) lookup, so +# it bounds how long a changed skin keeps showing the OLD avatar. Kept short so +# skin changes on the account page propagate within ~1 min (NMSR has no +# per-request force-refresh; a `?skin=` override is ignored by this build). Cost +# is one extra internal Drasl profile lookup per avatar per minute — trivial over +# mcnet for a 4-10 player server. For an instant global flush: restart nmsr. +resolve_cache_duration = "60s" +# texture cache stays long: Drasl skin URLs are content-hashed +# (/web/texture/skin/.png), so a new skin = new URL = fresh fetch; this +# 48h cache never serves a stale skin. texture_cache_duration = "48h" [caching.cache_biases] diff --git a/plan/00-overview.md b/plan/00-overview.md index 65287d6..50a2f6a 100644 --- a/plan/00-overview.md +++ b/plan/00-overview.md @@ -146,6 +146,7 @@ and uptime-kuma. - `15-letsencrypt.md` — Let's Encrypt (OVH DNS-01) + host nginx ingress - `17-mod-shortlist.md` — kitchen-sink gap doc + landing mod-list dataset (PLANNED) - `18-landing-rework.md` — join flow / rosters / account / mod list / footer (PLANNED) +- `19-routes.md` — HTTP route reference (nmsr / landing / auth + internal) ## Boot/dependency order diff --git a/plan/19-routes.md b/plan/19-routes.md new file mode 100644 index 0000000..103b6b2 --- /dev/null +++ b/plan/19-routes.md @@ -0,0 +1,84 @@ +# 19 — HTTP route reference (nmsr, landing, auth) + +> Quick map of the public + internal HTTP routes. caddy (`caddy/conf.d/*.caddy`) +> is the internal router; the host nginx terminates TLS and forwards each vhost +> to it by Host header. All `${BASE_DOMAIN}` = `ulicraft.net` in prod. + +## Vhost → service (caddy) + +| Vhost | caddy file | Upstream | +|-------|-----------|----------| +| `${BASE_DOMAIN}` (apex) | `10-static.caddy` | static `/srv/www` + `/srv/launcher` + mc-status | +| `auth.${BASE_DOMAIN}` | `00-core.caddy` | `drasl:25585` | +| `avatar.${BASE_DOMAIN}` | `40-avatar.caddy` | `nmsr:8080` | +| `status.${BASE_DOMAIN}` | `50-status.caddy` | `uptime-kuma:3001` | +| `distribution.${BASE_DOMAIN}` | `30-distribution.caddy` | static `/srv/distribution` (other repo) | + +--- + +## NMSR — `avatar.${BASE_DOMAIN}` (skin/avatar renderer) + +`reverse_proxy nmsr:8080`. Stateless renderer; pulls profiles+skins from Drasl +over mcnet (no public round-trip). + +- `GET /{mode}/{uuid}?size=N` — render an avatar PNG. + - `mode` = any NMSR render mode (`headiso` is the configured default via + `AVATAR_MODE`; others incl. `head`, `face`, `fullbody`, `fullbodyiso`). + - `uuid` = Drasl player UUID; `size` = px. + - Landing usage: `${avatarUrl}/${avatarMode}/?size=64|96`. +- **No per-request refresh.** A `?skin=` override is **ignored** by this + build (tested). Skin changes propagate via the resolve cache only. +- Caching (`nmsr/config.toml`): `resolve_cache_duration = 60s` (uuid→profile, + bounds stale-avatar window), `texture_cache_duration = 48h` (safe — Drasl skin + URLs are content-hashed, new skin = new URL). Instant global flush = restart + nmsr. See `plan/03-drasl.md` for the Drasl-backed mojank wiring. + +--- + +## Landing — `${BASE_DOMAIN}` (apex) + +`10-static.caddy`. Three handlers: + +- `handle_path /launcher/*` → `/srv/launcher` (file browse) — launcher downloads. +- `handle /api/mcstatus/*` → `uri strip_prefix /api/mcstatus` → `mc-status:8080`: + - `/api/mcstatus/v2/status/java/` → internal `/v2/status/java/` — live + server status JSON (mcstatus.io-compatible, `CORS: *`, `` ignored — it + only pings `MC_STATUS_TARGET`). Landing `statusApi`. + - `/api/mcstatus/players` → internal `/players` — registered-players roster, + sanitized `[{uuid,name}]`, ~60s cache (mc-status admin-logs-in to Drasl + `GET /players`). Landing `rosterApi`. + - `/healthz` — mc-status liveness (internal). +- `handle {}` → `/srv/www` static (Astro output). Pages: `/`, `/es`, `/eu` + (index), `/{lang}/register`, `/{lang}/account`, `/{lang}/fjord`. + +Browser → Drasl **directly** (CORS-scoped to apex via Drasl `CORSAllowOrigins`), +base `https://auth.${BASE_DOMAIN}/drasl/api/v2` (`site.draslApiUrl`): +- `register.astro`: `POST /users` (register), `POST /login`, `PATCH /players/{uuid}` (set initial skin). +- `account.astro`: `POST /login`, `PATCH /players/{uuid}` with `{skinBase64,skinModel}` (upload) or `{deleteSkin:true}` (reset). + +--- + +## Auth / Drasl — `auth.${BASE_DOMAIN}` + +`reverse_proxy drasl:25585`. One service exposes three surfaces: + +- **Web UI:** `/` (login / register / profile pages), `/web/texture/skin/.png`, `/web/texture/cape/.png`. +- **Yggdrasil / authlib-injector:** `/authlib-injector` (API root — server JVM + agent + clients point here), `/session/minecraft/...`, mojang-compat + `/users/profiles/minecraft/{name}` and `/session/minecraft/profile/{uuid}` + (also what NMSR calls internally). +- **Drasl REST API v2** (`/drasl/api/v2`): + - `POST /login` `{username,password}` → `{apiToken}` + - `POST /users` `{username,password[,inviteCode]}` — register (invite-gated when `RequireInvite=true`; admins from `DefaultAdmins` are promoted at startup, see `plan/03-drasl.md`) + - `GET /players` — **admin only**, full roster + - `PATCH /players/{uuid}` — skin CRUD (own player, or admin) + - `POST /invites` — admin, mint invite codes + +--- + +## Internal (service → service, over mcnet) + +- `minecraft` → `-javaagent authlib-injector=http://auth.${BASE_DOMAIN}/authlib-injector` (caddy mcnet alias → drasl). +- `nmsr` → `http://auth.${BASE_DOMAIN}` (mojang-compat session/textures/api). +- `mc-status` → `http://drasl:25585/drasl/api/v2` (`DRASL_API_URL`) for the roster; pings `MC_STATUS_TARGET` for live status. +- `mc-backup` → RCON `minecraft:25575`.