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>
This commit is contained in:
10
.gitignore
vendored
10
.gitignore
vendored
@@ -6,15 +6,13 @@ runtime/authlib-injector.jar
|
|||||||
# rendered configs (from tooling/render-config.sh)
|
# rendered configs (from tooling/render-config.sh)
|
||||||
drasl/config/config.toml
|
drasl/config/config.toml
|
||||||
nmsr/config.toml
|
nmsr/config.toml
|
||||||
# packwiz pack files rendered from pack/**/*.tmpl by tooling/render-pack.sh
|
|
||||||
# (domain-dependent build output); source of truth is the .tmpl + custom/ jars.
|
# server mod subset synced from the distribution repo by
|
||||||
pack/mods/*.pw.toml
|
# tooling/sync-server-mods.sh (source of truth is mods-sides.json + $DIST jars)
|
||||||
pack/index.toml
|
server-mods/
|
||||||
pack/CustomSkinLoader/CustomSkinLoader.json
|
|
||||||
|
|
||||||
# vendored binaries
|
# vendored binaries
|
||||||
launcher/
|
launcher/
|
||||||
pack/mods-files/
|
|
||||||
|
|
||||||
# landing page: generated build output + deps
|
# landing page: generated build output + deps
|
||||||
www/
|
www/
|
||||||
|
|||||||
41
CLAUDE.md
41
CLAUDE.md
@@ -1,7 +1,7 @@
|
|||||||
# Ulicraft Server — Project Context
|
# Ulicraft Server — Project Context
|
||||||
|
|
||||||
> Self-hosted modded Minecraft (NeoForge 1.21.1) with self-hosted auth/skins
|
> Self-hosted modded Minecraft (NeoForge 1.21.1) with self-hosted auth/skins
|
||||||
> (Drasl), a packwiz modpack, avatar rendering, a guest landing page, and uptime
|
> (Drasl), a distribution-fed modpack, avatar rendering, a guest landing page, and uptime
|
||||||
> monitoring. Public, internet-present deployment behind the host's nginx + TLS.
|
> monitoring. Public, internet-present deployment behind the host's nginx + TLS.
|
||||||
|
|
||||||
**`plan/` is the source of truth for the design.** Start at `plan/00-overview.md`.
|
**`plan/` is the source of truth for the design.** Start at `plan/00-overview.md`.
|
||||||
@@ -30,18 +30,17 @@ Internet ── host nginx (TLS, LE certs, HSTS) ──► caddy (127.0.0.1:8880
|
|||||||
│ routes by Host:
|
│ routes by Host:
|
||||||
${BASE_DOMAIN} ─► /srv/www landing + /launcher/ downloads
|
${BASE_DOMAIN} ─► /srv/www landing + /launcher/ downloads
|
||||||
auth.${BASE_DOMAIN} ─► drasl (Yggdrasil API + web UI)
|
auth.${BASE_DOMAIN} ─► drasl (Yggdrasil API + web UI)
|
||||||
pack.${BASE_DOMAIN} ─► packwiz files + /custom/ jars
|
|
||||||
avatar.${BASE_DOMAIN} ─► nmsr (skin/avatar renderer, Drasl-backed)
|
avatar.${BASE_DOMAIN} ─► nmsr (skin/avatar renderer, Drasl-backed)
|
||||||
status.${BASE_DOMAIN} ─► uptime-kuma (status page + monitors)
|
status.${BASE_DOMAIN} ─► uptime-kuma (status page + monitors)
|
||||||
distribution.${BASE} ─► static site from ANOTHER repo (DISTRIBUTION_WEB_ROOT)
|
distribution.${BASE} ─► static site from ANOTHER repo (DISTRIBUTION_WEB_ROOT)
|
||||||
|
|
||||||
minecraft :25565 (+ 24454/udp Simple Voice Chat)
|
minecraft :25565 (+ 24454/udp Simple Voice Chat)
|
||||||
└ ONLINE_MODE=false, -javaagent authlib-injector → http://auth.${BASE_DOMAIN}/authlib-injector
|
└ ONLINE_MODE=false, -javaagent authlib-injector → http://auth.${BASE_DOMAIN}/authlib-injector
|
||||||
└ mods via PACKWIZ_URL → http://pack.${BASE_DOMAIN}/pack.toml
|
└ mods from ./server-mods volume (/mods, REMOVE_OLD_MODS) — see plan/04-mods.md
|
||||||
mc-backup ── RCON → minecraft (6h interval, prune 14d, world-only)
|
mc-backup ── RCON → minecraft (6h interval, prune 14d, world-only)
|
||||||
```
|
```
|
||||||
|
|
||||||
Bring-up: render configs → build landing → fetch launcher → `docker compose up -d --build`.
|
Bring-up: render configs → sync server mods → build landing → fetch launcher → `docker compose up -d --build`.
|
||||||
See `plan/12-build-order.md` and `plan/14-deploy.md`. Deploy to prod host `cochi`
|
See `plan/12-build-order.md` and `plan/14-deploy.md`. Deploy to prod host `cochi`
|
||||||
via the `deploy` skill.
|
via the `deploy` skill.
|
||||||
|
|
||||||
@@ -55,8 +54,11 @@ via the `deploy` skill.
|
|||||||
- **Manual curation** (user choice, flagged expensive) — if it gets painful,
|
- **Manual curation** (user choice, flagged expensive) — if it gets painful,
|
||||||
fork-and-trim Leaking Kitchen Sink is still on the table (closest vibe match).
|
fork-and-trim Leaking Kitchen Sink is still on the table (closest vibe match).
|
||||||
- **itzg/minecraft-server** base image — de-facto standard, auto-installs NeoForge.
|
- **itzg/minecraft-server** base image — de-facto standard, auto-installs NeoForge.
|
||||||
- **packwiz** for the modpack — `PACKWIZ_URL` drives server mod install; clients
|
- **Distribution-fed mod volume (replaced packwiz)** — the modset's source of
|
||||||
import the same `pack.toml`. Jars come from the upstream CDN per `.pw.toml`.
|
truth is the HeliosLauncher distribution repo (`$DISTRIBUTION_WEB_ROOT`).
|
||||||
|
Clients install the full set via `distribution.json`. The SERVER loads a
|
||||||
|
filtered subset (`both`/`server`) from a mounted `./server-mods` volume
|
||||||
|
(`REMOVE_OLD_MODS=TRUE`). See `plan/04-mods.md`.
|
||||||
|
|
||||||
## Critical Gotchas
|
## Critical Gotchas
|
||||||
|
|
||||||
@@ -81,10 +83,17 @@ Mods are picky about exact minor versions. Compose pins `NEOFORGE_VERSION:
|
|||||||
"21.1.209"`. **Verify against https://projects.neoforged.net/neoforged/neoforge
|
"21.1.209"`. **Verify against https://projects.neoforged.net/neoforged/neoforge
|
||||||
before deploying.** Never `"latest"` for a stable server.
|
before deploying.** Never `"latest"` for a stable server.
|
||||||
|
|
||||||
### Mod source preference
|
### Mod source + server filtering
|
||||||
Prefer **Modrinth over CurseForge** (`packwiz modrinth add <slug>`). CF needs
|
The full modset lives in the distribution repo (`$DISTRIBUTION_WEB_ROOT/servers/
|
||||||
`CF_API_KEY` and is flakier. Tag client-only mods `side = "client"` so the server
|
ulicraft-1.21.1/forgemods/required/*.jar`), managed by Nebula. Clients get
|
||||||
doesn't pull and crash on them.
|
everything via `distribution.json`. The server runs a **filtered subset**:
|
||||||
|
`tooling/classify-mods.py` reads each jar's `neoforge.mods.toml` with tomllib and
|
||||||
|
seeds `mods-sides.json` (`client|server|both`, default `both`), then
|
||||||
|
`tooling/sync-server-mods.sh` copies the `both`/`server` jars into `./server-mods`
|
||||||
|
(mounted at `/mods`). Hard client-only mods (sodium/iris) classify as `client`
|
||||||
|
automatically; cosmetic-client mods that declare `BOTH` stay `both` until you
|
||||||
|
hand-edit `mods-sides.json` to `client`. No Modrinth/packwiz, no network at
|
||||||
|
classify/sync time. See `plan/04-mods.md`.
|
||||||
|
|
||||||
### Memory sizing
|
### Memory sizing
|
||||||
~50–100 mods, 8 players, 1.21.1: `INIT_MEMORY: 4G`, `MAX_MEMORY: 10G`,
|
~50–100 mods, 8 players, 1.21.1: `INIT_MEMORY: 4G`, `MAX_MEMORY: 10G`,
|
||||||
@@ -93,9 +102,11 @@ doesn't pull and crash on them.
|
|||||||
## Config (.env)
|
## Config (.env)
|
||||||
|
|
||||||
`BASE_DOMAIN`, `RCON_PASSWORD`, `CADDY_HTTP_PORT`/`CADDY_HTTP_BIND`,
|
`BASE_DOMAIN`, `RCON_PASSWORD`, `CADDY_HTTP_PORT`/`CADDY_HTTP_BIND`,
|
||||||
`DISTRIBUTION_WEB_ROOT`, the Let's Encrypt block (`LE_EMAIL`, `LE_SUBDOMAINS`,
|
`DISTRIBUTION_WEB_ROOT` (source of the modset + caddy `distribution.` mount), the
|
||||||
`OVH_*`), optional `CF_API_KEY`. Rendered configs (drasl, nmsr, packwiz) come
|
Let's Encrypt block (`LE_EMAIL`, `LE_SUBDOMAINS`, `OVH_*`). Rendered configs
|
||||||
from `*.tmpl` via `tooling/render-config.sh` (substitutes `${BASE_DOMAIN}` only).
|
(drasl, nmsr) come from `*.tmpl` via `tooling/render-config.sh` (substitutes
|
||||||
|
`${BASE_DOMAIN}` only). Server mods are synced (not rendered) by
|
||||||
|
`tooling/sync-server-mods.sh`.
|
||||||
|
|
||||||
## Client Distribution (guests)
|
## Client Distribution (guests)
|
||||||
|
|
||||||
@@ -105,7 +116,9 @@ mirrored at apex `/launcher/`. Per guest:
|
|||||||
2. Add an authlib-injector account, URL
|
2. Add an authlib-injector account, URL
|
||||||
`https://auth.${BASE_DOMAIN}/authlib-injector` (register/login at
|
`https://auth.${BASE_DOMAIN}/authlib-injector` (register/login at
|
||||||
`https://auth.${BASE_DOMAIN}`).
|
`https://auth.${BASE_DOMAIN}`).
|
||||||
3. Import the pack `https://pack.${BASE_DOMAIN}/pack.toml`.
|
3. The launcher installs the modpack from the distribution (`distribution.json`).
|
||||||
|
(NOTE: packwiz/`pack.` removed — the landing join step still references a
|
||||||
|
packwiz URL and needs reworking around the launcher/distribution flow.)
|
||||||
4. Connect to `${BASE_DOMAIN}` (`:25565`).
|
4. Connect to `${BASE_DOMAIN}` (`:25565`).
|
||||||
|
|
||||||
## Open / Pending Work
|
## Open / Pending Work
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# Base Caddy config. Vhosts live in conf.d/*.caddy snippets:
|
# Base Caddy config. Vhosts live in conf.d/*.caddy snippets:
|
||||||
# conf.d/00-core.caddy auth + packwiz
|
# conf.d/00-core.caddy auth
|
||||||
# conf.d/10-static.caddy apex landing + launcher downloads
|
# conf.d/10-static.caddy apex landing + launcher downloads
|
||||||
# conf.d/30-distribution.caddy static site from another repo
|
# conf.d/30-distribution.caddy static site from another repo
|
||||||
# conf.d/40-avatar.caddy nmsr skin/avatar renderer
|
# conf.d/40-avatar.caddy nmsr skin/avatar renderer
|
||||||
|
|||||||
@@ -1,9 +1,4 @@
|
|||||||
# Core ingress — always mounted. Drasl auth + packwiz pack metadata.
|
# Core ingress — always mounted. Drasl auth.
|
||||||
http://auth.{$BASE_DOMAIN} {
|
http://auth.{$BASE_DOMAIN} {
|
||||||
reverse_proxy drasl:25585
|
reverse_proxy drasl:25585
|
||||||
}
|
}
|
||||||
|
|
||||||
http://pack.{$BASE_DOMAIN} {
|
|
||||||
root * /srv/pack
|
|
||||||
file_server browse
|
|
||||||
}
|
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -3,9 +3,9 @@
|
|||||||
# ──────────────────────────────────────────────────────────────────
|
# ──────────────────────────────────────────────────────────────────
|
||||||
# Single, unified compose. One file holds the whole stack:
|
# Single, unified compose. One file holds the whole stack:
|
||||||
# drasl auth + skins (Yggdrasil), internal only
|
# drasl auth + skins (Yggdrasil), internal only
|
||||||
# minecraft the server (itzg/NEOFORGE), installs mods via packwiz
|
# minecraft the server (itzg/NEOFORGE), mods from ./server-mods volume
|
||||||
# mc-backup world backups every 6h via RCON
|
# mc-backup world backups every 6h via RCON
|
||||||
# caddy internal ingress — routes auth./pack./apex/launcher/avatar.
|
# caddy internal ingress — routes auth./apex/launcher/avatar.
|
||||||
# /distribution. by Host header (conf.d/*.caddy)
|
# /distribution. by Host header (conf.d/*.caddy)
|
||||||
# nmsr skin/avatar renderer at avatar.${BASE_DOMAIN}
|
# nmsr skin/avatar renderer at avatar.${BASE_DOMAIN}
|
||||||
# uptime-kuma status monitoring at status.${BASE_DOMAIN}
|
# uptime-kuma status monitoring at status.${BASE_DOMAIN}
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
# Production TLS terminates at the HOST's nginx in front of caddy
|
# Production TLS terminates at the HOST's nginx in front of caddy
|
||||||
# (nginx/ulicraft-caddy.conf.tmpl + Let's Encrypt); caddy is published on a
|
# (nginx/ulicraft-caddy.conf.tmpl + Let's Encrypt); caddy is published on a
|
||||||
# localhost-only port (CADDY_HTTP_BIND/CADDY_HTTP_PORT in .env). Containers reach
|
# localhost-only port (CADDY_HTTP_BIND/CADDY_HTTP_PORT in .env). Containers reach
|
||||||
# the stack's own names internally via caddy's mcnet aliases (auth./pack.).
|
# the stack's own names internally via caddy's mcnet alias (auth.).
|
||||||
# ──────────────────────────────────────────────────────────────────
|
# ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
services:
|
services:
|
||||||
@@ -71,8 +71,12 @@ services:
|
|||||||
ENFORCE_SECURE_PROFILE: "FALSE" # required for authlib-injector on 1.21+
|
ENFORCE_SECURE_PROFILE: "FALSE" # required for authlib-injector on 1.21+
|
||||||
ALLOW_FLIGHT: "TRUE" # many tech mods need this
|
ALLOW_FLIGHT: "TRUE" # many tech mods need this
|
||||||
|
|
||||||
# ── Mod source: packwiz pack served by caddy (alias pack.) ──
|
# ── Mod source: filtered subset of the distribution modset ──
|
||||||
PACKWIZ_URL: "http://pack.${BASE_DOMAIN}/pack.toml"
|
# Jars are synced into ./server-mods by tooling/sync-server-mods.sh
|
||||||
|
# (both/server entries from mods-sides.json) and mounted at /mods below;
|
||||||
|
# itzg auto-syncs /mods → /data/mods. REMOVE_OLD_MODS makes that mirror
|
||||||
|
# authoritative so removed mods get pruned. See plan/04-mods.md.
|
||||||
|
REMOVE_OLD_MODS: "TRUE"
|
||||||
|
|
||||||
# ── RCON for remote admin ───────────────────────────────────
|
# ── RCON for remote admin ───────────────────────────────────
|
||||||
ENABLE_RCON: "TRUE"
|
ENABLE_RCON: "TRUE"
|
||||||
@@ -82,16 +86,15 @@ services:
|
|||||||
TZ: "Europe/Madrid"
|
TZ: "Europe/Madrid"
|
||||||
volumes:
|
volumes:
|
||||||
- mc_data:/data
|
- mc_data:/data
|
||||||
- ./runtime:/extras:ro # authlib-injector.jar lives here
|
- ./runtime:/extras:ro # authlib-injector.jar lives here
|
||||||
|
- ./server-mods:/mods:ro # itzg auto-syncs /mods → /data/mods
|
||||||
depends_on:
|
depends_on:
|
||||||
- drasl
|
- drasl # authlib (auth.) must resolve at boot
|
||||||
- caddy # pack./auth. must resolve at boot
|
|
||||||
# Containers don't inherit the host's /etc/hosts; the LAN resolver returns a
|
# Containers don't inherit the host's /etc/hosts; the LAN resolver returns a
|
||||||
# dead address for the public names. Pin auth./pack. to the host so HTTPS to
|
# dead address for the public names. Pin auth. to the host so HTTPS to it
|
||||||
# them lands on the host's nginx (valid LE cert) -> caddy -> service.
|
# lands on the host's nginx (valid LE cert) -> caddy -> drasl.
|
||||||
extra_hosts:
|
extra_hosts:
|
||||||
- "auth.${BASE_DOMAIN}:host-gateway"
|
- "auth.${BASE_DOMAIN}:host-gateway"
|
||||||
- "pack.${BASE_DOMAIN}:host-gateway"
|
|
||||||
networks:
|
networks:
|
||||||
- mcnet
|
- mcnet
|
||||||
|
|
||||||
@@ -114,7 +117,7 @@ services:
|
|||||||
- mcnet
|
- mcnet
|
||||||
|
|
||||||
# Internal ingress. Routes every vhost by Host header (conf.d/*.caddy):
|
# Internal ingress. Routes every vhost by Host header (conf.d/*.caddy):
|
||||||
# auth. -> drasl, pack. -> packwiz files, apex -> landing + /launcher,
|
# auth. -> drasl, apex -> landing + /launcher,
|
||||||
# avatar. -> nmsr, status. -> uptime-kuma, distribution. -> static site.
|
# avatar. -> nmsr, status. -> uptime-kuma, distribution. -> static site.
|
||||||
# Published on a localhost-only port; the host's nginx terminates TLS in
|
# Published on a localhost-only port; the host's nginx terminates TLS in
|
||||||
# front of it (nginx/ulicraft-caddy.conf.tmpl).
|
# front of it (nginx/ulicraft-caddy.conf.tmpl).
|
||||||
@@ -135,13 +138,6 @@ services:
|
|||||||
- ./caddy/conf.d/50-status.caddy:/etc/caddy/conf.d/50-status.caddy:ro
|
- ./caddy/conf.d/50-status.caddy:/etc/caddy/conf.d/50-status.caddy:ro
|
||||||
- ./www:/srv/www:ro
|
- ./www:/srv/www:ro
|
||||||
- ./launcher:/srv/launcher:ro
|
- ./launcher:/srv/launcher:ro
|
||||||
- ./pack:/srv/pack:ro
|
|
||||||
# Custom (locally-hosted) mod jars live OUTSIDE ./pack so packwiz refresh
|
|
||||||
# doesn't index them as direct files. Served at pack.${BASE_DOMAIN}/custom/.
|
|
||||||
# Nested under the :ro /srv/pack mount — the mountpoint pack/custom/ must
|
|
||||||
# pre-exist on the host (tracked via pack/custom/.gitkeep) or Docker can't
|
|
||||||
# mkdir it in the read-only parent.
|
|
||||||
- ./custom:/srv/pack/custom:ro
|
|
||||||
# Static site from ANOTHER repo (absolute host path in .env).
|
# Static site from ANOTHER repo (absolute host path in .env).
|
||||||
- ${DISTRIBUTION_WEB_ROOT:?DISTRIBUTION_WEB_ROOT unset in .env}:/srv/distribution:ro
|
- ${DISTRIBUTION_WEB_ROOT:?DISTRIBUTION_WEB_ROOT unset in .env}:/srv/distribution:ro
|
||||||
networks:
|
networks:
|
||||||
@@ -149,7 +145,6 @@ services:
|
|||||||
# Containers resolve the stack's own public names to caddy internally.
|
# Containers resolve the stack's own public names to caddy internally.
|
||||||
aliases:
|
aliases:
|
||||||
- "auth.${BASE_DOMAIN}"
|
- "auth.${BASE_DOMAIN}"
|
||||||
- "pack.${BASE_DOMAIN}"
|
|
||||||
|
|
||||||
# Avatar/skin renderer — built from source (docker/nmsr/Dockerfile), pulls
|
# Avatar/skin renderer — built from source (docker/nmsr/Dockerfile), pulls
|
||||||
# player profiles from Drasl over mcnet. caddy proxies avatar. -> nmsr:8080.
|
# player profiles from Drasl over mcnet. caddy proxies avatar. -> nmsr:8080.
|
||||||
|
|||||||
@@ -43,7 +43,13 @@ export const site = {
|
|||||||
// Requires CORSAllowOrigins to include the apex in drasl config.toml.
|
// Requires CORSAllowOrigins to include the apex in drasl config.toml.
|
||||||
draslApiUrl: `https://auth.${BASE_DOMAIN}/drasl/api/v2`,
|
draslApiUrl: `https://auth.${BASE_DOMAIN}/drasl/api/v2`,
|
||||||
|
|
||||||
// packwiz modpack entrypoint (public HTTPS, pack. subdomain).
|
// TODO: packwiz removed — the `pack.` vhost no longer exists, so this URL 404s.
|
||||||
|
// The modpack is now distributed to clients by HeliosLauncher via the
|
||||||
|
// distribution.json (server loads a filtered subset from ./server-mods/; see
|
||||||
|
// plan/04-mods.md). The join flow still references this in the launcher step
|
||||||
|
// and needs to be redesigned around the launcher/distribution flow. Kept
|
||||||
|
// (non-empty) only so the Astro build + the CopyChip in [...lang].astro don't
|
||||||
|
// break; do not treat as live.
|
||||||
packwizUrl: `https://pack.${BASE_DOMAIN}/pack.toml`,
|
packwizUrl: `https://pack.${BASE_DOMAIN}/pack.toml`,
|
||||||
|
|
||||||
// NMSR avatar renderer root. Player heads come from OUR Drasl skins, not
|
// NMSR avatar renderer root. Player heads come from OUR Drasl skins, not
|
||||||
|
|||||||
78
mods-sides.json
Normal file
78
mods-sides.json
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
{
|
||||||
|
"BetterF3-11.0.3-NeoForge-1.21.1.jar": "both",
|
||||||
|
"BridgingMod-2.6.2+1.21.1.neoforge-release.jar": "client",
|
||||||
|
"Controlling-neoforge-1.21.1-19.0.5.jar": "client",
|
||||||
|
"CustomSkinLoader_Universal-14.28.jar": "client",
|
||||||
|
"ExplorersCompass-1.21.1-3.4.0-neoforge.jar": "both",
|
||||||
|
"FarmersDelight-1.21.1-1.3.2.jar": "both",
|
||||||
|
"FramedBlocks-10.6.0.jar": "both",
|
||||||
|
"ImmediatelyFast-NeoForge-1.6.10+1.21.1.jar": "client",
|
||||||
|
"Jade-1.21.1-NeoForge-15.10.5.jar": "both",
|
||||||
|
"JadeAddons-1.21.1-NeoForge-6.1.0.jar": "both",
|
||||||
|
"JustEnoughProfessions-neoforge-1.21.1-4.0.5.jar": "both",
|
||||||
|
"JustEnoughResources-NeoForge-1.21.1-1.6.0.17.jar": "both",
|
||||||
|
"MouseTweaks-neoforge-mc1.21-2.26.1.jar": "both",
|
||||||
|
"Necronomicon-NeoForge-1.6.0+1.21.jar": "both",
|
||||||
|
"Patchouli-1.21.1-93-NEOFORGE.jar": "both",
|
||||||
|
"Searchables-neoforge-1.21.1-1.0.2.jar": "both",
|
||||||
|
"TravelersTitles-1.21.1-NeoForge-5.1.3.jar": "both",
|
||||||
|
"YungsApi-1.21.1-NeoForge-5.1.6.jar": "both",
|
||||||
|
"appleskin-neoforge-mc1.21-3.0.9.jar": "client",
|
||||||
|
"architectury-13.0.8-neoforge.jar": "both",
|
||||||
|
"baguettelib-1.21.1-NeoForge-2.0.3.jar": "both",
|
||||||
|
"balm-neoforge-1.21.1-21.0.58.jar": "both",
|
||||||
|
"cardboardboxes-1.21.1-0.1.2.jar": "both",
|
||||||
|
"cloth-config-15.0.140-neoforge.jar": "both",
|
||||||
|
"comforts-neoforge-9.0.5+1.21.1.jar": "both",
|
||||||
|
"configured-neoforge-1.21.1-2.6.3.jar": "both",
|
||||||
|
"corpse-neoforge-1.21.1-1.1.13.jar": "both",
|
||||||
|
"corpsecurioscompat-1.21.1-NeoForge-4.0.1.jar": "both",
|
||||||
|
"curios-neoforge-9.5.1+1.21.1.jar": "both",
|
||||||
|
"drippyloadingscreen_neoforge_3.1.2_MC_1.21.1.jar": "both",
|
||||||
|
"entity_model_features-3.2.4-1.21-neoforge.jar": "client",
|
||||||
|
"entity_texture_features_1.21-neoforge-7.1.jar": "client",
|
||||||
|
"entityculling-neoforge-1.10.2-mc1.21.1.jar": "both",
|
||||||
|
"fancymenu_neoforge_3.9.1_MC_1.21.1.jar": "both",
|
||||||
|
"fastleafdecay-35.jar": "both",
|
||||||
|
"ferritecore-7.0.3-neoforge.jar": "both",
|
||||||
|
"ftb-chunks-neoforge-2101.1.17.jar": "both",
|
||||||
|
"ftb-essentials-neoforge-2101.1.9.jar": "both",
|
||||||
|
"ftb-library-neoforge-2101.1.31.jar": "both",
|
||||||
|
"ftb-teams-neoforge-2101.1.10.jar": "both",
|
||||||
|
"ftb-ultimine-neoforge-2101.1.14.jar": "both",
|
||||||
|
"functionalstorage-1.21.1-1.5.7.jar": "both",
|
||||||
|
"handcrafted-neoforge-1.21.1-4.0.3.jar": "both",
|
||||||
|
"inventoryessentials-neoforge-1.21.1-21.1.15.jar": "both",
|
||||||
|
"iris-neoforge-1.8.12+mc1.21.1.jar": "client",
|
||||||
|
"jamlib-neoforge-1.3.6+1.21.1.jar": "both",
|
||||||
|
"jei-1.21.1-neoforge-19.27.0.340.jar": "both",
|
||||||
|
"konkrete_neoforge_1.9.9_MC_1.21.jar": "both",
|
||||||
|
"kubejs-neoforge-2101.7.2-build.368.jar": "both",
|
||||||
|
"kubejsdelight-1.1.6.jar": "both",
|
||||||
|
"lithium-neoforge-0.15.3+mc1.21.1.jar": "both",
|
||||||
|
"lootr-neoforge-1.21.1-1.11.37.120.jar": "both",
|
||||||
|
"melody_neoforge_1.0.10_MC_1.21.jar": "both",
|
||||||
|
"morefunctionalstorage-1.2.3.jar": "both",
|
||||||
|
"polymorph-neoforge-1.1.0+1.21.1.jar": "both",
|
||||||
|
"ponderjs-neoforge-1.21.1-2.4.0.jar": "both",
|
||||||
|
"resourcefullib-neoforge-1.21-3.0.12.jar": "both",
|
||||||
|
"rhino-2101.2.7-build.85.jar": "both",
|
||||||
|
"rightclickharvest-neoforge-4.6.1+1.21.1.jar": "both",
|
||||||
|
"simplemagnets-1.1.12c-neoforge-mc1.21.jar": "both",
|
||||||
|
"sodium-extra-neoforge-0.8.7+mc1.21.1.jar": "client",
|
||||||
|
"sodium-neoforge-0.8.12-alpha.4+mc1.21.1.jar": "client",
|
||||||
|
"sophisticatedbackpacks-1.21.1-3.25.55.1852.jar": "both",
|
||||||
|
"sophisticatedcore-1.21.1-1.4.49.1976.jar": "both",
|
||||||
|
"spark-1.10.124-neoforge.jar": "both",
|
||||||
|
"supermartijn642configlib-1.1.8-neoforge-mc1.21.jar": "both",
|
||||||
|
"supermartijn642corelib-1.1.21-neoforge-mc1.21.jar": "both",
|
||||||
|
"tagtooltips-neoforge-1.21.1-1.2.0.jar": "client",
|
||||||
|
"titanium-1.21-4.0.43.jar": "both",
|
||||||
|
"torchmaster-neoforge-1.21.1-21.1.9.jar": "both",
|
||||||
|
"trashcans-1.0.18c-neoforge-mc1.21.jar": "both",
|
||||||
|
"utilitarian-1.21.1-0.19.2.jar": "both",
|
||||||
|
"voicechat-neoforge-1.21.1-2.6.18.jar": "both",
|
||||||
|
"welcomescreen-neoforge-1.0.0-1.21.1.jar": "both",
|
||||||
|
"yeetusexperimentus-neoforge-87.0.0.jar": "both",
|
||||||
|
"yet_another_config_lib_v3-3.8.2+1.21.1-neoforge.jar": "both"
|
||||||
|
}
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
# Template sources for pack metadata/config — rendered to their real files by
|
|
||||||
# tooling/render-pack.sh. packwiz must NOT index the templates themselves.
|
|
||||||
*.tmpl
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
{
|
|
||||||
"enable": true,
|
|
||||||
"loadlist": [
|
|
||||||
{
|
|
||||||
"name": "Ulicraft Skins",
|
|
||||||
"type": "MojangAPI",
|
|
||||||
"apiRoot": "https://auth.${BASE_DOMAIN}/account/",
|
|
||||||
"sessionRoot": "https://auth.${BASE_DOMAIN}/session/"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
# Ulicraft modpack (packwiz)
|
|
||||||
|
|
||||||
This directory is the **single source of truth** for the modpack (MC 1.21.1, NeoForge 21.1.209). Curate mods from here with `packwiz modrinth add <slug>` (prefer Modrinth over CurseForge); each mod produces a `mods/<slug>.pw.toml` metadata file referencing the upstream CDN. **Client-only mods must be tagged `side = "client"`** in their `.pw.toml` so the itzg server does not pull and crash on them. After adding/removing mods run `packwiz refresh` to recompute `index.toml`. Clients and the itzg server fetch jars from the upstream CDN referenced in each `.pw.toml`. Curation is an ongoing manual task — no mods are added yet.
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
name = "balm-neoforge-1.21.1-21.0.58"
|
|
||||||
filename = "balm-neoforge-1.21.1-21.0.58.jar"
|
|
||||||
side = "both"
|
|
||||||
|
|
||||||
[download]
|
|
||||||
url = "http://pack.${BASE_DOMAIN}/custom/balm-neoforge-1.21.1-21.0.58.jar"
|
|
||||||
hash-format = "sha256"
|
|
||||||
hash = "aa2463747bad4c2351869226ea7bce7839510dfd2ac387f8d1588b1ce9309c67"
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
name = "CustomSkinLoader_Universal-14.28"
|
|
||||||
filename = "CustomSkinLoader_Universal-14.28.jar"
|
|
||||||
side = "both"
|
|
||||||
|
|
||||||
[download]
|
|
||||||
url = "http://pack.${BASE_DOMAIN}/custom/CustomSkinLoader_Universal-14.28.jar"
|
|
||||||
hash-format = "sha256"
|
|
||||||
hash = "0008fb0a91dd1e84d52162cfe96addb5f6aed3b76d426ea7305d7624e31f1894"
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
name = "inventoryessentials-neoforge-1.21.1-21.1.15"
|
|
||||||
filename = "inventoryessentials-neoforge-1.21.1-21.1.15.jar"
|
|
||||||
side = "both"
|
|
||||||
|
|
||||||
[download]
|
|
||||||
url = "http://pack.${BASE_DOMAIN}/custom/inventoryessentials-neoforge-1.21.1-21.1.15.jar"
|
|
||||||
hash-format = "sha256"
|
|
||||||
hash = "b7a5c5526a45256551543419ba2b5d54e56c585d76a645a8cea8cb8ea843ae2c"
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
name = "Jade-1.21.1-NeoForge-15.10.5"
|
|
||||||
filename = "Jade-1.21.1-NeoForge-15.10.5.jar"
|
|
||||||
side = "both"
|
|
||||||
|
|
||||||
[download]
|
|
||||||
url = "http://pack.${BASE_DOMAIN}/custom/Jade-1.21.1-NeoForge-15.10.5.jar"
|
|
||||||
hash-format = "sha256"
|
|
||||||
hash = "067bb4b007e1d6f6b79f0afe99c91252aa825472b99a76d33a60d24442f9e92d"
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
name = "JadeAddons-1.21.1-NeoForge-6.1.0"
|
|
||||||
filename = "JadeAddons-1.21.1-NeoForge-6.1.0.jar"
|
|
||||||
side = "both"
|
|
||||||
|
|
||||||
[download]
|
|
||||||
url = "http://pack.${BASE_DOMAIN}/custom/JadeAddons-1.21.1-NeoForge-6.1.0.jar"
|
|
||||||
hash-format = "sha256"
|
|
||||||
hash = "5f242668ad710092964d4d9afda8a94fa2401665ea13eea67d55d07e4dff8a64"
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
name = "jei-1.21.1-neoforge-19.27.0.340"
|
|
||||||
filename = "jei-1.21.1-neoforge-19.27.0.340.jar"
|
|
||||||
side = "both"
|
|
||||||
|
|
||||||
[download]
|
|
||||||
url = "http://pack.${BASE_DOMAIN}/custom/jei-1.21.1-neoforge-19.27.0.340.jar"
|
|
||||||
hash-format = "sha256"
|
|
||||||
hash = "8aaf547432f1b4958239b036356b910692fe40f858c3073d996f56bbf7c99826"
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
name = "JustEnoughProfessions-neoforge-1.21.1-4.0.5"
|
|
||||||
filename = "JustEnoughProfessions-neoforge-1.21.1-4.0.5.jar"
|
|
||||||
side = "both"
|
|
||||||
|
|
||||||
[download]
|
|
||||||
url = "http://pack.${BASE_DOMAIN}/custom/JustEnoughProfessions-neoforge-1.21.1-4.0.5.jar"
|
|
||||||
hash-format = "sha256"
|
|
||||||
hash = "b320b47f1791f56df9e1a05138f0e312e6e54541945ff15f2f1fd398cd1d4d9d"
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
name = "JustEnoughResources-NeoForge-1.21.1-1.6.0.17"
|
|
||||||
filename = "JustEnoughResources-NeoForge-1.21.1-1.6.0.17.jar"
|
|
||||||
side = "both"
|
|
||||||
|
|
||||||
[download]
|
|
||||||
url = "http://pack.${BASE_DOMAIN}/custom/JustEnoughResources-NeoForge-1.21.1-1.6.0.17.jar"
|
|
||||||
hash-format = "sha256"
|
|
||||||
hash = "7a47d69b5530704690d5a5f0726cd74a2d6c57df17dd4a8589c4ab5212f91460"
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
name = "MouseTweaks-neoforge-mc1.21-2.26.1"
|
|
||||||
filename = "MouseTweaks-neoforge-mc1.21-2.26.1.jar"
|
|
||||||
side = "both"
|
|
||||||
|
|
||||||
[download]
|
|
||||||
url = "http://pack.${BASE_DOMAIN}/custom/MouseTweaks-neoforge-mc1.21-2.26.1.jar"
|
|
||||||
hash-format = "sha256"
|
|
||||||
hash = "68e6f4201c5de97b77929a7215c9552495696ca6a3bf3ae4eacc34e135f6cc8b"
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
name = "Ulicraft"
|
|
||||||
author = "Oier"
|
|
||||||
version = "1.0.0"
|
|
||||||
pack-format = "packwiz:1.1.0"
|
|
||||||
|
|
||||||
[index]
|
|
||||||
file = "index.toml"
|
|
||||||
hash-format = "sha256"
|
|
||||||
hash = "9bca0b1a4434050835cab46116d7c14aafec971ff5a95a1ee0c4232c8bcbc355"
|
|
||||||
|
|
||||||
[versions]
|
|
||||||
minecraft = "1.21.1"
|
|
||||||
neoforge = "21.1.209"
|
|
||||||
BIN
pack/servers.dat
BIN
pack/servers.dat
Binary file not shown.
172
plan/04-mods.md
Normal file
172
plan/04-mods.md
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
# 04 — Mods (custom volume, distribution-fed)
|
||||||
|
|
||||||
|
> **Supersedes `04-packwiz.md`.** packwiz is being removed. This describes the
|
||||||
|
> replacement: the server loads mods from a filtered local volume; clients keep
|
||||||
|
> getting the full set from the HeliosLauncher `distribution.json`.
|
||||||
|
|
||||||
|
## Why drop packwiz
|
||||||
|
|
||||||
|
- The packwiz index tracked only **9** mods while the real modset (76 jars) lives
|
||||||
|
in the distribution repo — packwiz was half-built and stale.
|
||||||
|
- Manual packwiz curation (`.tmpl` render → `refresh` → re-hash) is the flagged
|
||||||
|
pain point, and a single missing file (`custom/.gitkeep`) crashlooped the
|
||||||
|
server (404 → installer aborts → minecraft never starts).
|
||||||
|
- The launcher (HeliosLauncher) already distributes mods to clients via a
|
||||||
|
Nebula-generated `distribution.json`. That is the canonical client mod list.
|
||||||
|
The server just needs the same jars, minus client-only ones.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
ulicraft-distribution/dist (another repo, = $DISTRIBUTION_WEB_ROOT)
|
||||||
|
servers/ulicraft-1.21.1/
|
||||||
|
forgemods/required/*.jar ← 76 jars, Nebula-managed (SOURCE OF TRUTH)
|
||||||
|
distribution.json ← generated by Nebula; CLIENT install list
|
||||||
|
│ │
|
||||||
|
│ caddy distribution. vhost │ HeliosLauncher reads it →
|
||||||
|
▼ ▼ installs ALL listed mods (client)
|
||||||
|
guests' launcher ◄───────────────────────────────────
|
||||||
|
|
||||||
|
ulicraft-server (this repo)
|
||||||
|
mods-sides.json ← { "<jar>": "client|server|both" } (committed, editable)
|
||||||
|
tooling/classify-mods ← tomllib: seeds/updates mods-sides.json from jar manifests
|
||||||
|
tooling/sync-server-mods ← copies both+server jars from $DIST → ./server-mods/
|
||||||
|
server-mods/ (gitignored) ──mount──► minecraft /mods (REMOVE_OLD_MODS=TRUE)
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Client distribution: unchanged.** HeliosLauncher installs everything in
|
||||||
|
`distribution.json`. No packwiz, no client zip.
|
||||||
|
- **Server: filtered subset.** Only `both` + `server` jars are copied into the
|
||||||
|
mounted `server-mods/`. `client`-tagged jars never reach the server.
|
||||||
|
- **Single source of jars:** `$DISTRIBUTION_WEB_ROOT/.../forgemods/required/`.
|
||||||
|
This repo stores no jars — only `mods-sides.json`.
|
||||||
|
|
||||||
|
## Classification — tomllib only (no Modrinth)
|
||||||
|
|
||||||
|
`tooling/classify-mods.py` reads each jar's `META-INF/neoforge.mods.toml` with
|
||||||
|
Python `tomllib` and derives a side from the **`minecraft`/`neoforge` dependency
|
||||||
|
`side`** (the field modders use to declare a mod's environment):
|
||||||
|
|
||||||
|
| manifest signal | derived side |
|
||||||
|
|---|---|
|
||||||
|
| `minecraft` (or `neoforge`) dep `side = "CLIENT"` | `client` |
|
||||||
|
| dep `side = "SERVER"` | `server` |
|
||||||
|
| dep `side = "BOTH"`, unspecified, or no manifest | `both` (default) |
|
||||||
|
|
||||||
|
Output is **merged** into `mods-sides.json`: existing (human-edited) entries are
|
||||||
|
**preserved**; only new/unknown jars get a derived default. So the file is a
|
||||||
|
one-time seed + permanent manual override surface. New jars default to `both`
|
||||||
|
(your chosen default) unless their manifest says otherwise.
|
||||||
|
|
||||||
|
### Accuracy + the manual step (important)
|
||||||
|
|
||||||
|
tomllib reliably catches **hard client-only** mods — ones that declare their
|
||||||
|
`minecraft`/`neoforge` dep as `CLIENT` (e.g. `sodium`, `iris`). Those are exactly
|
||||||
|
the mods that refuse/break on a dedicated server, so the must-exclude set is
|
||||||
|
handled automatically.
|
||||||
|
|
||||||
|
It will **not** auto-exclude client-*cosmetic* mods that declare `BOTH` in their
|
||||||
|
manifest (e.g. `BetterF3`, `entityculling`, `sodium-extra`, `entity_texture_features`,
|
||||||
|
`entity_model_features`, `ImmediatelyFast`, `drippyloadingscreen`, `melody`,
|
||||||
|
`welcomescreen`, `Controlling`, `MouseTweaks`, `Searchables`, `tagtooltips`,
|
||||||
|
`JustEnoughResources`, `TravelersTitles`, `BridgingMod`, `ponderjs`,
|
||||||
|
`yeetusexperimentus`). These load on the server harmlessly (wasted RAM, no crash)
|
||||||
|
unless you mark them `client` in `mods-sides.json`. **That manual curation is the
|
||||||
|
"filter manually" step** — `classify-mods` seeds, you trim cosmetic-client mods.
|
||||||
|
|
||||||
|
Also worth an explicit decision in the override file:
|
||||||
|
- `CustomSkinLoader` — server doesn't need it (skins resolve via Drasl/authlib);
|
||||||
|
set `client` to keep it off the server.
|
||||||
|
|
||||||
|
## `mods-sides.json` format
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"jei-1.21.1-neoforge-19.27.0.340.jar": "both",
|
||||||
|
"sodium-neoforge-0.8.12-alpha.4+mc1.21.1.jar": "client",
|
||||||
|
"ftb-essentials-neoforge-2101.1.9.jar": "both",
|
||||||
|
"rightclickharvest-neoforge-4.6.1+1.21.1.jar": "server"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Key = exact jar filename (matches `forgemods/required/*.jar`). Value ∈
|
||||||
|
`client | server | both`. Hand-edit freely; `classify-mods` won't clobber it.
|
||||||
|
|
||||||
|
## Tooling
|
||||||
|
|
||||||
|
### `tooling/classify-mods.py` (dev-time, no network)
|
||||||
|
1. Enumerate `$DISTRIBUTION_WEB_ROOT/servers/ulicraft-1.21.1/forgemods/required/*.jar`.
|
||||||
|
2. For each jar not already in `mods-sides.json`: parse manifest → derive side
|
||||||
|
(table above) → add entry.
|
||||||
|
3. Report: counts per side, and **which jars were defaulted to `both`** (so you
|
||||||
|
know what to review). Never overwrites existing entries.
|
||||||
|
4. Optionally warn on entries in `mods-sides.json` whose jar no longer exists.
|
||||||
|
|
||||||
|
### `tooling/sync-server-mods.sh` (deploy-time, offline) — replaces `render-pack.sh`
|
||||||
|
1. Read `mods-sides.json`.
|
||||||
|
2. `mkdir -p server-mods`; for each `both`/`server` jar, copy from
|
||||||
|
`$DISTRIBUTION_WEB_ROOT/.../forgemods/required/` → `server-mods/`.
|
||||||
|
3. Remove any `server-mods/*.jar` not currently selected (keeps it authoritative;
|
||||||
|
`REMOVE_OLD_MODS=TRUE` in the container also enforces this on `/data/mods`).
|
||||||
|
4. **Halt** (non-zero exit) if a selected jar is missing from `$DIST`, or if
|
||||||
|
`mods-sides.json` is absent — cautious, no silent partial sync.
|
||||||
|
|
||||||
|
## docker-compose.yml changes
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# remove:
|
||||||
|
# PACKWIZ_URL: ...
|
||||||
|
# extra_hosts: pack.${BASE_DOMAIN}:host-gateway (auth. stays — authlib)
|
||||||
|
environment:
|
||||||
|
MODS_FILE: "" # ensure unset
|
||||||
|
REMOVE_OLD_MODS: "TRUE" # mounted folder is authoritative
|
||||||
|
volumes:
|
||||||
|
- mc_data:/data
|
||||||
|
- ./runtime:/extras:ro
|
||||||
|
- ./server-mods:/mods:ro # itzg auto-syncs /mods → /data/mods
|
||||||
|
```
|
||||||
|
|
||||||
|
`depends_on: caddy` can drop (server no longer fetches `pack.` at boot); keep
|
||||||
|
`drasl` (authlib). `pack.` caddy vhost + the `./pack`/`./custom` mounts become
|
||||||
|
dead — remove in the same pass.
|
||||||
|
|
||||||
|
## Deploy flow (updated `plan/14`)
|
||||||
|
|
||||||
|
```
|
||||||
|
git pull --ff-only
|
||||||
|
tooling/render-config.sh # drasl/nmsr/etc. (unchanged)
|
||||||
|
tooling/sync-server-mods.sh # NEW — replaces render-pack.sh
|
||||||
|
tooling/fetch-authlib.sh
|
||||||
|
docker compose down --remove-orphans
|
||||||
|
docker compose up -d --build
|
||||||
|
```
|
||||||
|
|
||||||
|
`$DISTRIBUTION_WEB_ROOT` must be present on the host (already is — caddy mounts
|
||||||
|
it). `classify-mods.py` is a dev step run when the modset changes; it is **not**
|
||||||
|
in the deploy path (keeps deploy offline + deterministic).
|
||||||
|
|
||||||
|
## Decommission checklist (on execution)
|
||||||
|
|
||||||
|
- [ ] `tooling/classify-mods.py` + generate initial `mods-sides.json` (review it).
|
||||||
|
- [ ] `tooling/sync-server-mods.sh`.
|
||||||
|
- [ ] compose: drop `PACKWIZ_URL`, `pack.` extra_host, `./pack`/`./custom` mounts,
|
||||||
|
`pack.` caddy vhost; add `server-mods` mount + `REMOVE_OLD_MODS`.
|
||||||
|
- [ ] delete `tooling/render-pack.sh`; remove its call from docs/deploy.
|
||||||
|
- [ ] delete `pack/` (pack.toml, index.toml, `*.pw.toml*`, `.packwizignore`,
|
||||||
|
CustomSkinLoader templates if duplicated in DIST) and `custom/` (jars now
|
||||||
|
sourced from `$DIST`).
|
||||||
|
- [ ] `.gitignore`: drop packwiz lines; add `server-mods/`.
|
||||||
|
- [ ] supersede `plan/04-packwiz.md`; update `05-minecraft.md`, `14-deploy.md`,
|
||||||
|
`CLAUDE.md` (mod source section), and the landing join steps if they still
|
||||||
|
reference `pack.toml`.
|
||||||
|
- [ ] resolves the host-local divergence around `pack/` ([[cochi-host-local-divergence]]).
|
||||||
|
|
||||||
|
## Open questions / risks
|
||||||
|
|
||||||
|
- **Cosmetic-client mods** load on the server unless manually set `client` — RAM
|
||||||
|
waste, not a crash. Curate `mods-sides.json` over time.
|
||||||
|
- **No manifest** in a jar → defaults `both`. Acceptable; review the defaulted
|
||||||
|
list `classify-mods` prints.
|
||||||
|
- **Nebula regen** may rename jars (version bumps) → `mods-sides.json` keys go
|
||||||
|
stale; `classify-mods` re-seeds new names as `both`, and `sync` warns on
|
||||||
|
missing-from-DIST. Re-curate on big mod updates.
|
||||||
@@ -1,37 +1,7 @@
|
|||||||
# packwiz — modpack source of truth
|
# packwiz — modpack source of truth (SUPERSEDED)
|
||||||
|
|
||||||
## Summary
|
> **Superseded by [`04-mods.md`](04-mods.md).** packwiz has been removed.
|
||||||
|
> The server now loads a filtered mod subset from a local `server-mods/` volume
|
||||||
`packwiz` (installed at `~/go/bin/packwiz`) authors the modpack. The `./pack`
|
> (synced from the distribution repo by `tooling/sync-server-mods.sh`); clients
|
||||||
directory is the **single source of truth** for mods, consumed by both:
|
> still get the full set from the HeliosLauncher `distribution.json`.
|
||||||
- the **server** (itzg `PACKWIZ_URL`), and
|
> See `04-mods.md` for the current design.
|
||||||
- **clients** (Fjord launcher imports the pack / packwiz-installer).
|
|
||||||
|
|
||||||
Caddy serves `./pack` at `pack.ulicraft.net`. packwiz emits **metadata**
|
|
||||||
(`pack.toml`, `index.toml`, `mods/*.pw.toml`); the `.pw.toml` files reference
|
|
||||||
Modrinth/CF **CDN URLs**, so server and clients fetch the jars from those CDNs.
|
|
||||||
The stack assumes the internet is present.
|
|
||||||
|
|
||||||
## Behaviors confirmed
|
|
||||||
|
|
||||||
- **Prune**: packwiz-installer tracks a manifest and **removes** client mods no
|
|
||||||
longer in the index on re-run. (Server side via itzg likewise re-syncs.)
|
|
||||||
- **Download source**: jars come from the CDN URLs in `.pw.toml`, not the pack
|
|
||||||
host (which serves only metadata).
|
|
||||||
- **itzg side filtering**: itzg downloads **server-side mods only**. Client-only
|
|
||||||
mods MUST be tagged `side = "client"` (not `both`) or the server may pull and
|
|
||||||
crash on them.
|
|
||||||
|
|
||||||
## NeoForge pinning
|
|
||||||
|
|
||||||
`pack.toml` pins MC `1.21.1` + NeoForge `21.1.x`. Verify exact NeoForge build
|
|
||||||
against projects.neoforged.net before locking.
|
|
||||||
|
|
||||||
## Tasks
|
|
||||||
|
|
||||||
- [ ] `packwiz init` → `./pack` (MC 1.21.1, modloader neoforge, pinned version)
|
|
||||||
- [ ] Curate mod shortlist (perf/tech/magic/storage/QoL/worldgen/map/voice)
|
|
||||||
- [ ] Tag client-only mods `side = "client"`
|
|
||||||
- [ ] `packwiz refresh`; verify `index.toml` committed (clients fetch it)
|
|
||||||
- [ ] Point server `PACKWIZ_URL=http://pack.ulicraft.net/pack.toml`
|
|
||||||
- [ ] Test install end-to-end on a fresh client
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user