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:
@@ -14,7 +14,7 @@ Redeploy the running stack on the production host. Full reference:
|
||||
- Path: `/home/ubuntu/mc/ulicraft-server-v1`
|
||||
- Branch: `main`
|
||||
- Stack: single unified `docker-compose.yml` (drasl, minecraft, mc-backup,
|
||||
caddy, nmsr, uptime-kuma)
|
||||
caddy, nmsr, mc-status, uptime-kuma, filestash)
|
||||
- Auth: Drasl
|
||||
- Restart: **hard** (down → up) — players are disconnected for a few minutes
|
||||
|
||||
@@ -31,6 +31,18 @@ invoking this skill is the authorization to proceed, but:
|
||||
already up to date and ask whether to restart anyway (they may want a plain
|
||||
restart). If `git fetch` fails (host unreachable, auth), STOP and report.
|
||||
|
||||
**If the incoming commits ADD A SERVICE, check `.env` on the host first.**
|
||||
A new service with `${FOO:?...}` guards (filestash uses them) fails the
|
||||
**whole compose file**, not just that service — so a missing var means
|
||||
`up` refuses to start *anything*, and a `--hard` deploy has already run
|
||||
`down`. The stack stays down. Check before tearing it down:
|
||||
```bash
|
||||
ssh cochi 'cd /home/ubuntu/mc/ulicraft-server-v1 && grep -c "^FILES_" .env'
|
||||
```
|
||||
Same for a new **subdomain**: it needs a cert (`LE_SUBDOMAINS` +
|
||||
`issue-letsencrypt.sh`) and an nginx vhost — see step 2b, which the deploy
|
||||
does NOT do for you.
|
||||
|
||||
2. **Pull, ensure authlib, then `update-all.sh --hard`** (single SSH session,
|
||||
halts on any error):
|
||||
```bash
|
||||
@@ -58,16 +70,31 @@ invoking this skill is the authorization to proceed, but:
|
||||
landing rebuild is unconditional (the gitignored `www/` is never updated by
|
||||
`git pull`), so no separate landing step is needed.
|
||||
|
||||
2b. **Only if the deploy changed `nginx/ulicraft-caddy.conf.tmpl`** (a new
|
||||
subdomain, a new body-size/rate-limit rule, a changed `CADDY_HTTP_PORT`).
|
||||
`update-all.sh` does **not** touch the host nginx, so a new vhost simply
|
||||
won't exist and the subdomain 404s / hits the wrong server block:
|
||||
```bash
|
||||
ssh cochi 'cd /home/ubuntu/mc/ulicraft-server-v1 && tooling/render-nginx.sh --install'
|
||||
```
|
||||
It runs `nginx -t` before reloading, so a bad render fails safe. The cert
|
||||
must already exist (`certs/<name>/cert.pem`) or nginx won't load the block —
|
||||
issue it first with `tooling/issue-letsencrypt.sh` (reads `LE_SUBDOMAINS`).
|
||||
|
||||
3. **Verify.**
|
||||
```bash
|
||||
ssh cochi 'cd /home/ubuntu/mc/ulicraft-server-v1 && docker compose ps'
|
||||
```
|
||||
Confirm `caddy`, `drasl`, `minecraft`, `mc-backup`, `nmsr`, `uptime-kuma` are
|
||||
Up. Tail the minecraft log briefly and confirm it reaches `Done` (server
|
||||
ready):
|
||||
Confirm `caddy`, `drasl`, `minecraft`, `mc-backup`, `nmsr`, `mc-status`,
|
||||
`uptime-kuma`, `filestash` are Up. Tail the minecraft log briefly and confirm
|
||||
it reaches the ready marker (match `Done (Ns)! For help` — a bare `Done`
|
||||
also matches unrelated mod-loading lines):
|
||||
```bash
|
||||
ssh cochi 'cd /home/ubuntu/mc/ulicraft-server-v1 && timeout 120 docker compose logs -f minecraft' 2>/dev/null | grep -m1 "Done"
|
||||
ssh cochi 'cd /home/ubuntu/mc/ulicraft-server-v1 && timeout 180 docker compose logs -f minecraft' 2>/dev/null | grep -m1 'Done ([0-9.]*s)! For help'
|
||||
```
|
||||
If a public vhost changed, verify it end-to-end from your workstation rather
|
||||
than trusting `compose ps` (a container can be Up while nginx never routes to
|
||||
it): `curl -s -o /dev/null -w '%{http_code}' https://<vhost>/`.
|
||||
|
||||
## Guardrails
|
||||
|
||||
@@ -81,6 +108,18 @@ invoking this skill is the authorization to proceed, but:
|
||||
`docker compose up -d --force-recreate caddy`, then verify with
|
||||
`docker compose exec caddy cat /etc/caddy/conf.d/<file>`. The `--hard` path
|
||||
(full `down`/`up`) already recreates, so it's unaffected.
|
||||
- **Same trap for `filestash/config.json`** — also a single-file bind mount, and
|
||||
it is *re-rendered on every run* by `render-config.sh`, so a rolling
|
||||
`update-all.sh` leaves filestash reading the stale inode after any `FILES_*`
|
||||
change (rotating the shared password, flipping `FILES_AUTH`). Apply with
|
||||
`docker compose up -d --force-recreate filestash`. `--hard` is unaffected.
|
||||
- **Never let `filestash/config.json` be missing when compose runs.** It is
|
||||
gitignored and rendered; if it doesn't exist, Docker creates a *directory* at
|
||||
that path and filestash breaks in a confusing way. `update-all.sh` renders
|
||||
before `up`, so the normal path is safe — just don't hand-run `docker compose
|
||||
up` on the host without rendering first. If it happened: `rm -rf
|
||||
filestash/config.json && tooling/render-config.sh && docker compose up -d
|
||||
--force-recreate filestash`.
|
||||
- Volumes (`mc_data`, `drasl_state`, `kuma_data`) persist across `down`; the
|
||||
world and monitors are safe. Do NOT pass `-v`/`--volumes` to `down`.
|
||||
- If a step fails, STOP and surface the exact error + the failing command. Do not
|
||||
|
||||
@@ -175,6 +175,10 @@ Full design settled across WS1–WS6; all tasks unchecked, ready to execute.
|
||||
distribution forgemods (`tooling/build-modlist.py`), flat alphabetical, pretty
|
||||
names, no categories/links; feeds the "50+" stat tile.
|
||||
- [ ] Server-side perf mods (C2ME, etc.).
|
||||
- [x] **Filestash file share** (`files.`, 2026-07-14) — live in prod. One shared
|
||||
htpasswd login, writable, rendered `:ro` config. See `plan/20-files.md`.
|
||||
Follow-ups: add the Uptime Kuma HTTP monitor for `files.`; the share is
|
||||
**outside mc-backup** and unquota'd (accepted — eyeballed).
|
||||
- [ ] **Verify NeoForge version** against current stable before first deploy.
|
||||
- [x] **Bootstrap Drasl admin** account (`admin`, 2026-06-10). Key is
|
||||
`DefaultAdmins` (not `DefaultAdminUsernames`); first admin needs an invite-flip.
|
||||
|
||||
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.
|
||||
|
||||
@@ -13,9 +13,10 @@ Everything is driven by one env var in `.env`:
|
||||
BASE_DOMAIN=ulicraft.net # all services are subdomains of this
|
||||
```
|
||||
|
||||
`render-config.sh` substitutes **only** `${BASE_DOMAIN}` into the templates.
|
||||
`render-config.sh` substitutes `${BASE_DOMAIN}` into the templates (plus the
|
||||
derived `REGISTRATION_*` and `FILES_*` values — see `20-files.md`).
|
||||
DNS is **not** this repo's concern: point `${BASE_DOMAIN}` and all subdomains
|
||||
(`auth.` `avatar.` `status.` `distribution.` `www.`) at the host running
|
||||
(`auth.` `avatar.` `status.` `files.` `distribution.` `www.`) at the host running
|
||||
nginx; that is configured outside this repo.
|
||||
|
||||
## Subdomain / ingress map
|
||||
@@ -25,6 +26,7 @@ ulicraft.net → nginx → caddy → landing page (/srv/www) + /la
|
||||
auth.ulicraft.net → nginx → caddy → drasl:25585 (auth web UI + Yggdrasil API)
|
||||
avatar.ulicraft.net → nginx → caddy → nmsr:8080 (skin/avatar renderer)
|
||||
status.ulicraft.net → nginx → caddy → uptime-kuma:3001 (status page)
|
||||
files.ulicraft.net → nginx → caddy → filestash:8334 (player file share, shared login)
|
||||
distribution.ulicraft.net → nginx → caddy → /srv/distribution (static, another repo)
|
||||
mc.ulicraft.net:25565 → minecraft (raw MC protocol, not HTTP)
|
||||
```
|
||||
@@ -90,8 +92,8 @@ UP:
|
||||
```
|
||||
|
||||
Bring-up is a single command. There are no run modes and no override compose
|
||||
files — one `docker-compose.yml` holds drasl, minecraft, mc-backup, caddy, nmsr
|
||||
and uptime-kuma.
|
||||
files — one `docker-compose.yml` holds drasl, minecraft, mc-backup, caddy, nmsr,
|
||||
mc-status, uptime-kuma and filestash.
|
||||
|
||||
## Why each hard call was made
|
||||
|
||||
@@ -119,6 +121,9 @@ and uptime-kuma.
|
||||
│ ├── config.toml.tmpl # render-config.sh source
|
||||
│ └── config.toml # generated (gitignored)
|
||||
├── nmsr/ # avatar renderer config + Dockerfile context
|
||||
├── filestash/
|
||||
│ ├── config.json.tmpl # render-config.sh source (files. share)
|
||||
│ └── config.json # generated, mounted :ro (gitignored — holds hashes)
|
||||
├── docker/nmsr/ # nmsr image build context
|
||||
├── server-mods/ # filtered server modset (synced, gitignored)
|
||||
├── launcher/ # vendored Fjord launcher releases (gitignored)
|
||||
@@ -145,9 +150,11 @@ 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)
|
||||
- `19-routes.md` — HTTP route reference (nmsr / landing / auth / files + internal)
|
||||
- `20-files.md` — Filestash player file share (`files.`, one shared login)
|
||||
|
||||
## Boot/dependency order
|
||||
|
||||
`caddy` (aliases) → `drasl` → `minecraft` (depends on drasl) → `mc-backup`.
|
||||
`nmsr` and `uptime-kuma` are always-on and join `mcnet`.
|
||||
`nmsr`, `mc-status`, `uptime-kuma` and `filestash` are always-on and join `mcnet`
|
||||
(no ordering constraints — none of them are on the auth/boot path).
|
||||
|
||||
@@ -7,7 +7,8 @@ nginx (which terminates TLS — see `15-letsencrypt.md`). caddy is published onl
|
||||
on a localhost-only port (`CADDY_HTTP_BIND=127.0.0.1`, `CADDY_HTTP_PORT=8880`);
|
||||
nginx reverse-proxies every public vhost to it by Host header. Roles:
|
||||
|
||||
1. **Reverse proxy** — `auth.` → drasl, `avatar.` → nmsr, `status.` → uptime-kuma.
|
||||
1. **Reverse proxy** — `auth.` → drasl, `avatar.` → nmsr, `status.` → uptime-kuma,
|
||||
`files.` → filestash.
|
||||
2. **Static** — apex serves the landing page + `/launcher/` downloads;
|
||||
`distribution.` serves a static site from another repo.
|
||||
|
||||
@@ -36,10 +37,17 @@ The Caddyfile imports per-vhost snippets from `caddy/conf.d/`:
|
||||
`DISTRIBUTION_WEB_ROOT`, another repo).
|
||||
- `40-avatar.caddy` — `avatar.` → `nmsr:8080` (skin/avatar renderer).
|
||||
- `50-status.caddy` — `status.` → `uptime-kuma:3001` (status page).
|
||||
- `60-files.caddy` — `files.` → `filestash:8334` (player file share, `20-files.md`).
|
||||
|
||||
All vhosts are plain `http://…`; TLS is nginx's job. Mounts: `./www:/srv/www:ro`,
|
||||
`./launcher:/srv/launcher:ro`, `${DISTRIBUTION_WEB_ROOT}:/srv/distribution:ro`.
|
||||
|
||||
> **Don't rewrite the `Host` header for `files.`** — filestash rejects any
|
||||
> request whose Host doesn't match its configured `general.host`, with *"only
|
||||
> traffic from X is allowed"* (a 403 that reads like an auth failure). nginx
|
||||
> forwards the original Host and caddy passes it through untouched. Keep it that
|
||||
> way.
|
||||
|
||||
## Notes / risks
|
||||
|
||||
- drasl behind a proxy: with matching `BaseURL` and nginx terminating TLS it is
|
||||
|
||||
@@ -50,6 +50,15 @@ Dependency-ordered. Each numbered item = one git commit. Operational steps
|
||||
17. `feat(nginx): add ulicraft-caddy.conf.tmpl + render-nginx.sh`
|
||||
18. `feat(tls): add issue-letsencrypt.sh (OVH DNS-01) + LE env block`
|
||||
|
||||
## Phase 11 — file share (DONE, 2026-07-14)
|
||||
19. `feat(files): add Filestash player file share at files.${BASE_DOMAIN}` —
|
||||
compose service (digest-pinned) + `60-files.caddy` + nginx vhost (2g uploads,
|
||||
`/admin` rate limit) + `filestash/config.json.tmpl` rendered `:ro` +
|
||||
`FILES_*` env block. One shared htpasswd login, writable. See `20-files.md`.
|
||||
- [ops] `files` added to `LE_SUBDOMAINS` → `issue-letsencrypt.sh`, then
|
||||
`render-nginx.sh --install` (a new subdomain needs BOTH; `update-all.sh`
|
||||
does not touch host nginx).
|
||||
|
||||
## Phase 10 — operational (no commits)
|
||||
- [ops] Prep online (once): `tooling/render-config.sh`, build landing
|
||||
(`cd landing && set -a && . ../.env && set +a && pnpm run build` — sources
|
||||
|
||||
@@ -56,8 +56,19 @@ Everyday landing/config tweak (no world downtime):
|
||||
ssh cochi 'cd /home/ubuntu/mc/ulicraft-server-v1 && git pull --ff-only && ./update-all.sh'
|
||||
```
|
||||
|
||||
Steps `update-all.sh` runs, in order: `render-config.sh` (drasl/nmsr from `.env`;
|
||||
halts on a bad `REGISTRATION_MODE`) → `sync-server-mods.sh` (mirror the
|
||||
> **Same inode trap for `filestash/config.json`.** It is a single-file bind mount
|
||||
> *and* it is re-rendered on every `update-all.sh` run, so a **rolling** update
|
||||
> after any `FILES_*` change (rotating the shared password, flipping
|
||||
> `FILES_AUTH`) leaves filestash reading the stale inode. Apply with
|
||||
> `docker compose up -d --force-recreate filestash`. `--hard` is unaffected.
|
||||
> Also: never run `docker compose up` on the host without rendering first — a
|
||||
> missing `filestash/config.json` makes Docker create a **directory** at that
|
||||
> path. See `plan/20-files.md`.
|
||||
|
||||
Steps `update-all.sh` runs, in order: `render-config.sh` (drasl/nmsr/filestash
|
||||
from `.env`; halts on a bad `REGISTRATION_MODE`, a bad `FILES_AUTH`, or a
|
||||
`FILES_PASSWORD_HASH` in a format the htpasswd plugin can't verify) →
|
||||
`sync-server-mods.sh` (mirror the
|
||||
`both`/`server` subset from `mods-sides.json` out of `$DISTRIBUTION_WEB_ROOT`
|
||||
into `./server-mods`) → `build-modlist.py` (regen landing `mods.json` + logos;
|
||||
**run under `python>=3.11`** — cochi's default `python3` is 3.10, but
|
||||
@@ -75,30 +86,25 @@ pull` never updates them. `update-all.sh` regenerates both every run
|
||||
`render-config.sh` changed its config (Drasl's `RequireInvite`/CORS/RateLimit) —
|
||||
no world downtime.
|
||||
|
||||
## Known host-local divergence on `cochi` (uncommitted)
|
||||
## Host-local divergence on `cochi` — RESOLVED (verified 2026-07-14)
|
||||
|
||||
`cochi` carries deliberate local edits that are **not in the repo**, so
|
||||
`git pull --ff-only` **aborts** ("local changes would be overwritten") whenever
|
||||
an incoming commit also touches `docker-compose.yml`. As of 2026-06-09 the host
|
||||
has, uncommitted:
|
||||
**There are no longer any tracked modifications on the host.** `git diff HEAD`
|
||||
is empty; `git pull --ff-only` fast-forwards cleanly (confirmed on the filestash
|
||||
deploy). The old divergence — `docker-compose.yml` forced `http`→`https`,
|
||||
`landing/package.json`, `landing/pnpm-workspace.yaml` — has been converged into
|
||||
the repo. The stash–pull–pop dance previously documented here is **no longer
|
||||
needed**.
|
||||
|
||||
- `docker-compose.yml` — internal URLs forced `http`→`https`
|
||||
(`JVM_OPTS` authlib-injector), for the in-container HTTPS path. (The old
|
||||
`PACKWIZ_URL` divergence is moot — packwiz was removed; see `04-mods.md`.)
|
||||
- `dnsmasq/` (new), `caddy/caddy-root-ca.crt`, `landing/pnpm-workspace.yaml` —
|
||||
internal DNS + private CA so containers resolve/trust `https://auth.` etc.
|
||||
- `landing/package.json` modified. (The former `pack/` divergence is resolved —
|
||||
`pack/` and `custom/` were deleted with the packwiz removal.)
|
||||
What remains on the host is **untracked only**, so it never blocks a pull:
|
||||
|
||||
**Do not steamroll it.** Before a deploy that edits `docker-compose.yml`, either:
|
||||
1. **Converge** — commit the host edits to `main` + push, then a clean ff-pull
|
||||
deploys (preferred — ends the divergence). The https/dnsmasq/CA setup belongs
|
||||
in the repo.
|
||||
2. **Stash–pull–pop** — `git stash` → `git pull --ff-only` → `git stash pop`.
|
||||
Non-destructive; safe only when the incoming and host edits are in different
|
||||
regions of the file. Stop on a pop conflict.
|
||||
- `dnsmasq/`, `caddy/caddy-root-ca.crt` — internal DNS + private CA so containers
|
||||
resolve/trust `https://auth.` etc. Host-local infra, deliberately not in git.
|
||||
- `pack/` — **stale leftover** from the packwiz era (packwiz was removed; see
|
||||
`04-mods.md`). Nothing reads it. Safe to delete.
|
||||
|
||||
Never `git reset --hard` / discard on the host without confirming first.
|
||||
Still true, and worth keeping: never `git reset --hard` or discard on the host
|
||||
without confirming first, and re-check `git status` there before any deploy that
|
||||
touches tracked files — a *new* host-local edit would reintroduce the abort.
|
||||
|
||||
## What survives a hard restart
|
||||
|
||||
@@ -116,8 +122,15 @@ restart (a few minutes); they reconnect once `minecraft` is healthy again.
|
||||
- SSH access as the `cochi` alias.
|
||||
- `.env` present and complete: `BASE_DOMAIN`, `RCON_PASSWORD`, `CADDY_HTTP_PORT`,
|
||||
`CADDY_HTTP_BIND`, `DISTRIBUTION_WEB_ROOT`, `REGISTRATION_MODE` (`invite`|`open`,
|
||||
defaults `invite`), and the Let's Encrypt block (`render-config.sh` halts on an
|
||||
unset `BASE_DOMAIN` or an invalid `REGISTRATION_MODE`).
|
||||
defaults `invite`), the `FILES_*` block (`plan/20-files.md`), and the Let's
|
||||
Encrypt block. `render-config.sh` halts on an unset `BASE_DOMAIN`, an invalid
|
||||
`REGISTRATION_MODE`/`FILES_AUTH`, or a `FILES_PASSWORD_HASH` the htpasswd
|
||||
plugin can't verify.
|
||||
> **A missing `FILES_*` var takes the WHOLE stack down, not just filestash.**
|
||||
> The compose service uses `${FILES_DATA_DIR:?}` / `${FILES_LOCAL_SECRET:?}`
|
||||
> guards, and compose fails the entire file on an unset required var — so
|
||||
> `up` starts *nothing*, and a `--hard` deploy has already run `down`. Check
|
||||
> `.env` on the host **before** deploying a commit that adds a guarded service.
|
||||
- Docker + compose plugin, `envsubst`, `jq` (used by `sync-server-mods.sh`;
|
||||
falls back to `python3` if absent), `git`.
|
||||
- Host nginx + Let's Encrypt certs installed once — see `15-letsencrypt.md`.
|
||||
@@ -127,11 +140,15 @@ restart (a few minutes); they reconnect once `minecraft` is healthy again.
|
||||
## Verify
|
||||
|
||||
```bash
|
||||
docker compose ps # caddy, drasl, minecraft, mc-backup, nmsr, mc-status, uptime-kuma up
|
||||
docker compose ps # caddy drasl minecraft mc-backup nmsr mc-status uptime-kuma filestash
|
||||
# self-hosted live status feed (plan/15-mc-status.md):
|
||||
curl -fsS "https://$(grep '^BASE_DOMAIN=' .env | cut -d= -f2)/api/mcstatus/v2/status/java/$(grep '^BASE_DOMAIN=' .env | cut -d= -f2)" | jq .online
|
||||
docker compose logs -f minecraft # watch for "Done"
|
||||
# server ready — match the full marker, not a bare "Done" (that also hits mod-load lines)
|
||||
docker compose logs -f minecraft | grep -m1 'Done ([0-9.]*s)! For help'
|
||||
curl -fsS "https://auth.$(grep '^BASE_DOMAIN=' .env | cut -d= -f2)/" >/dev/null && echo "auth ok"
|
||||
# files. — 200 = live; anonymous API must be REFUSED (auth is on)
|
||||
curl -s -o /dev/null -w 'files: %{http_code}\n' "https://files.$(grep '^BASE_DOMAIN=' .env | cut -d= -f2)/"
|
||||
curl -s -H 'X-Requested-With: XmlHttpRequest' "https://files.$(grep '^BASE_DOMAIN=' .env | cut -d= -f2)/api/session" # want "Not authorised"
|
||||
```
|
||||
|
||||
## Rollback
|
||||
|
||||
Reference in New Issue
Block a user