diff --git a/plan/00-overview.md b/plan/00-overview.md index 50a2f6a..93708f2 100644 --- a/plan/00-overview.md +++ b/plan/00-overview.md @@ -15,7 +15,7 @@ BASE_DOMAIN=ulicraft.net # all services are subdomains of this `render-config.sh` substitutes **only** `${BASE_DOMAIN}` into the templates. DNS is **not** this repo's concern: point `${BASE_DOMAIN}` and all subdomains -(`auth.` `pack.` `avatar.` `status.` `distribution.` `www.`) at the host running +(`auth.` `avatar.` `status.` `distribution.` `www.`) at the host running nginx; that is configured outside this repo. ## Subdomain / ingress map @@ -23,7 +23,6 @@ nginx; that is configured outside this repo. ``` ulicraft.net → nginx → caddy → landing page (/srv/www) + /launcher/ auth.ulicraft.net → nginx → caddy → drasl:25585 (auth web UI + Yggdrasil API) -pack.ulicraft.net → nginx → caddy → /srv/pack (packwiz pack metadata) avatar.ulicraft.net → nginx → caddy → nmsr:8080 (skin/avatar renderer) status.ulicraft.net → nginx → caddy → uptime-kuma:3001 (status page) distribution.ulicraft.net → nginx → caddy → /srv/distribution (static, another repo) @@ -45,36 +44,35 @@ resolves its own subdomains internally: caddy: networks: mcnet: - aliases: [ "auth.${BASE_DOMAIN}", "pack.${BASE_DOMAIN}" ] + aliases: [ "auth.${BASE_DOMAIN}" ] ``` -Result: `http://auth.ulicraft.net/authlib-injector` and -`http://pack.ulicraft.net/pack.toml` resolve identically inside the stack -(minecraft → caddy) as the public names do outside it (client → nginx → caddy). +Result: `http://auth.ulicraft.net/authlib-injector` resolves identically inside +the stack (minecraft → caddy) as the public name does outside it (client → nginx +→ caddy). #### HTTPS variant (`extra_hosts: host-gateway`) The caddy alias only serves **http** on `mcnet` (port 80). Once the stack moved -to https URLs (drasl `BaseURL`, `PACKWIZ_URL`, the authlib-injector agent must -match the client's https endpoint), the internal http alias is no longer enough — -the container needs to reach a **TLS** terminator holding a valid cert. +to https URLs (drasl `BaseURL`; the authlib-injector agent must match the +client's https endpoint), the internal http alias is no longer enough — the +container needs to reach a **TLS** terminator holding a valid cert. Caddy does not terminate TLS internally; the host's nginx does (on `0.0.0.0:443`). -So pin the public names to the host in the service, not to caddy: +So pin the public name to the host in the service, not to caddy: ```yaml minecraft: extra_hosts: - "auth.${BASE_DOMAIN}:host-gateway" - - "pack.${BASE_DOMAIN}:host-gateway" ``` `/etc/hosts` (extra_hosts) wins over the Docker resolver, so the container reaches the host's nginx (valid LE cert) → caddy → service. This is required because **containers do not inherit the host's `/etc/hosts`** — on cochi the host resolves -`auth./pack.` to the public IP, but inside a container the LAN resolver returns a -dead address (`192.168.0.3:443`, connection refused), which crash-loops the -packwiz install on boot. `host-gateway` sidesteps DNS entirely. +`auth.` to the public IP, but inside a container the LAN resolver returns a +dead address (`192.168.0.3:443`, connection refused), which crash-loops session +validation on boot. `host-gateway` sidesteps DNS entirely. ## Build / run flow @@ -83,7 +81,7 @@ PREP (run once, online): 1. render configs → tooling/render-config.sh (08-tooling.md) 2. build landing page → cd landing && pnpm run build → www/ (09-landing.md) 3. download launcher releases → tooling/fetch-launcher.sh (06-launcher.md) - 4. curate packwiz pack → see 04-packwiz.md + 4. sync server mods → tooling/sync-server-mods.sh (04-mods.md) 5. (TLS) issue Let's Encrypt certs → tooling/issue-letsencrypt.sh (15-letsencrypt.md) 6. (ingress) render+install nginx → tooling/render-nginx.sh --install (15-letsencrypt.md) @@ -99,8 +97,9 @@ and uptime-kuma. - **No OIDC/Keycloak (for now)** — drasl password login. Keycloak left out of this stack's scope. Re-add later if desired. -- **packwiz only serves metadata** — `.pw.toml` points at Modrinth/CF CDNs; - server and clients fetch jars from there. The internet is assumed present. +- **Distribution-fed mods** — the modset's source of truth is the HeliosLauncher + distribution repo; clients install the full set, the server loads a filtered + `both`/`server` subset from `./server-mods`. See `04-mods.md`. - **Fjord launcher** has authlib-injector built in → no manual JVM agent on clients. - **host nginx in front of caddy** — real Let's Encrypt certs (JVM trusts them @@ -121,7 +120,7 @@ and uptime-kuma. │ └── config.toml # generated (gitignored) ├── nmsr/ # avatar renderer config + Dockerfile context ├── docker/nmsr/ # nmsr image build context -├── pack/ # packwiz source of truth +├── server-mods/ # filtered server modset (synced, gitignored) ├── launcher/ # vendored Fjord launcher releases (gitignored) ├── runtime/ # authlib-injector.jar ├── tooling/ # render/fetch/issue scripts (see 08-tooling.md) @@ -135,7 +134,7 @@ and uptime-kuma. - `02-caddy.md` — internal router (vhost conf.d snippets) behind host nginx - `03-drasl.md` — auth (password login) -- `04-packwiz.md` — modpack source +- `04-mods.md` — distribution-fed mod volume + server filtering (supersedes `04-packwiz.md`) - `05-minecraft.md` — itzg NeoForge server - `06-launcher.md` — Fjord launcher fetch + distribution - `07-mc-backup.md` — world backups @@ -150,5 +149,5 @@ and uptime-kuma. ## Boot/dependency order -`caddy` (aliases) → `drasl` → `minecraft` (depends on drasl + pack served by -caddy) → `mc-backup`. `nmsr` and `uptime-kuma` are always-on and join `mcnet`. +`caddy` (aliases) → `drasl` → `minecraft` (depends on drasl) → `mc-backup`. +`nmsr` and `uptime-kuma` are always-on and join `mcnet`. diff --git a/plan/02-caddy.md b/plan/02-caddy.md index da63935..9957ecb 100644 --- a/plan/02-caddy.md +++ b/plan/02-caddy.md @@ -8,8 +8,8 @@ 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. -2. **Static** — `pack.` serves packwiz metadata; apex serves the landing page + - `/launcher/` downloads; `distribution.` serves a static site from another repo. +2. **Static** — apex serves the landing page + `/launcher/` downloads; + `distribution.` serves a static site from another repo. Image: `caddy:alpine`. caddy natively reads `{$BASE_DOMAIN}` env placeholders — no template render needed. @@ -22,16 +22,15 @@ caddy: mcnet: aliases: - "auth.${BASE_DOMAIN}" - - "pack.${BASE_DOMAIN}" ``` -Lets `minecraft` reach `http://auth.ulicraft.net/authlib-injector` and -`http://pack.ulicraft.net/pack.toml` from inside the stack. +Lets `minecraft` reach `http://auth.ulicraft.net/authlib-injector` from inside +the stack. ## conf.d snippets The Caddyfile imports per-vhost snippets from `caddy/conf.d/`: -- `00-core.caddy` — `auth.` → drasl, `pack.` → `/srv/pack` (browse). +- `00-core.caddy` — `auth.` → drasl. - `10-static.caddy` — apex landing (`/srv/www`) + `/launcher/*` (`/srv/launcher`). - `30-distribution.caddy` — `distribution.` → `/srv/distribution` (static, from `DISTRIBUTION_WEB_ROOT`, another repo). @@ -39,14 +38,12 @@ The Caddyfile imports per-vhost snippets from `caddy/conf.d/`: - `50-status.caddy` — `status.` → `uptime-kuma:3001` (status page). All vhosts are plain `http://…`; TLS is nginx's job. Mounts: `./www:/srv/www:ro`, -`./pack:/srv/pack:ro`, `./launcher:/srv/launcher:ro`, -`${DISTRIBUTION_WEB_ROOT}:/srv/distribution:ro`. +`./launcher:/srv/launcher:ro`, `${DISTRIBUTION_WEB_ROOT}:/srv/distribution:ro`. ## Notes / risks - drasl behind a proxy: with matching `BaseURL` and nginx terminating TLS it is fine; nginx passes the original Host through to caddy → drasl. -- `file_server browse` gives a directory listing — handy for debugging the pack. - `/launcher/` is a sibling mount (`/srv/launcher`), not nested under the read-only `/srv/www`; `handle_path` strips the prefix. @@ -55,8 +52,8 @@ All vhosts are plain `http://…`; TLS is nginx's job. Mounts: `./www:/srv/www:r - [ ] `caddy` service in `docker-compose.yml`, published `127.0.0.1:${CADDY_HTTP_PORT}:80` - [ ] Pass `BASE_DOMAIN` + `DISTRIBUTION_WEB_ROOT` into caddy env - [ ] Caddyfile imports `conf.d/*.caddy` -- [ ] Network aliases for `auth.` and `pack.` subdomains -- [ ] Mount `./www`, `./pack`, `./launcher`, `${DISTRIBUTION_WEB_ROOT}` +- [ ] Network alias for `auth.` subdomain +- [ ] Mount `./www`, `./launcher`, `${DISTRIBUTION_WEB_ROOT}` - [ ] Confirm reverse_proxy to drasl works (auth web UI loads, Yggdrasil API responds) -- [ ] Verify internal alias resolution from `minecraft` container (curl pack.toml) +- [ ] Verify internal alias resolution from `minecraft` container (curl authlib-injector) - [ ] Verify nmsr + uptime-kuma proxied (`avatar.` renders, `status.` loads) diff --git a/plan/07-mc-backup.md b/plan/07-mc-backup.md index d521cf7..798a5bc 100644 --- a/plan/07-mc-backup.md +++ b/plan/07-mc-backup.md @@ -4,7 +4,7 @@ `itzg/mc-backup` alongside the server, talking via RCON. Snapshots the world every 6h, prunes after 14 days. World-only (no mod jars — mods are reproducible from the -packwiz pack). Unchanged from the original design; lowest-priority service. +distribution). Unchanged from the original design; lowest-priority service. ## Config (carry-over) diff --git a/plan/08-tooling.md b/plan/08-tooling.md index a226e0f..56343bf 100644 --- a/plan/08-tooling.md +++ b/plan/08-tooling.md @@ -1,4 +1,4 @@ -# Tooling — config rendering, pack render, ingress, fetch scripts +# Tooling — config rendering, mod sync, ingress, fetch scripts ## Summary @@ -10,11 +10,13 @@ half-broken output. Surviving scripts: - `render-config.sh` — render `*.tmpl` configs (BASE_DOMAIN only) -- `render-pack.sh` — render packwiz pack templates + index +- `classify-mods.py` — read each jar's `neoforge.mods.toml`, seed `mods-sides.json` (see 04-mods.md) +- `sync-server-mods.sh` — copy `both`/`server` jars from the distribution → `./server-mods` (see 04-mods.md) +- `build-modlist.py` — generate the landing `mods.json` + extract logos (see 17-mod-shortlist.md) - `render-nginx.sh` — render/install the host nginx vhost (TLS terminator → caddy) - `issue-letsencrypt.sh` — issue LE certs via OVH DNS-01 (see 15-letsencrypt.md) - `fetch-launcher.sh` — download Fjord launcher release assets (see 06-launcher.md) -- `add-custom-mod.sh` — add a custom (non-CDN) mod to the pack +- `fetch-authlib.sh` — vendor the authlib-injector jar → `runtime/` - `fetch-fonts.sh` — vendor the landing page fonts (see 09-landing.md) ## render-config.sh @@ -28,8 +30,8 @@ Targets: - `drasl/config/config.toml.tmpl` → `drasl/config/config.toml` - `nmsr/config.toml.tmpl` → `nmsr/config.toml` -It then delegates pack templates (`mods/*.pw.toml.tmpl`, CustomSkinLoader, …) + -the packwiz index to `render-pack.sh` — the single source of pack render. +Mods are **not** rendered — they are synced from the distribution by +`sync-server-mods.sh` (see 04-mods.md), no templating involved. ```bash #!/usr/bin/env bash @@ -46,7 +48,6 @@ render() { # $1=tmpl $2=out render drasl/config/config.toml.tmpl drasl/config/config.toml render nmsr/config.toml.tmpl nmsr/config.toml -tooling/render-pack.sh ``` ## render-nginx.sh @@ -81,7 +82,7 @@ certs/ # Let's Encrypt certs ## Tasks - [ ] Write `tooling/render-config.sh` (above), `chmod +x` -- [ ] Write `tooling/render-pack.sh` (pack templates + index) +- [ ] Write `tooling/sync-server-mods.sh` (see 04-mods.md) - [ ] Write `tooling/render-nginx.sh` (see 15) - [ ] Write `tooling/issue-letsencrypt.sh` (see 15) - [ ] Write `tooling/fetch-launcher.sh` (see 06) diff --git a/plan/10-uptime-kuma.md b/plan/10-uptime-kuma.md index 516025b..52e8e7e 100644 --- a/plan/10-uptime-kuma.md +++ b/plan/10-uptime-kuma.md @@ -39,7 +39,7 @@ monitor stays green. Run both to tell *server down* apart from *path broken*. > Internal `minecraft` resolves because uptime-kuma is on `mcnet` (the container > name is the hostname). It is **not** routed through caddy — caddy only fronts > HTTP vhosts, and MC is raw TCP. Do not point the monitor at `caddy` or any -> `status.`/`pack.` alias. +> `status.` alias. ## Notes @@ -50,6 +50,6 @@ monitor stays green. Run both to tell *server down* apart from *path broken*. - Put both MC monitors on the public **status page** (Settings → Status Pages) so guests can self-check before pinging you. - Optional companion HTTP monitors for the web stack: - `https://auth.${BASE_DOMAIN}`, `https://pack.${BASE_DOMAIN}/pack.toml`, - `https://${BASE_DOMAIN}` — a red `pack.toml` here explains client mod-mismatch - join failures. + `https://auth.${BASE_DOMAIN}`, `https://distribution.${BASE_DOMAIN}`, + `https://${BASE_DOMAIN}` — a red `distribution.` here explains client + mod-mismatch join failures. diff --git a/plan/15-letsencrypt.md b/plan/15-letsencrypt.md index 347222c..ae6391b 100644 --- a/plan/15-letsencrypt.md +++ b/plan/15-letsencrypt.md @@ -9,7 +9,7 @@ path — there are no compose overrides. ## Certs: one per name, via OVH DNS-01 `tooling/issue-letsencrypt.sh` issues a **separate** cert for the apex and each -subdomain (`${BASE_DOMAIN}`, `auth.…`, `pack.…`, plus the rest of +subdomain (`${BASE_DOMAIN}`, `auth.…`, plus the rest of `LE_SUBDOMAINS`) using acme.sh + the OVH DNS-01 challenge — no inbound ports needed, so it works regardless of public reachability. @@ -55,7 +55,7 @@ the reload with `RELOAD_CMD=...` if nginx isn't systemd-managed. ## DNS Point the public DNS for `${BASE_DOMAIN}` and every subdomain -(`auth.` `pack.` `avatar.` `status.` `distribution.` `www.`) at the host running +(`auth.` `avatar.` `status.` `distribution.` `www.`) at the host running nginx. DNS is configured **outside this repo**. The Minecraft container reaches the stack's own names via caddy's `mcnet` aliases, not public DNS.