feat(tooling): add mirror-mods.sh (vendor jars + rewrite .pw.toml URLs)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
136
tooling/mirror-mods.sh
Executable file
136
tooling/mirror-mods.sh
Executable file
@@ -0,0 +1,136 @@
|
||||
#!/usr/bin/env bash
|
||||
# mirror-mods.sh — vendor mod jars locally + rewrite packwiz download URLs.
|
||||
#
|
||||
# For offline (air-gapped) LAN play, packwiz .pw.toml files point at the
|
||||
# Modrinth/CF CDN. This script downloads each jar into pack/mods-files/,
|
||||
# rewrites the [download] url to the local Caddy mirror, then runs
|
||||
# `packwiz refresh` so index.toml/hashes match the rewritten state.
|
||||
#
|
||||
# Cautious by design: halts on download failure or hash mismatch, idempotent
|
||||
# (skips already-vendored valid jars), and no-ops gracefully with zero mods.
|
||||
# pack/mods-files/ is gitignored — only the .pw.toml metadata is tracked.
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "$0")/.." # repo root
|
||||
|
||||
# --- env -------------------------------------------------------------------
|
||||
[ -f .env ] && { set -a; . ./.env; set +a; }
|
||||
: "${BASE_DOMAIN:?BASE_DOMAIN unset (set in .env)}"
|
||||
|
||||
# --- packwiz binary --------------------------------------------------------
|
||||
PACKWIZ="/home/oier/go/bin/packwiz"
|
||||
[ -x "$PACKWIZ" ] || PACKWIZ="$(command -v packwiz || true)"
|
||||
[ -n "$PACKWIZ" ] || { echo "error: packwiz not found (looked at /home/oier/go/bin/packwiz and PATH)" >&2; exit 1; }
|
||||
|
||||
PACK_DIR="pack"
|
||||
MODS_META_DIR="$PACK_DIR/mods"
|
||||
MODS_FILES_DIR="$PACK_DIR/mods-files"
|
||||
MIRROR_BASE="http://packwiz.${BASE_DOMAIN}/mods-files"
|
||||
|
||||
[ -f "$PACK_DIR/pack.toml" ] || { echo "error: $PACK_DIR/pack.toml not found — run packwiz init first" >&2; exit 1; }
|
||||
|
||||
# --- collect .pw.toml files ------------------------------------------------
|
||||
shopt -s nullglob
|
||||
metas=("$MODS_META_DIR"/*.pw.toml)
|
||||
shopt -u nullglob
|
||||
|
||||
if [ "${#metas[@]}" -eq 0 ]; then
|
||||
echo "no mods in $MODS_META_DIR/ — nothing to mirror (no-op)."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
mkdir -p "$MODS_FILES_DIR"
|
||||
|
||||
# --- helpers ---------------------------------------------------------------
|
||||
# Read a top-level scalar TOML key (filename) from a .pw.toml.
|
||||
toml_top_value() { # $1=file $2=key
|
||||
sed -n "s/^[[:space:]]*$2[[:space:]]*=[[:space:]]*\"\(.*\)\"[[:space:]]*\$/\1/p" "$1" | head -n1
|
||||
}
|
||||
|
||||
# Read a key from the [download] table only.
|
||||
toml_download_value() { # $1=file $2=key
|
||||
awk -v key="$2" '
|
||||
/^\[/ { in_dl = ($0 ~ /^\[download\]/) ; next }
|
||||
in_dl {
|
||||
line=$0
|
||||
sub(/^[ \t]*/, "", line)
|
||||
if (line ~ "^" key "[ \t]*=") {
|
||||
sub("^" key "[ \t]*=[ \t]*", "", line)
|
||||
gsub(/^"|"[ \t]*$/, "", line)
|
||||
print line
|
||||
exit
|
||||
}
|
||||
}
|
||||
' "$1"
|
||||
}
|
||||
|
||||
sha256_of() { sha256sum "$1" | awk '{print $1}'; }
|
||||
|
||||
changed=0
|
||||
echo "mirroring ${#metas[@]} mod(s) → $MIRROR_BASE"
|
||||
|
||||
for meta in "${metas[@]}"; do
|
||||
filename="$(toml_top_value "$meta" filename)"
|
||||
url="$(toml_download_value "$meta" url)"
|
||||
hash_format="$(toml_download_value "$meta" hash-format)"
|
||||
want_hash="$(toml_download_value "$meta" hash)"
|
||||
|
||||
[ -n "$filename" ] || { echo "error: no filename in $meta" >&2; exit 1; }
|
||||
[ -n "$url" ] || { echo "error: no [download] url in $meta" >&2; exit 1; }
|
||||
|
||||
dest="$MODS_FILES_DIR/$filename"
|
||||
already_local="$MIRROR_BASE/$filename"
|
||||
|
||||
# If the .pw.toml already points at the mirror and the jar exists & verifies,
|
||||
# skip (idempotent re-run).
|
||||
if [ "$url" = "$already_local" ] && [ -f "$dest" ]; then
|
||||
if [ "${hash_format:-sha256}" = "sha256" ] && [ -n "$want_hash" ]; then
|
||||
if [ "$(sha256_of "$dest")" = "$want_hash" ]; then
|
||||
echo " skip $filename (already vendored, hash ok)"
|
||||
continue
|
||||
fi
|
||||
echo " re-fetch $filename (local hash mismatch)"
|
||||
else
|
||||
echo " skip $filename (already vendored)"
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
|
||||
# Download (from current url — CDN on first run). Use a temp file so a failed
|
||||
# download never leaves a partial jar behind.
|
||||
echo " fetch $filename"
|
||||
tmp="$dest.partial"
|
||||
if ! curl -fSL --retry 3 -o "$tmp" "$url"; then
|
||||
rm -f "$tmp"
|
||||
echo "error: download failed for $filename ($url)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Verify against the recorded hash before trusting the file.
|
||||
if [ "${hash_format:-sha256}" = "sha256" ] && [ -n "$want_hash" ]; then
|
||||
got_hash="$(sha256_of "$tmp")"
|
||||
if [ "$got_hash" != "$want_hash" ]; then
|
||||
rm -f "$tmp"
|
||||
echo "error: hash mismatch for $filename" >&2
|
||||
echo " expected: $want_hash" >&2
|
||||
echo " got: $got_hash" >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
mv -f "$tmp" "$dest"
|
||||
|
||||
# Rewrite the [download] url to the local mirror if not already.
|
||||
if [ "$url" != "$already_local" ]; then
|
||||
esc_old="$(printf '%s' "$url" | sed -e 's/[\/&]/\\&/g')"
|
||||
esc_new="$(printf '%s' "$already_local" | sed -e 's/[\/&]/\\&/g')"
|
||||
sed -i "s/^\([[:space:]]*url[[:space:]]*=[[:space:]]*\)\"$esc_old\"/\1\"$esc_new\"/" "$meta"
|
||||
echo " url → $already_local"
|
||||
changed=1
|
||||
fi
|
||||
done
|
||||
|
||||
# --- refresh so index.toml + hashes reflect the rewritten metadata ---------
|
||||
echo "running packwiz refresh ..."
|
||||
( cd "$PACK_DIR" && "$PACKWIZ" refresh )
|
||||
|
||||
echo "done. mirrored ${#metas[@]} mod(s)$( [ "$changed" -eq 1 ] && echo ', URLs rewritten to local mirror' )."
|
||||
Reference in New Issue
Block a user