Commit Graph

20 Commits

Author SHA1 Message Date
037b1777d6 feat(landing): rename crew to "El valle" + source downloads from launcher.json
Rename all "crew" mentions (en/es/eu) to the untranslated proper name
"El valle" across hero, members, and features copy in i18n/ui.ts.

Rewire the homepage launcher download list to the custom-launcher build
output launcher.json (productName/version/files[]) instead of the
never-produced launcher-manifest.json shape. site.ts now reads launcher.json
and normalizes files[] -> internal builds[]; launcher.example.json is the
committed fallback. Drop the stale launcher-manifest.example/schema, update
.gitignore and docs (CLAUDE.md, plan/18).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 01:55:05 +02:00
220b43d007 docs(deploy): document update-all.sh as the post-pull orchestrator
Rewrite the routine-redeploy section around update-all.sh (rolling vs --hard),
list the steps it runs, note the python>=3.11 requirement for build-modlist on
cochi (default python3 is 3.10), and that www/ + mods.json are generated every
run. The deploy skill = pull + fetch-authlib + ./update-all.sh --hard.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 01:06:39 +02:00
c8a6c77a8e feat(landing): rework — es default, 2-step join, roster + account polish
- Default language Spanish at root /, English moves to /en/ (eu unchanged):
  getStaticPaths {lang:undefined}->es + LANG_*_PATH maps swapped in ui.ts.
- Section order: hero -> join -> status -> members -> mods -> features.
- Join flow 3 -> 2 steps (drop the Connect/IP step; launcher handles the
  address). Remove the hero IP CopyChip. Header CTA renamed Play/Jugar/Jolastu
  and gains Register + Login links on all four pages.
- Account page: hide the whole login block once authenticated (not just the
  form), add an avatar Refresh button (cache-busted NMSR re-fetch), flatten the
  avatar (no bevel). New i18n key account.refresh.
- Register page: new REGISTRATION_SHOW_INVITE env (default false/hidden) toggles
  the invite-code field independently of the REGISTRATION_MODE backend gate.
- Members roster: drop the `admin` account, render full-body via a new
  ROSTER_AVATAR_MODE (default fullbodyiso); portrait tiles.
- Style: flatten emboss on feature icons (.picon) + player tiles (.roster-tile).
  PixelIcon gains optional image support (/icons/<name>.png|svg).
- Favicon now /favicon.png (committed) on all pages.
- Docs: plan/09-landing.md Assets section (logo/favicon/features-icons drop
  points) + default-language note; .env.example documents the two new vars.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 22:03:18 +02:00
c233b1bfbc docs(drasl): record admin bootstrap + config-key/CORS/NMSR gotchas
Capture what this session learned the hard way, in plan/03-drasl.md and CLAUDE.md:

- Correct admin key is DefaultAdmins (not DefaultAdminUsernames); wrong/unknown
  keys are silently ignored by drasl (logged as "unknown config option").
- CORSAllowOrigins must be top-level, before any [Section], or it's ignored.
- First-admin bootstrap under invite mode is a chicken-egg → temp-flip
  RequireInvite=false, register, revert.
- NMSR needs the auth. host-gateway pin + https mojank endpoints or avatars
  render the default skin (cross-ref plan/19-routes.md).
- Fix the stale example config and mark the admin-bootstrap task done.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 21:20:54 +02:00
5d5602fbd7 feat(nmsr): cut avatar refresh lag to ~1min + document HTTP routes
NMSR has no per-request avatar refresh (the `?skin=` override is ignored by this
build), so the only lever for a changed skin to appear is the resolve cache.
Drop resolve_cache_duration 15m -> 60s so account-page skin changes propagate
within ~1 min; texture cache stays 48h (Drasl skin URLs are content-hashed, so a
new skin is always a new URL — never stale). Cost is one extra internal Drasl
profile lookup per avatar per minute, trivial for 4-10 players.

Update the now-stale "~15 min" copy (skinLead + skinPreviewNote, en/es/eu) and
code comments to "~1 min".

Add plan/19-routes.md: a reference for the nmsr / landing / auth HTTP routes
(public via caddy + browser->Drasl + internal service-to-service), indexed in
plan/00-overview.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 20:58:57 +02:00
6c7e259fcb feat(landing): join-flow rework, player rosters, account page, mod list
Execute plan/18 (WS1-6) + plan/17.

- WS1: homepage join = 3 steps off packwiz (register -> UlicraftLauncher
  -> join); per-OS downloads from a synced launcher-manifest.json (schema +
  example committed); Fjord moved to its own /fjord page. Drop packwizUrl.
- WS2/3: mc-status gains an isolated /players endpoint (admin login ->
  Drasl GET /players, own client+TTL+token cache, never blocks the status
  path); online + registered rosters render shared name+avatar tiles;
  AVATAR_MODE env (default headiso).
- WS4: /account skin CRUD via browser-direct Drasl (login -> upload ->
  reset), in-memory token, players[0]; register relinks "Manage it here".
- WS5: footer link to status.${BASE_DOMAIN} in every footer.
- WS6: tooling/build-modlist.py generates mods.json + extracted logos from
  the distribution forgemods; ModList.astro renders a flat list; stat tile
  fed from the count.

Manifest/mods loaders read at build with a process.cwd() fallback (the
import.meta.url-only path does not resolve in Astro's bundled build).

New env: AVATAR_MODE, DRASL_ADMIN_USERNAME, DRASL_ADMIN_PASSWORD (mc-status
only). Generated mods.json / launcher-manifest.json / logos are gitignored;
.example.json files document the shapes. Build: 12 pages; mc-status builds.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 18:17:14 +02:00
096ef82420 docs(04-mods): record first-cutover deploy gotchas
Document the two crash classes hit on first deploy: NeoForge pin must match
the distribution's version, and client-only mods that declare BOTH crash the
dedicated server with "invalid dist DEDICATED_SERVER" (tomllib can't catch
them — hand-set to client + re-sync). Mark the decommission checklist done;
flag the landing join-flow rework as the one remaining open item. Current
state: 24 client / 52 both.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 01:16:48 +02:00
27237bc7c1 feat(mods): replace packwiz with distribution-fed custom mod volume
Drop packwiz entirely. The server now loads a filtered mod subset from a
local ./server-mods volume instead of fetching a packwiz pack at boot.

New flow:
- tooling/classify-mods.py (tomllib only, no network) reads each jar's
  META-INF/neoforge.mods.toml and seeds mods-sides.json with a side
  (client|server|both; default both). Existing entries are preserved.
- mods-sides.json: committed, keyed by jar filename, hand-editable override
  surface. Initial seed: 65 both, 11 client, 0 server (76 jars).
- tooling/sync-server-mods.sh mirrors the both/server jars from
  $DISTRIBUTION_WEB_ROOT into ./server-mods/ (authoritative; prunes strays;
  halts on a missing jar). Replaces render-pack.sh.
- compose: minecraft mounts ./server-mods:/mods:ro with REMOVE_OLD_MODS=TRUE
  (itzg auto-syncs /mods -> /data/mods). Removed PACKWIZ_URL, the pack.
  extra_host (auth. kept for authlib), and depends_on caddy (drasl kept).

Both classify-mods.py and sync-server-mods.sh resolve their source dir as:
CLI arg / MODS_DIST_DIR env / $DISTRIBUTION_WEB_ROOT (in that order).

Removed: tooling/render-pack.sh + add-custom-mod.sh (packwiz tooling),
pack/ (packwiz source), custom/ (76 jars now sourced from the distribution),
the pack. caddy vhost + its mounts/alias, and the packwiz .gitignore block.

Client distribution is UNCHANGED: HeliosLauncher still installs the full
modset from distribution.json. Docs (CLAUDE.md, plan/04/05/14, runtime)
updated; plan/04-packwiz.md stubbed as superseded by plan/04-mods.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 23:34:38 +02:00
d21a7f0e92 feat(landing): live server card backed by self-hosted mc-status
Add a featured live ServerCard to the landing (replaces the static
ServerListPanel in hero + status sections): server favicon, color MOTD,
online/offline pill, players online/max with fill bar, and a player-head
row rendered by our own NMSR from Drasl skins. Progressive enhancement —
SSG skeleton degrades gracefully when JS or the API is unavailable.

Back it with a self-hosted pinger instead of the public api.mcstatus.io:
mcstatus.io's API service is closed-source (only the mcutil library is
open), so docker/mc-status wraps mcutil and re-emits its v2 JSON shape,
keeping the frontend unchanged. The service ignores the path address and
only pings MC_STATUS_TARGET (no SSRF relay), with a 30s TTL cache.

Exposed same-origin via caddy at the apex /api/mcstatus/* path (no new DNS
subdomain or LE cert change, no CORS). Uptime Kuma stays the uptime
history + alerting backend; see plan/15-mc-status.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 22:08:30 +02:00
df8ffca53e feat(landing): self-serve registration page via Drasl API (B1)
Add a static /register page (en/es/eu) that calls the Drasl REST API v2
directly from the browser: register -> login -> optional skin upload, all
in the pixel design. Guests never touch Drasl's own web UI.

Drasl config gains the pieces this needs:
- CORSAllowOrigins scoped to the apex (the API has no CORS until set;
  never "*").
- [RateLimit] for the now public-facing anonymous POST /users.
- [RegistrationNewPlayer] with RequireInvite driven by a new
  REGISTRATION_MODE (invite|open) .env flag.

REGISTRATION_MODE has two consumers from one key: render-config.sh derives
the TOML boolean for Drasl (drasl is TOML-only, no env), and the landing
build reads it to show/hide the invite-code field. render-config.sh halts
on any value other than invite/open.

Security verified against drasl source: anonymous POST /users cannot set
privileged fields (isAdmin/isLocked/chosenUuid/maxPlayerCount are gated on
callerIsAdmin in CreateUser), so browser-direct registration is safe.

Docs: plan/16-landing-registration.md captures the design + the B1 vs fork
decision; build-order, deploy, and the deploy skill wire REGISTRATION_MODE
and the landing-rebuild requirement (www/ is gitignored, not updated by a
git pull).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 21:55:51 +02:00
067e990306 mods 2026-06-09 18:29:48 +02:00
673202e1e4 fix(compose): pin auth./pack. to host-gateway so HTTPS resolves in-container
Containers don't inherit the host's /etc/hosts; the LAN resolver returns a
dead address (192.168.0.3:443) for the public names, crash-looping the
packwiz install on boot. extra_hosts host-gateway routes HTTPS through the
host's nginx (valid LE cert) -> caddy -> service.

Also add plan/10-uptime-kuma.md (Minecraft monitor: internal + public probes)
and document the HTTPS resolution variant in plan/00-overview.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 04:11:53 +02:00
cfd60131fb fix(landing): https + correct subdomains, drop dead CA-cert step
The guest landing page advertised http:// service URLs (mixed-content on the
https origin) and the wrong packwiz subdomain:
- authUrl / authlibUrl: http -> https
- packwizUrl: http://packwiz. -> https://pack.  (correct vhost)
- serverAddress: mc. -> apex (connect to ${BASE_DOMAIN}:25565)
- remove caCertUrl + the "trust the local CA" join step (airgap-only, /ca.crt
  no longer exists) and its en/es/eu i18n strings + type.

Also fix the stale http BaseURL example in plan/03-drasl.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 02:56:59 +02:00
67d1f82e6a refactor!: unify into one compose, drop airgap/mirror/dnsmasq/avahi/blessingskin
Collapse the override-file matrix into a single docker-compose.yml holding the
whole stack: drasl, minecraft, mc-backup, caddy, nmsr, uptime-kuma. Bring-up is
now just `docker compose up -d --build`.

Removed features (dead-ends for this deployment):
- airgap / full asset mirror (mirror-airgap.sh, mirror-mods.sh, 20-mirror.caddy,
  caddy local-CA export, /ca.crt)
- dnsmasq + avahi/mDNS + dns-records.sh + mdns-host-setup.sh + ENABLE_MDNS;
  DNS is now handled outside this repo (HOST_LAN_IP dropped)
- Blessing Skin auth variant (compose + 00-core-blessingskin.caddy + BS_* env)
- host-nginx-static variant (docker-compose.nginx.yml, nginx/ulicraft.conf.tmpl)
- build-stack.sh and its run modes (online/airgap/core)

Production ingress is the only path now: host nginx terminates TLS (LE certs)
in front of caddy on a localhost-only port; caddy routes every vhost by Host
header. Launcher downloads move mirror/launcher -> launcher/.

Docs: README, deploy skill, and plan/ rewritten to match; obsolete plan docs
(dnsmasq, blessing-skin, assets-mirror, full-airgap-mirror, dns-and-run-modes)
deleted.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 02:14:09 +02:00
3d52951355 feat(landing): pixel design ported to static Astro + en/es/eu i18n
Adopt the "Carved from stone" pixel design (landing/design/) as the apex
onboarding page, ported from its React/Babel-via-CDN prototype to static
Astro so it works on an offline LAN. Real modded-LAN content replaces the
prototype's fictional public-SMP copy.

- Vendor web fonts locally (tooling/fetch-fonts.sh -> public/fonts/) so the
  page renders without the Google Fonts CDN at party time.
- Port the design system (main.css: mood/hero/bevels) and split markup into
  Astro components (Creeper, PixelIcon, CopyChip, ServerListPanel).
- site.ts holds language-neutral config + theme knobs (mood/hero/headFont/
  dust) that replace the design's in-browser TweaksPanel; LITERALS holds
  never-translated product/in-game terms.
- i18n: English (/), Spanish (/es/), Euskera (/eu/) generated from one
  [...lang].astro via getStaticPaths; copy lives in src/i18n/ui.ts. Nav has
  an EN/ES/EU switcher; html lang + hreflang set per page.
- Status panel shows a static server-list row + honest stat tiles (no fake
  live count). Copy + scroll-reveal are the only client JS.

Build emits to ../www (gitignored). Euskera strings pending a native review.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 18:18:36 +02:00
843347ad00 feat(tls): Let's Encrypt via OVH DNS-01 + host nginx vhost
tooling/issue-letsencrypt.sh: manual script, issues one cert per name
(apex + LE_SUBDOMAINS) with acme.sh + OVH DNS-01 (no inbound ports),
installs to certs/<name>/ and reloads nginx. nginx/ulicraft.conf.tmpl:
TLS vhosts for apex/pack (static) + auth (proxy to drasl). OVH creds and
LE_EMAIL in .env.example; certs/ gitignored. plan/15-letsencrypt.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 23:35:07 +02:00
67718d8e05 docs(deploy): cochi redeploy guide + deploy skill
plan/14-deploy.md: git pull main + hard restart (compose down ->
build-stack.sh --up online), drasl/online, verify + rollback.
.claude/skills/deploy: skill to run it (pre-check, pull, hard restart,
verify) with prod guardrails.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 23:22:37 +02:00
5172316919 feat(auth): Blessing Skin variant as Drasl alternative
Override stack that swaps Drasl for Blessing Skin Server + MariaDB,
talking to Minecraft via the yggdrasil-api plugin (authlib endpoint
/api/yggdrasil, ONLINE_MODE=TRUE). Caddy auth.* repointed to
blessing-skin:80; Drasl left idle (compose merges depends_on).

Run: docker compose -f docker-compose.yml -f docker-compose.blessingskin.yml up -d

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 18:48:54 +02:00
3c2ce72c2a docs(plan): add 13-dns-and-run-modes; mark dnsmasq optional
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 06:06:33 +02:00
c09da15bd0 initial commit 2026-05-24 04:03:42 +02:00