From 17058208193a784e705ed785ecbb54eaa7dd675d Mon Sep 17 00:00:00 2001 From: MiTHRAL Date: Thu, 20 Aug 2026 13:03:24 -0400 Subject: [PATCH] feat: custom server mods build pipeline (IPSable, Create Diesel Generators) --- .gitea/workflows/build-mods.yaml | 55 ++++++++++++++++++ .gitignore | 10 ++++ README.md | 67 ++++++++++++++++++++++ mods/create-diesel-generators/manifest.sh | 21 +++++++ mods/ipsable/manifest.sh | 23 ++++++++ mods/ipsable_zzz_smoketest.sh | 7 +++ scripts/build-all.sh | 30 ++++++++++ scripts/build-mod.sh | 68 +++++++++++++++++++++++ scripts/lib.sh | 63 +++++++++++++++++++++ 9 files changed, 344 insertions(+) create mode 100644 .gitea/workflows/build-mods.yaml create mode 100644 .gitignore create mode 100644 README.md create mode 100755 mods/create-diesel-generators/manifest.sh create mode 100755 mods/ipsable/manifest.sh create mode 100644 mods/ipsable_zzz_smoketest.sh create mode 100755 scripts/build-all.sh create mode 100755 scripts/build-mod.sh create mode 100755 scripts/lib.sh diff --git a/.gitea/workflows/build-mods.yaml b/.gitea/workflows/build-mods.yaml new file mode 100644 index 0000000..2e13e2c --- /dev/null +++ b/.gitea/workflows/build-mods.yaml @@ -0,0 +1,55 @@ +name: Build Custom Server Mods + +on: + push: + tags: ['v*'] + +jobs: + build: + runs-on: docker + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup JDK 21 + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '21' + + - name: Make scripts executable + run: chmod +x scripts/*.sh mods/*/manifest.sh + + - name: Build all manifests + run: ./scripts/build-all.sh + + - name: Create release + id: release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + API: https://git.mithraic.cloud/api/v1/repos/ad3laid3/custom-server-mods/releases + run: | + set -e + TAG="${GITHUB_REF_NAME}" + AUTH="Authorization: token ${GITHUB_TOKEN}" + RELEASE_JSON=$(curl -sS -X POST "$API" \ + -H "$AUTH" -H "Content-Type: application/json" \ + -d "{\"tag_name\":\"$TAG\",\"name\":\"Custom Server Mods $TAG\",\"body\":\"Pre-built JARs for SciCraft pack.\"}") + RELEASE_ID=$(printf '%s' "$RELEASE_JSON" | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])") + echo "release_id=$RELEASE_ID" >> "$GITHUB_ENV" + echo "API=$API" >> "$GITHUB_ENV" + + - name: Upload jars as release assets + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -e + AUTH="Authorization: token ${GITHUB_TOKEN}" + for jar in out/*/*.jar; do + [ -f "$jar" ] || continue + echo "Uploading $jar" + curl -sS -X POST "$API/$RELEASE_ID/assets?name=$(basename "$jar")" \ + -H "$AUTH" \ + -F "attachment=@$jar" + done + echo "Uploaded all jars to release $RELEASE_ID" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cbc5ade --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +# Build outputs — never commit JARs or downloaded sources +out/ +build/ +*.jar +*.zip + +# Java/Gradle +.gradle/ +**/build/ +**/.gradle/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..a828eb4 --- /dev/null +++ b/README.md @@ -0,0 +1,67 @@ +# Custom Server Mods + +Builds permissively-licensed custom forks / newer-than-release builds of mods +used by the **SciCraft** NeoForge 1.21.1 modpack, and publishes the resulting +JARs as release assets that `packwiz` entries can point at. + +Only mods whose licenses permit redistribution (MIT / Apache-2.0 / LGPL / BSD / +MPL) are included. Every mod has a `mods//manifest.sh` describing exactly +what gets cloned and built. **Nothing is built unless a manifest exists.** + +## Why + +Some mods we use are either: +- **abandoned/buggy** upstream (their Modrinth/CurseForge release is stale or + broken), or +- **ahead of release** — the GitHub source has fixes not yet published to + Modrinth/CurseForge. + +For those, we compile from source and host our own build so the pack always gets +the fixed/newest behavior without waiting on a release. + +## Current manifests + +| Mod | License | Why we build it | Build cmd | +|-----|---------|-----------------|-----------| +| `ipsable` | Apache-2.0 | Immersive-Portals×Sable fork; experimental, GitHub-only | `./gradlew jarJar` | +| `create-diesel-generators` | MIT | GitHub ahead of Modrinth (CME fix, configurable oil barrel) | `./gradlew build` | + +## Build locally + +Requires **JDK 21** and network access (NeoForge deps are downloaded by Gradle). + +```bash +./scripts/build-all.sh # build every manifest in mods/ +./scripts/build-mod.sh ipsable # build one mod +``` + +Built JARs land in `out//*.jar` (gitignored). + +## Build via CI + +The Gitea Actions workflow `.gitea/workflows/build-mods.yaml` runs the same +logic on the runner and uploads each JAR to the GitHub/Gitea **release assets** +for the tag that triggered it. Tag with `v*` to publish: + +```bash +git tag v0.1.0 && git push origin v0.1.0 +``` + +The pack's `*.pw.toml` entries then reference +`https://git.mithraic.cloud/ad3laid3/custom-server-mods/releases/download/v0.1.0/.jar`. + +## Adding a mod + +1. `mkdir mods/` +2. Write `mods//manifest.sh` (see existing manifests for the shape): + - `MOD_NAME`, `SOURCE_URL` (git clone URL), `SOURCE_REF` (tag/branch/commit) + - `BUILD_CMD` (e.g. `./gradlew jarJar`), `JAR_GLOB` (relative to the source dir) + - `LICENSE` (must be permissive — review before adding) +3. Optionally add a `patch/` step if you maintain local fixes (see `PATCH_CMD`). +4. Commit, tag `vX`, push; CI builds and attaches the JAR. + +## License review policy + +Only add a mod here if its license allows redistribution AND it's already used +(or intended) in the SciCraft pack. When in doubt, do **not** add it. Re-review +licenses whenever the upstream source changes. diff --git a/mods/create-diesel-generators/manifest.sh b/mods/create-diesel-generators/manifest.sh new file mode 100755 index 0000000..f0227b5 --- /dev/null +++ b/mods/create-diesel-generators/manifest.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +# Manifest: Create Diesel Generators +# MIT. GitHub (1.21.1 branch) is ahead of the Modrinth release with fixes +# (e.g. "Fix occasional CME on start", configurable oil barrel max length). +# See: https://github.com/george8188625/Create-Diesel-Generators + +MOD_NAME="createdieselgenerators" + +SOURCE_URL="https://github.com/george8188625/Create-Diesel-Generators.git" +SOURCE_REF="1.21.1" + +BUILD_CMD="./gradlew build" +JAR_GLOB="build/libs/*.jar" + +LICENSE="MIT" + +PATCH_CMD="" + +# Name shown in the pack. The exact jar name is version-dependent; CI renames +# the produced jar to -.jar for stable pack references. +PACK_MOD_NAME="create-diesel-generators" diff --git a/mods/ipsable/manifest.sh b/mods/ipsable/manifest.sh new file mode 100755 index 0000000..cba1add --- /dev/null +++ b/mods/ipsable/manifest.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +# Manifest: Immersive Portals x Sable (IPSable) +# Apache-2.0. Custom fork of Immersive Portals for the Sable + Create +# Aeronautics stack. GitHub-only, pre-release. We compile from source so the +# pack always matches our exact stack (Create 6.0.10 / Aeronautics 1.3.x). +# See: https://github.com/r2smith141/IPSable + +MOD_NAME="immersive-portals-sable-compat" + +SOURCE_URL="https://github.com/r2smith141/IPSable.git" +SOURCE_REF="v0.5.0" + +# ./gradlew jarJar produces the distributable jar in build/libs/ +BUILD_CMD="./gradlew jarJar" +JAR_GLOB="build/libs/*.jar" + +LICENSE="Apache-2.0" + +# Optional: run before BUILD_CMD (e.g. apply local patches). Leave empty to skip. +PATCH_CMD="" + +# The jar filename the pack's ipsable.pw.toml references (must match output). +EXPECTED_JAR="immersive-portals-sable-compat-0.5.0+ip-6.0.7.jar" diff --git a/mods/ipsable_zzz_smoketest.sh b/mods/ipsable_zzz_smoketest.sh new file mode 100644 index 0000000..048f82e --- /dev/null +++ b/mods/ipsable_zzz_smoketest.sh @@ -0,0 +1,7 @@ +MOD_NAME="smoketest" +SOURCE_URL="file:///tmp/fake-mod-source" +SOURCE_REF="main" +BUILD_CMD="mkdir -p build/libs && echo smoke > build/libs/smoketest-1.0.0.jar" +JAR_GLOB="build/libs/*.jar" +LICENSE="MIT" +PATCH_CMD="" diff --git a/scripts/build-all.sh b/scripts/build-all.sh new file mode 100755 index 0000000..2af643f --- /dev/null +++ b/scripts/build-all.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +# Build every mod with a manifest in mods/. +# +# Usage: ./scripts/build-all.sh [mod-name ...] +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck disable=SC1091 +source "$SCRIPT_DIR/lib.sh" + +check_jdk + +if [[ "$#" -gt 0 ]]; then + targets=("$@") +else + mapfile -t targets < <(list_manifests) +fi + +if [[ "${#targets[@]}" -eq 0 ]]; then + echo "No manifests found in $MODS_DIR" + exit 0 +fi + +echo "Building ${#targets[@]} mod(s): ${targets[*]}" +for name in "${targets[@]}"; do + "$SCRIPT_DIR/build-mod.sh" "$name" +done + +echo "" +echo "All done. Jars are in $OUT_DIR" diff --git a/scripts/build-mod.sh b/scripts/build-mod.sh new file mode 100755 index 0000000..8a24dc6 --- /dev/null +++ b/scripts/build-mod.sh @@ -0,0 +1,68 @@ +#!/usr/bin/env bash +# Build a single custom mod from its manifest: clone (or refresh) the source at +# the pinned ref, apply optional patch, run the build, then copy produced jars +# to out//. +# +# Usage: ./scripts/build-mod.sh +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck disable=SC1091 +source "$SCRIPT_DIR/lib.sh" + +name="${1:-}" +if [[ -z "$name" ]]; then + echo "Usage: $0 " >&2 + exit 1 +fi + +load_manifest "$name" + +src_dir="$WORK_DIR/$name" +mkdir -p "$WORK_DIR" "$OUT_DIR/$name" + +echo "==> [$name] ensuring source at $SOURCE_REF from $SOURCE_URL" +if [[ -d "$src_dir/.git" ]]; then + git -C "$src_dir" fetch --tags --force origin +else + git clone --no-single-branch "$SOURCE_URL" "$src_dir" +fi + +# Checkout the pinned ref robustly: prefer an exact tag, then a remote branch, +# then fall back to a raw commit SHA. +if git -C "$src_dir" rev-parse -q --verify "refs/tags/$SOURCE_REF" >/dev/null 2>&1; then + git -C "$src_dir" checkout --force "$SOURCE_REF" +elif git -C "$src_dir" rev-parse -q --verify "origin/$SOURCE_REF" >/dev/null 2>&1; then + git -C "$src_dir" checkout -B "$SOURCE_REF" "origin/$SOURCE_REF" +else + git -C "$src_dir" checkout --force "$SOURCE_REF" +fi +git -C "$src_dir" submodule update --init --recursive || true + +if [[ -n "${PATCH_CMD:-}" ]]; then + echo "==> [$name] applying patch: $PATCH_CMD" + ( cd "$src_dir" && eval "$PATCH_CMD" ) +fi + +echo "==> [$name] running: $BUILD_CMD" +( cd "$src_dir" && eval "$BUILD_CMD" ) + +echo "==> [$name] collecting jars matching '$JAR_GLOB'" +# shellcheck disable=SC2086 +shopt -s nullglob +found=0 +for jar in "$src_dir"/$JAR_GLOB; do + [[ -f "$jar" ]] || continue + base="$(basename "$jar")" + cp "$jar" "$OUT_DIR/$name/$base" + echo " -> out/$name/$base" + found=1 +done +shopt -u nullglob + +if [[ "$found" -eq 0 ]]; then + echo "ERROR: [$name] no jar matched '$JAR_GLOB' after build" >&2 + exit 1 +fi + +echo "==> [$name] done" diff --git a/scripts/lib.sh b/scripts/lib.sh new file mode 100755 index 0000000..68134df --- /dev/null +++ b/scripts/lib.sh @@ -0,0 +1,63 @@ +#!/usr/bin/env bash +# Shared helpers for building custom mods from manifests. +# Expected layout: +# mods//manifest.sh -> source the manifest (defines MOD_NAME, SOURCE_URL, ...) +# scripts/lib.sh -> this file +# out// -> built jars + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +MODS_DIR="$REPO_ROOT/mods" +OUT_DIR="$REPO_ROOT/out" +WORK_DIR="$REPO_ROOT/build" + +require_manifest() { + local name="$1" + local manifest="$MODS_DIR/$name/manifest.sh" + if [[ ! -f "$manifest" ]]; then + echo "ERROR: no manifest at $manifest" >&2 + return 1 + fi +} + +load_manifest() { + local name="$1" + require_manifest "$name" + # shellcheck disable=SC1090 + source "$MODS_DIR/$name/manifest.sh" + : "${MOD_NAME:?manifest must set MOD_NAME}" + : "${SOURCE_URL:?manifest must set SOURCE_URL}" + : "${BUILD_CMD:?manifest must set BUILD_CMD}" + : "${JAR_GLOB:?manifest must set JAR_GLOB}" + : "${LICENSE:?manifest must set LICENSE}" +} + +list_manifests() { + for d in "$MODS_DIR"/*/; do + [[ -d "$d" ]] || continue + basename "$d" + done +} + +check_jdk() { + local ver + ver="$(java -version 2>&1 | awk -F'"' '/version/ {print $2; exit}')" + case "$ver" in + 21*) + return 0 + ;; + esac + # Not on JDK 21: try to switch to a local JDK 21 install so the build uses + # the right toolchain for NeoForge 1.21.1 mods. + for jdk in /usr/lib/jvm/java-21-openjdk /usr/lib/jvm/java-21*; do + if [[ -x "$jdk/bin/java" ]]; then + export JAVA_HOME="$jdk" + export PATH="$jdk/bin:$PATH" + echo "Using JDK 21 at $jdk" + return 0 + fi + done + echo "WARNING: JDK $ver detected and no JDK 21 found; NeoForge 1.21.1 mods are built with JDK 21." >&2 +}