docs(files): document filestash across plan/README/deploy skill
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>
This commit is contained in:
45
README.md
45
README.md
@@ -15,24 +15,27 @@ One compose file, one `docker compose up -d`:
|
||||
| `drasl` | unmojang/drasl | Yggdrasil auth + skins (password login) |
|
||||
| `caddy` | caddy:alpine | internal ingress: routes every vhost by Host header |
|
||||
| `nmsr` | built from source | skin/avatar renderer at `avatar.${BASE_DOMAIN}` |
|
||||
| `mc-status` | built from source | live server-status JSON for the landing card |
|
||||
| `uptime-kuma` | louislam/uptime-kuma | status page at `status.${BASE_DOMAIN}` |
|
||||
| `filestash` | machines/filestash (digest-pinned) | player file share at `files.${BASE_DOMAIN}` |
|
||||
| `mc-backup` | itzg/mc-backup | world backups every 6h via RCON |
|
||||
|
||||
Vhosts (all proxied through the host nginx → caddy):
|
||||
|
||||
- apex `${BASE_DOMAIN}` — landing page + `/launcher/` downloads
|
||||
- apex `${BASE_DOMAIN}` — landing page + `/launcher/` downloads + `/api/mcstatus/*`
|
||||
- `auth.` — Drasl
|
||||
- `avatar.` — NMSR
|
||||
- `status.` — Uptime Kuma
|
||||
- `files.` — Filestash player file share (one shared login, writable)
|
||||
- `distribution.` — static site from another repo (`DISTRIBUTION_WEB_ROOT`)
|
||||
|
||||
Config lives in `.env`: `BASE_DOMAIN`, `RCON_PASSWORD`, `CADDY_HTTP_PORT` /
|
||||
`CADDY_HTTP_BIND`, `DISTRIBUTION_WEB_ROOT`. See `.env.example`.
|
||||
`CADDY_HTTP_BIND`, `DISTRIBUTION_WEB_ROOT`, the `FILES_*` block. See `.env.example`.
|
||||
|
||||
## DNS
|
||||
|
||||
Handled **outside this repo**. Point `${BASE_DOMAIN}` and every subdomain
|
||||
(`auth. avatar. status. distribution. www.`) at the host running nginx.
|
||||
(`auth. avatar. status. files. distribution. www.`) at the host running nginx.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
@@ -121,6 +124,7 @@ persists in the `kuma_data` volume):
|
||||
| Landing (apex) | HTTP(s) | `https://${BASE_DOMAIN}` |
|
||||
| Distribution | HTTP(s) | `https://distribution.${BASE_DOMAIN}` |
|
||||
| Avatar (NMSR) | HTTP(s) | `https://avatar.${BASE_DOMAIN}` |
|
||||
| Files (Filestash) | HTTP(s) | `https://files.${BASE_DOMAIN}` (expect 200 — the login page is a 200) |
|
||||
|
||||
Internal-name targets (`minecraft`, `drasl:25585`, `nmsr:8080`) isolate "service
|
||||
down" from "ingress/TLS/DNS down"; public-URL targets test the whole chain.
|
||||
@@ -153,6 +157,41 @@ curl -H "Host: ulicraft.net" \
|
||||
|
||||
Wired in `landing/src/data/site.ts` (`statusApi`) and `caddy/conf.d/10-static.caddy`.
|
||||
|
||||
## Player file share (Filestash)
|
||||
|
||||
`files.${BASE_DOMAIN}` is a web file share for player media — screenshots,
|
||||
schematics, maps, guides. **One shared username/password for everybody**
|
||||
(`FILES_USER` / `FILES_PASSWORD_HASH`), read **and** write. It is *not* a backup
|
||||
browser and *not* a mod mirror: world snapshots contain every player's inventory
|
||||
and coordinates, and the modset already ships via `distribution.`.
|
||||
|
||||
Full design, deploy steps and gotchas: **`plan/20-files.md`**. The short version:
|
||||
|
||||
- Config is `filestash/config.json`, **rendered from a template and mounted
|
||||
`:ro`** — git is the source of truth. The admin console at `/admin` loads but
|
||||
**cannot save**; that's deliberate. Change config by editing the `.tmpl`,
|
||||
re-rendering, and **recreating** the container (`up -d --force-recreate
|
||||
filestash` — a single-file bind mount pins the inode, so `restart` reads the
|
||||
stale file).
|
||||
- Files live in `${FILES_DATA_DIR}` on the host, outside the repo. **Not backed
|
||||
up** by mc-backup — treat the share as disposable.
|
||||
- `FILES_MOUNT_MODE=ro|rw` is the literal mount flag: `ro` makes the share
|
||||
read-only at the *kernel*, whatever the UI thinks.
|
||||
- `FILES_AUTH=htpasswd|passthrough` — `passthrough` means **no login at all**.
|
||||
Only valid once the host is off the public internet: an unauthenticated
|
||||
*writable* share on a public domain is an open upload relay.
|
||||
|
||||
Three traps that cost real time (all enforced or documented in the tooling):
|
||||
|
||||
- **`FILES_PASSWORD_HASH` must be `$6$`** (`openssl passwd -6`). The htpasswd
|
||||
plugin's bcrypt branch only matches `$2a$`, so a `$2y$` hash from
|
||||
`htpasswd -B` fails *every* login, silently. `render-config.sh` rejects it.
|
||||
- **The `local` storage backend is admin-gated** — without `FILES_LOCAL_SECRET`
|
||||
reaching it through the connection params, every login dies with
|
||||
`backend error - Not Allowed`, which looks like bad credentials and isn't.
|
||||
- **Don't set `APPLICATION_URL`/`ADMIN_PASSWORD` env** — either makes Filestash
|
||||
rewrite `config.json` at boot, which fails against the `:ro` mount.
|
||||
|
||||
## Join (guests)
|
||||
|
||||
1. Open `https://${BASE_DOMAIN}`, download FjordLauncherUnlocked for your OS.
|
||||
|
||||
Reference in New Issue
Block a user