Support client-only mods via a separate side=client directory
Rendering mods link against LWJGL, which a dedicated server does not have, so Sodium in mods/ killed the server at boot with NoClassDefFoundError on org/lwjgl/Version. The kit had nowhere else to put such a mod: it used the server's own mods/ as the mirror, so reaching clients and not being loaded by the server were mutually exclusive. $SHARE/client-mods is now mirrored alongside mods/ and enters the pack with side = "client", so packwiz-installer delivers it to players while the server never loads it. packwiz url add always writes side = "both", so the field is rewritten explicitly after each add — inserted when absent, replaced when present, and re-asserted on unchanged mods in case it drifted. Covers packwiz-setup.sh, mc-refresh-restart.sh (both directories synced through one function), packwiz-http.py (allowlist) and mc-service-setup.sh (creates the directory). Traversal out of client-mods/ is still refused. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>main
parent
f1731698ed
commit
361d3ed82a
20
README.md
20
README.md
|
|
@ -141,6 +141,26 @@ Every script refuses to run if `/minecraft` isn't a mountpoint. An unmounted
|
||||||
dataset leaves it as an empty directory on the root filesystem, and writing a
|
dataset leaves it as an empty directory on the root filesystem, and writing a
|
||||||
world or a mod mirror there fills the root disk instead of the pool.
|
world or a mod mirror there fills the root disk instead of the pool.
|
||||||
|
|
||||||
|
## Client-only mods
|
||||||
|
|
||||||
|
Rendering and UI mods — Sodium, Iris, Embeddium, Oculus, shader mods — link
|
||||||
|
against LWJGL, which does not exist on a dedicated server. Put one in `mods/`
|
||||||
|
and the server dies at boot with `NoClassDefFoundError: org/lwjgl/Version`.
|
||||||
|
|
||||||
|
Two directories, both mirrored over HTTP:
|
||||||
|
|
||||||
|
| Directory | Loaded by the server | In the pack as |
|
||||||
|
|---|---|---|
|
||||||
|
| `/minecraft/mods` | yes | `side = "both"` |
|
||||||
|
| `/minecraft/client-mods` | no | `side = "client"` |
|
||||||
|
|
||||||
|
Drop client-only jars in `client-mods/` and they reach players normally while
|
||||||
|
the server never sees them — `packwiz-installer -s client` fetches both sides,
|
||||||
|
and nothing server-side reads the second directory.
|
||||||
|
|
||||||
|
The `side` field is set explicitly after each `packwiz url add`, since packwiz
|
||||||
|
writes `both` by default.
|
||||||
|
|
||||||
## Day-to-day
|
## Day-to-day
|
||||||
|
|
||||||
Add or remove a jar in `/minecraft/mods`, then:
|
Add or remove a jar in `/minecraft/mods`, then:
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -9,6 +9,8 @@ NEOFORGE="latest" # or pin e.g. 21.1.248
|
||||||
|
|
||||||
# --- where things live ------------------------------------------------------
|
# --- where things live ------------------------------------------------------
|
||||||
SHARE="/minecraft" # ZFS dataset mountpoint
|
SHARE="/minecraft" # ZFS dataset mountpoint
|
||||||
|
# Mods go in $SHARE/mods (server+client) and $SHARE/client-mods (client only —
|
||||||
|
# Sodium, Iris and other rendering mods, which crash a dedicated server).
|
||||||
|
|
||||||
# --- how clients reach it ---------------------------------------------------
|
# --- how clients reach it ---------------------------------------------------
|
||||||
# The public URL of the mods directory. Everything else is derived from it:
|
# The public URL of the mods directory. Everything else is derived from it:
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,8 @@ set -euo pipefail
|
||||||
### CONFIG ###############################################
|
### CONFIG ###############################################
|
||||||
SHARE="/minecraft" # ZFS dataset mountpoint
|
SHARE="/minecraft" # ZFS dataset mountpoint
|
||||||
PACK_DIR="${SHARE}/packs/mypack" # packwiz project root (has pack.toml)
|
PACK_DIR="${SHARE}/packs/mypack" # packwiz project root (has pack.toml)
|
||||||
MODS_DIR="${SHARE}/mods" # live mods, served by the HTTP mirror
|
MODS_DIR="${SHARE}/mods" # server+client mods, loaded by the server
|
||||||
|
CLIENT_MODS_DIR="${SHARE}/client-mods" # client-only mods, never loaded server-side
|
||||||
BASE_URL="https://games.abdelbaki.eu/mods" # mirror base URL, no trailing slash
|
BASE_URL="https://games.abdelbaki.eu/mods" # mirror base URL, no trailing slash
|
||||||
PACK_URL="https://games.abdelbaki.eu/packs/mypack" # where PACK_DIR is served; "" to skip the check
|
PACK_URL="https://games.abdelbaki.eu/packs/mypack" # where PACK_DIR is served; "" to skip the check
|
||||||
SERVICE_NAME="minecraft.service"
|
SERVICE_NAME="minecraft.service"
|
||||||
|
|
@ -34,6 +35,7 @@ die() { log "ERROR: $*"; exit 1; }
|
||||||
priv() { if [ "$(id -u)" -eq 0 ]; then "$@"; else sudo "$@"; fi; }
|
priv() { if [ "$(id -u)" -eq 0 ]; then "$@"; else sudo "$@"; fi; }
|
||||||
|
|
||||||
BASE_URL="${BASE_URL%/}"
|
BASE_URL="${BASE_URL%/}"
|
||||||
|
CLIENT_URL="${CLIENT_URL%/}"
|
||||||
|
|
||||||
### 0. Preflight #########################################
|
### 0. Preflight #########################################
|
||||||
command -v systemctl >/dev/null 2>&1 || die "systemctl not found."
|
command -v systemctl >/dev/null 2>&1 || die "systemctl not found."
|
||||||
|
|
@ -46,65 +48,93 @@ mountpoint -q "${SHARE}" || die "${SHARE} is not a mountpoint — the ZFS datase
|
||||||
|
|
||||||
[ -f "${PACK_DIR}/pack.toml" ] || die "no pack.toml in ${PACK_DIR}"
|
[ -f "${PACK_DIR}/pack.toml" ] || die "no pack.toml in ${PACK_DIR}"
|
||||||
[ -d "${MODS_DIR}" ] || die "mods dir not found: ${MODS_DIR}"
|
[ -d "${MODS_DIR}" ] || die "mods dir not found: ${MODS_DIR}"
|
||||||
|
[ -d "${CLIENT_MODS_DIR}" ] || mkdir -p "${CLIENT_MODS_DIR}"
|
||||||
|
|
||||||
DATASET="$(findmnt -no SOURCE "${SHARE}" 2>/dev/null || true)"
|
DATASET="$(findmnt -no SOURCE "${SHARE}" 2>/dev/null || true)"
|
||||||
[ -n "${DATASET}" ] || die "could not determine the dataset backing ${SHARE}"
|
[ -n "${DATASET}" ] || die "could not determine the dataset backing ${SHARE}"
|
||||||
|
|
||||||
### 1. Sync pack metadata against the live mods folder ####
|
### 1. Sync pack metadata against the live mods folders ###
|
||||||
# Mods are sourced by URL with hashes pinned in the index, so `packwiz refresh`
|
# Mods are sourced by URL with hashes pinned in the index, so `packwiz refresh`
|
||||||
# alone will NOT notice a new or replaced jar — refresh only reindexes existing
|
# alone will NOT notice a new or replaced jar — refresh only reindexes existing
|
||||||
# .pw.toml files. Each jar has to be (re-)registered with `packwiz url add`.
|
# .pw.toml files. Each jar has to be (re-)registered with `packwiz url add`.
|
||||||
log "Syncing pack metadata against ${MODS_DIR}..."
|
#
|
||||||
|
# Two directories: MODS_DIR is what the server loads and clients also get
|
||||||
|
# (side=both); CLIENT_MODS_DIR reaches clients only (side=client). Rendering
|
||||||
|
# mods like Sodium link against LWJGL and crash a dedicated server on boot, so
|
||||||
|
# they must never sit in MODS_DIR.
|
||||||
|
log "Syncing pack metadata..."
|
||||||
cd "${PACK_DIR}"
|
cd "${PACK_DIR}"
|
||||||
|
|
||||||
shopt -s nullglob
|
|
||||||
JARS=("${MODS_DIR}"/*.jar)
|
|
||||||
shopt -u nullglob
|
|
||||||
|
|
||||||
# Zero jars is legitimate — an empty pack, or every mod deliberately removed.
|
|
||||||
# The orphan sweep below then clears the metadata to match.
|
|
||||||
if (( ${#JARS[@]} > 0 )); then
|
|
||||||
probe="$(curl -so /dev/null -w '%{http_code}' --max-time 10 "${BASE_URL}/" || true)"
|
|
||||||
probe="${probe:-000}"
|
|
||||||
[ "${probe}" = "000" ] && die "cannot reach ${BASE_URL} — packwiz needs it to hash each mod"
|
|
||||||
else
|
|
||||||
log "WARNING: no .jar files in ${MODS_DIR} — pack will end up empty"
|
|
||||||
fi
|
|
||||||
|
|
||||||
added=0; updated=0; unchanged=0; FAILED=()
|
added=0; updated=0; unchanged=0; FAILED=()
|
||||||
declare -A SEEN=()
|
declare -A SEEN=()
|
||||||
|
|
||||||
for jar in "${JARS[@]}"; do
|
# packwiz url add always writes side = "both"; force the right one.
|
||||||
file="$(basename "${jar}")"
|
set_side() {
|
||||||
modname="$(printf '%s' "${file%.jar}" | sed -E 's/-[0-9][0-9A-Za-z.+_-]*$//')"
|
local meta="$1" side="$2"
|
||||||
[ -n "${modname}" ] || modname="${file%.jar}"
|
[ -f "${meta}" ] || return 0
|
||||||
SEEN["${modname}"]=1
|
if grep -q '^side *=' "${meta}"; then
|
||||||
|
sed -i "s|^side *=.*|side = \"${side}\"|" "${meta}"
|
||||||
|
else
|
||||||
|
sed -i "0,/^filename *=/s|^\(filename *=.*\)$|\1\nside = \"${side}\"|" "${meta}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
meta="mods/${modname}.pw.toml"
|
sync_dir() {
|
||||||
enc="$(printf '%s' "${file}" | sed -e 's/%/%25/g' -e 's/ /%20/g' -e 's/+/%2B/g')"
|
local dir="$1" url="$2" side="$3" label="$4"
|
||||||
|
local jars=() jar file modname meta enc sha256 sha512 action probe
|
||||||
|
|
||||||
# Re-add only when the jar is new or its content changed. packwiz downloads
|
shopt -s nullglob
|
||||||
# the URL to hash it, so skipping unchanged mods is what keeps this quick.
|
jars=("${dir}"/*.jar)
|
||||||
if [ -f "${meta}" ]; then
|
shopt -u nullglob
|
||||||
sha256="$(sha256sum "${jar}" | cut -d' ' -f1)"
|
|
||||||
sha512="$(sha512sum "${jar}" | cut -d' ' -f1)"
|
if (( ${#jars[@]} == 0 )); then
|
||||||
if grep -qF -e "${sha256}" -e "${sha512}" "${meta}"; then
|
log " no ${label} jars in ${dir}"
|
||||||
(( unchanged++ )) || true
|
return 0
|
||||||
continue
|
fi
|
||||||
|
|
||||||
|
probe="$(curl -so /dev/null -w '%{http_code}' --max-time 10 "${url}/" || true)"
|
||||||
|
probe="${probe:-000}"
|
||||||
|
[ "${probe}" = "000" ] && die "cannot reach ${url} — packwiz needs it to hash each mod"
|
||||||
|
|
||||||
|
log " ${#jars[@]} ${label} jar(s) in ${dir}"
|
||||||
|
for jar in "${jars[@]}"; do
|
||||||
|
file="$(basename "${jar}")"
|
||||||
|
modname="$(printf '%s' "${file%.jar}" | sed -E 's/-[0-9][0-9A-Za-z.+_-]*$//')"
|
||||||
|
[ -n "${modname}" ] || modname="${file%.jar}"
|
||||||
|
SEEN["${modname}"]=1
|
||||||
|
|
||||||
|
meta="mods/${modname}.pw.toml"
|
||||||
|
enc="$(printf '%s' "${file}" | sed -e 's/%/%25/g' -e 's/ /%20/g' -e 's/+/%2B/g')"
|
||||||
|
|
||||||
|
# Re-add only when the jar is new or its content changed. packwiz
|
||||||
|
# downloads the URL to hash it, so skipping unchanged mods keeps this
|
||||||
|
# quick. The side is re-asserted either way, in case it drifted.
|
||||||
|
if [ -f "${meta}" ]; then
|
||||||
|
sha256="$(sha256sum "${jar}" | cut -d' ' -f1)"
|
||||||
|
sha512="$(sha512sum "${jar}" | cut -d' ' -f1)"
|
||||||
|
if grep -qF -e "${sha256}" -e "${sha512}" "${meta}"; then
|
||||||
|
set_side "${meta}" "${side}"
|
||||||
|
(( unchanged++ )) || true
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
action="updated"
|
||||||
|
else
|
||||||
|
action="added"
|
||||||
fi
|
fi
|
||||||
action="updated"
|
|
||||||
else
|
|
||||||
action="added"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if packwiz url add "${modname}" "${BASE_URL}/${enc}" >/dev/null 2>&1; then
|
if packwiz url add "${modname}" "${url}/${enc}" >/dev/null 2>&1; then
|
||||||
log " ${action}: ${modname}"
|
set_side "${meta}" "${side}"
|
||||||
[ "${action}" = "added" ] && (( added++ )) || (( updated++ ))
|
log " ${action}: ${modname} (${side})"
|
||||||
else
|
[ "${action}" = "added" ] && (( added++ )) || (( updated++ ))
|
||||||
log " FAILED: ${modname}"
|
else
|
||||||
FAILED+=("${file}")
|
log " FAILED: ${modname}"
|
||||||
fi
|
FAILED+=("${file}")
|
||||||
done
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
sync_dir "${MODS_DIR}" "${BASE_URL}" both "server+client"
|
||||||
|
sync_dir "${CLIENT_MODS_DIR}" "${CLIENT_URL}" client "client-only"
|
||||||
|
|
||||||
### 1b. Drop metadata for jars that are gone ##############
|
### 1b. Drop metadata for jars that are gone ##############
|
||||||
if (( REMOVE_ORPHANS )); then
|
if (( REMOVE_ORPHANS )); then
|
||||||
|
|
|
||||||
|
|
@ -161,6 +161,10 @@ fi
|
||||||
# before that. Setgid so jars dropped in by either side stay group-readable.
|
# before that. Setgid so jars dropped in by either side stay group-readable.
|
||||||
install -d -o "$MCUSER" -g "$MCUSER" -m 2775 "$SHARE/mods"
|
install -d -o "$MCUSER" -g "$MCUSER" -m 2775 "$SHARE/mods"
|
||||||
|
|
||||||
|
# Client-only mods live outside mods/ so the dedicated server never loads them:
|
||||||
|
# rendering mods link against LWJGL, which does not exist server-side.
|
||||||
|
install -d -o "$MCUSER" -g "$MCUSER" -m 2775 "$SHARE/client-mods"
|
||||||
|
|
||||||
# Without this the chown above locks the human out of the share they author the
|
# Without this the chown above locks the human out of the share they author the
|
||||||
# pack on. Share root and packs/ become group-writable and setgid, so files
|
# pack on. Share root and packs/ become group-writable and setgid, so files
|
||||||
# created by either side stay readable to both.
|
# created by either side stay readable to both.
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ import sys
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
|
|
||||||
# Top-level prefixes clients legitimately need.
|
# Top-level prefixes clients legitimately need.
|
||||||
ALLOWED_DIRS = ("mods/", "packs/")
|
ALLOWED_DIRS = ("mods/", "client-mods/", "packs/")
|
||||||
|
|
||||||
|
|
||||||
def is_allowed(path: str) -> bool:
|
def is_allowed(path: str) -> bool:
|
||||||
|
|
|
||||||
107
packwiz-setup.sh
107
packwiz-setup.sh
|
|
@ -19,6 +19,8 @@ AUTHOR=""
|
||||||
MCVER="1.21.1"
|
MCVER="1.21.1"
|
||||||
NFVER="" # empty = latest
|
NFVER="" # empty = latest
|
||||||
MODSDIR=""
|
MODSDIR=""
|
||||||
|
CLIENTDIR="" # client-only mods; not loaded by the server
|
||||||
|
CLIENTURL="" # defaults to $SITE_URL/client-mods
|
||||||
BASEURL=""
|
BASEURL=""
|
||||||
DIR=""
|
DIR=""
|
||||||
PACKVER="1.0.0"
|
PACKVER="1.0.0"
|
||||||
|
|
@ -45,6 +47,8 @@ while [ $# -gt 0 ]; do
|
||||||
-m|--mc) MCVER="${2:-}"; shift 2 ;;
|
-m|--mc) MCVER="${2:-}"; shift 2 ;;
|
||||||
-f|--neoforge) NFVER="${2:-}"; shift 2 ;;
|
-f|--neoforge) NFVER="${2:-}"; shift 2 ;;
|
||||||
-M|--mods-dir) MODSDIR="${2:-}"; shift 2 ;;
|
-M|--mods-dir) MODSDIR="${2:-}"; shift 2 ;;
|
||||||
|
-c|--client-mods) CLIENTDIR="${2:-}"; shift 2 ;;
|
||||||
|
--client-url) CLIENTURL="${2:-}"; shift 2 ;;
|
||||||
-u|--base-url) BASEURL="${2:-}"; shift 2 ;;
|
-u|--base-url) BASEURL="${2:-}"; shift 2 ;;
|
||||||
-v|--version) PACKVER="${2:-}"; shift 2 ;;
|
-v|--version) PACKVER="${2:-}"; shift 2 ;;
|
||||||
-d|--dir) DIR="${2:-}"; shift 2 ;;
|
-d|--dir) DIR="${2:-}"; shift 2 ;;
|
||||||
|
|
@ -104,6 +108,15 @@ if [ -n "$MODSDIR$BASEURL" ]; then
|
||||||
info "creating $MODSDIR"
|
info "creating $MODSDIR"
|
||||||
mkdir -p "$MODSDIR" || die "could not create $MODSDIR"
|
mkdir -p "$MODSDIR" || die "could not create $MODSDIR"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Client-only mods (Sodium, Iris, shader and GUI mods) must reach players
|
||||||
|
# but must never be loaded by the dedicated server — they link against
|
||||||
|
# LWJGL, which does not exist server-side. Separate directory, side=client.
|
||||||
|
[ -n "$CLIENTDIR" ] || CLIENTDIR="$SHARE/client-mods"
|
||||||
|
if [ ! -d "$CLIENTDIR" ]; then
|
||||||
|
info "creating $CLIENTDIR"
|
||||||
|
mkdir -p "$CLIENTDIR" || die "could not create $CLIENTDIR"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
BASEURL="${BASEURL%/}"
|
BASEURL="${BASEURL%/}"
|
||||||
|
|
||||||
|
|
@ -118,6 +131,7 @@ if [ -n "$SCHEME" ]; then
|
||||||
[ -z "$BASEURL" ] || BASEURL="$SCHEME://${BASEURL#*://}"
|
[ -z "$BASEURL" ] || BASEURL="$SCHEME://${BASEURL#*://}"
|
||||||
[ -z "$SITE_URL" ] || SITE_URL="$SCHEME://${SITE_URL#*://}"
|
[ -z "$SITE_URL" ] || SITE_URL="$SCHEME://${SITE_URL#*://}"
|
||||||
[ -z "$PACK_URL" ] || PACK_URL="$SCHEME://${PACK_URL#*://}"
|
[ -z "$PACK_URL" ] || PACK_URL="$SCHEME://${PACK_URL#*://}"
|
||||||
|
[ -z "$CLIENTURL" ] || CLIENTURL="$SCHEME://${CLIENTURL#*://}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ------------------------------------------------------------------ site ----
|
# ------------------------------------------------------------------ site ----
|
||||||
|
|
@ -138,6 +152,8 @@ if [ -n "$SITE_URL" ]; then
|
||||||
[ -n "$SERVER_ADDR" ] || SERVER_ADDR="$(sed -e 's#^[a-z]*://##' -e 's#/.*##' -e 's#:.*##' <<<"$SITE_URL")"
|
[ -n "$SERVER_ADDR" ] || SERVER_ADDR="$(sed -e 's#^[a-z]*://##' -e 's#/.*##' -e 's#:.*##' <<<"$SITE_URL")"
|
||||||
fi
|
fi
|
||||||
PACK_URL="${PACK_URL%/}"
|
PACK_URL="${PACK_URL%/}"
|
||||||
|
[ -n "$CLIENTURL" ] || [ -z "$SITE_URL" ] || CLIENTURL="$SITE_URL/client-mods"
|
||||||
|
CLIENTURL="${CLIENTURL%/}"
|
||||||
|
|
||||||
# Minecraft assumes 25565, so only show a port when it is not that — a
|
# Minecraft assumes 25565, so only show a port when it is not that — a
|
||||||
# needless ":25565" in the address is a classic source of player confusion.
|
# needless ":25565" in the address is a classic source of player confusion.
|
||||||
|
|
@ -230,62 +246,85 @@ info "initialising NeoForge pack for MC $MCVER in $(pwd)"
|
||||||
|
|
||||||
# ---------------------------------------------------------------- mirror ----
|
# ---------------------------------------------------------------- mirror ----
|
||||||
|
|
||||||
if [ -n "$MODSDIR" ]; then
|
# packwiz url add always writes side = "both". Client-only mods have to say
|
||||||
grep -q 'url' <<<"$("$PACKWIZ" --help 2>&1 || true)" \
|
# "client", so `packwiz-installer -s server` skips them and the dedicated
|
||||||
|| die "this packwiz build has no 'url' subcommand — cannot add mods by URL"
|
# server never tries to load a rendering mod.
|
||||||
|
set_side() {
|
||||||
shopt -s nullglob
|
local meta="$1" side="$2"
|
||||||
jars=("$MODSDIR"/*.jar)
|
[ -f "$meta" ] || return 0
|
||||||
shopt -u nullglob
|
if grep -q '^side *=' "$meta"; then
|
||||||
|
sed -i "s|^side *=.*|side = \"$side\"|" "$meta"
|
||||||
failed=()
|
|
||||||
if [ "${#jars[@]}" -eq 0 ]; then
|
|
||||||
# An empty pack is a valid starting point: drop jars in later and run
|
|
||||||
# mc-refresh-restart.sh to register them. Nothing needs fetching, so the
|
|
||||||
# mirror does not have to be reachable yet either.
|
|
||||||
warn "no .jar files in $MODSDIR yet — creating an empty pack"
|
|
||||||
else
|
else
|
||||||
# packwiz downloads each URL to hash it, so a mirror that isn't up yet
|
sed -i "0,/^filename *=/s|^\(filename *=.*\)$|\1\nside = \"$side\"|" "$meta"
|
||||||
# produces a pile of confusing per-mod failures. Check once, up front.
|
fi
|
||||||
info "checking mirror at $BASEURL"
|
}
|
||||||
probe="$(curl -so /dev/null -w '%{http_code}' --max-time 10 "$BASEURL/" || true)"
|
|
||||||
|
# Filename without .jar and without a trailing version: packwiz uses this as
|
||||||
|
# the .pw.toml basename, and it should stay stable across version bumps.
|
||||||
|
mod_name_for() {
|
||||||
|
local n
|
||||||
|
n="$(printf '%s' "${1%.jar}" | sed -E 's/-[0-9][0-9A-Za-z.+_-]*$//')"
|
||||||
|
[ -n "$n" ] || n="${1%.jar}"
|
||||||
|
printf '%s' "$n"
|
||||||
|
}
|
||||||
|
|
||||||
|
probe_mirror() {
|
||||||
|
local url="$1" probe
|
||||||
|
info "checking mirror at $url"
|
||||||
|
probe="$(curl -so /dev/null -w '%{http_code}' --max-time 10 "$url/" || true)"
|
||||||
probe="${probe:-000}"
|
probe="${probe:-000}"
|
||||||
case "$probe" in
|
case "$probe" in
|
||||||
2*|3*|40[34]) ;; # a listing may legitimately be forbidden
|
2*|3*|40[34]) ;; # a listing may legitimately be forbidden
|
||||||
000) die "cannot reach $BASEURL — the pack server or proxy is not up yet" ;;
|
000) die "cannot reach $url — the pack server or proxy is not up yet" ;;
|
||||||
*) warn "$BASEURL/ returned HTTP $probe; continuing, individual mods may fail" ;;
|
*) warn "$url/ returned HTTP $probe; continuing, individual mods may fail" ;;
|
||||||
esac
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
info "adding ${#jars[@]} mods from the mirror"
|
add_dir() {
|
||||||
|
local dir="$1" url="$2" side="$3" label="$4"
|
||||||
|
local jars=() jar file modname enc
|
||||||
|
|
||||||
|
shopt -s nullglob; jars=("$dir"/*.jar); shopt -u nullglob
|
||||||
|
if [ "${#jars[@]}" -eq 0 ]; then
|
||||||
|
info "no jars in $dir — no $label mods"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
probe_mirror "$url"
|
||||||
|
info "adding ${#jars[@]} $label mods"
|
||||||
for jar in "${jars[@]}"; do
|
for jar in "${jars[@]}"; do
|
||||||
file="$(basename "$jar")"
|
file="$(basename "$jar")"
|
||||||
|
modname="$(mod_name_for "$file")"
|
||||||
# Mod name: filename without .jar and without a trailing version, since
|
# Percent-encode the filename; leave the base URL alone.
|
||||||
# packwiz uses this as the .pw.toml basename and it should stay stable
|
|
||||||
# across version bumps.
|
|
||||||
modname="$(printf '%s' "${file%.jar}" | sed -E 's/-[0-9][0-9A-Za-z.+_-]*$//')"
|
|
||||||
[ -n "$modname" ] || modname="${file%.jar}"
|
|
||||||
|
|
||||||
# Percent-encode the filename for the URL; leave the base URL alone.
|
|
||||||
enc="$(printf '%s' "$file" | sed -e 's/%/%25/g' -e 's/ /%20/g' -e 's/+/%2B/g')"
|
enc="$(printf '%s' "$file" | sed -e 's/%/%25/g' -e 's/ /%20/g' -e 's/+/%2B/g')"
|
||||||
|
|
||||||
if "$PACKWIZ" url add "$modname" "$BASEURL/$enc" >/dev/null 2>&1; then
|
if "$PACKWIZ" url add "$modname" "$url/$enc" >/dev/null 2>&1; then
|
||||||
printf ' + %s\n' "$modname"
|
set_side "mods/$modname.pw.toml" "$side"
|
||||||
|
printf ' + %-40s %s\n' "$modname" "$side"
|
||||||
|
total=$(( total + 1 ))
|
||||||
else
|
else
|
||||||
printf ' ! %s (failed)\n' "$modname"
|
printf ' ! %s (failed)\n' "$modname"
|
||||||
failed+=("$file")
|
failed+=("$file")
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
}
|
||||||
|
|
||||||
fi
|
if [ -n "$MODSDIR" ]; then
|
||||||
|
grep -q 'url' <<<"$("$PACKWIZ" --help 2>&1 || true)" \
|
||||||
|
|| die "this packwiz build has no 'url' subcommand — cannot add mods by URL"
|
||||||
|
|
||||||
|
failed=(); total=0
|
||||||
|
add_dir "$MODSDIR" "$BASEURL" both "server+client"
|
||||||
|
add_dir "$CLIENTDIR" "$CLIENTURL" client "client-only"
|
||||||
|
|
||||||
"$PACKWIZ" refresh
|
"$PACKWIZ" refresh
|
||||||
|
info "$total mods in the pack"
|
||||||
|
|
||||||
if [ "${#failed[@]}" -gt 0 ]; then
|
if [ "${#failed[@]}" -gt 0 ]; then
|
||||||
echo
|
echo
|
||||||
warn "${#failed[@]} of ${#jars[@]} mods failed to add:"
|
warn "${#failed[@]} mod(s) failed to add:"
|
||||||
printf ' %s\n' "${failed[@]}" >&2
|
printf ' %s\n' "${failed[@]}" >&2
|
||||||
warn "re-run those by hand to see the error, e.g."
|
warn "re-run one by hand to see the error, e.g."
|
||||||
warn " $PACKWIZ url add <name> $BASEURL/${failed[0]}"
|
warn " $PACKWIZ url add <name> $BASEURL/${failed[0]}"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue