feat: custom server mods build pipeline (IPSable, Create Diesel Generators)
This commit is contained in:
commit
1705820819
9 changed files with 344 additions and 0 deletions
55
.gitea/workflows/build-mods.yaml
Normal file
55
.gitea/workflows/build-mods.yaml
Normal 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
10
.gitignore
vendored
Normal 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
67
README.md
Normal 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.
|
||||
21
mods/create-diesel-generators/manifest.sh
Executable file
21
mods/create-diesel-generators/manifest.sh
Executable 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
23
mods/ipsable/manifest.sh
Executable 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"
|
||||
7
mods/ipsable_zzz_smoketest.sh
Normal file
7
mods/ipsable_zzz_smoketest.sh
Normal 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
30
scripts/build-all.sh
Executable 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
68
scripts/build-mod.sh
Executable 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
63
scripts/lib.sh
Executable 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
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue