Fold files./filestash into the docs that carry the service list, the vhost
map and the deploy procedure — README stack table, 00-overview, 02-caddy,
12-build-order, 14-deploy, and the deploy skill.
Two real gaps the filestash deploy exposed, now guardrails in the skill:
- update-all.sh never touches host nginx, so a NEW SUBDOMAIN silently 404s
after deploy. It needs issue-letsencrypt.sh + render-nginx.sh --install.
- a new service with ${FOO:?} guards fails the WHOLE compose file, so a
missing .env var means `up` starts nothing — and --hard has already run
`down`. Check the host's .env BEFORE tearing the stack down.
Also: filestash/config.json is a single-file bind mount re-rendered every
run, so a rolling update-all leaves it on a stale inode (same trap as the
caddy conf) — force-recreate after any FILES_* change.
Correct the cochi divergence section: `git diff HEAD` on the host is now
empty (verified this deploy — the ff-pull succeeded). The documented
docker-compose.yml/package.json divergence was converged; only untracked
dnsmasq/, caddy-root-ca.crt and a stale pack/ remain. The stash-pull-pop
procedure it prescribed is no longer needed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Document the gotcha hit fixing distribution CORS: caddy/conf.d/*.caddy are
single-file bind mounts pinned to the host inode at container creation. git pull
rewrites tracked files via atomic rename (new inode), so a running caddy keeps
the old config; `restart`/`caddy reload` reuse the stale inode. Apply conf
changes with `docker compose up -d --force-recreate caddy` (or the --hard
down/up). Added to plan/14-deploy.md and the deploy skill guardrails.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
update-all.sh is the single source of truth for the on-host post-pull build +
restart steps: render drasl/nmsr configs, sync server mods, regen the landing
mod list, rebuild www/, then apply via docker compose. Two modes:
- default (rolling): up -d --build, then restart ONLY services whose mounted
inputs changed since a pre-run snapshot — drasl/nmsr on a config re-render,
minecraft on a mod change. Minimal downtime. (Cannot detect caddy static-conf
changes — use --hard for those.)
- --hard: down --remove-orphans && up -d --build (full restart, MC downtime).
Strict halt on any error; landing build sources nvm + pnpm (never npm) and bakes
.env. fetch-authlib stays out (rarely changes) — the deploy skill runs it before
update-all. Refactor the deploy skill to `pull && fetch-authlib && update-all.sh
--hard` so the step list can't drift.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>
The server JVM agent (runtime/authlib-injector.jar, mounted at /extras) is
gitignored and was previously fetched by the deleted build-stack.sh prep. A
fresh checkout had no jar, so minecraft crashlooped with "Error opening zip
file or JAR manifest missing". Add a standalone fetch tool: resolves the latest
release asset, downloads, and verifies it's a real zip/jar (PK magic) before
trusting it. Idempotent (skips if valid, --force to re-download). Wire it into
the deploy skill and README launch steps.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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>
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>