feat: custom server mods build pipeline (IPSable, Create Diesel Generators)

This commit is contained in:
MiTHRAL 2026-08-20 13:03:24 -04:00
commit 1705820819
9 changed files with 344 additions and 0 deletions

View file

@ -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"

10
.gitignore vendored Normal file
View file

@ -0,0 +1,10 @@
# Build outputs — never commit JARs or downloaded sources
out/
build/
*.jar
*.zip
# Java/Gradle
.gradle/
**/build/
**/.gradle/

67
README.md Normal file
View file

@ -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/<name>/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/<mod>/*.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>.jar`.
## Adding a mod
1. `mkdir mods/<name>`
2. Write `mods/<name>/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.

View file

@ -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 <MOD_NAME>-<git-ref>.jar for stable pack references.
PACK_MOD_NAME="create-diesel-generators"

23
mods/ipsable/manifest.sh Executable file
View file

@ -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"

View file

@ -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=""

30
scripts/build-all.sh Executable file
View file

@ -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"

68
scripts/build-mod.sh Executable file
View file

@ -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/<name>/.
#
# Usage: ./scripts/build-mod.sh <name>
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 <mod-name>" >&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"

63
scripts/lib.sh Executable file
View file

@ -0,0 +1,63 @@
#!/usr/bin/env bash
# Shared helpers for building custom mods from manifests.
# Expected layout:
# mods/<name>/manifest.sh -> source the manifest (defines MOD_NAME, SOURCE_URL, ...)
# scripts/lib.sh -> this file
# out/<name>/ -> 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
}