chore(ci): improve auto updater

This commit is contained in:
omo50
2026-04-19 12:51:03 -06:00
parent 8dcdc24284
commit 7f98145702
4 changed files with 191 additions and 109 deletions

View File

@@ -14,53 +14,65 @@ jobs:
with:
token: ${{ secrets.FORGEJO_TOKEN }}
fetch-depth: 1
filter: blob:none
sparse-checkout: |
modpacks
src/actions
src/actions/updater
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: 'stable'
cache: true
- name: Cache Packwiz Binaries
id: cache-tooling
uses: actions/cache@v4
with:
path: $HOME/go/bin
key: tooling-${{ runner.os }}-packwiz
- name: Install Tooling
if: steps.cache-tooling.outputs.cache-hit != 'true'
run: |
mkdir -p $HOME/go/bin
go install github.com/packwiz/packwiz@latest
go install github.com/Merith-TK/packwiz-wrapper/cmd/pw@main
- name: Add Path
run: echo "$HOME/go/bin" >> $GITHUB_PATH
- name: Cache Updater
- name: Cache Updater Binary
id: cache-updater
uses: actions/cache@v4
with:
path: ./updater-bin
key: updater-v3-${{ runner.os }}-${{ hashFiles('src/actions/updater/**') }}
key: updater-v4-${{ runner.os }}-${{ hashFiles('src/actions/updater/**/*.rs', 'src/actions/updater/Cargo.toml', 'src/actions/updater/Cargo.lock') }}
- name: Rust Cache
- name: Install Rust
if: steps.cache-updater.outputs.cache-hit != 'true'
uses: Swatinem/rust-cache@v2
uses: https://github.com/dtolnay/rust-toolchain@stable
- name: Rust Cache (Compiler Internals)
if: steps.cache-updater.outputs.cache-hit != 'true'
uses: https://github.com/Swatinem/rust-cache@v2
with:
workspaces: "src/actions/updater -> target"
- name: Build Updater
if: steps.cache-updater.outputs.cache-hit != 'true'
run: |
cargo build --release --manifest-path src/actions/updater/Cargo.toml
cargo build --release --manifest-path src/actions/updater/Cargo.toml --bin updater
mkdir -p ./updater-bin
cp src/actions/updater/target/release/updater ./updater-bin/updater
- name: Cache Packwiz Binary
id: cache-go
uses: actions/cache@v4
with:
path: ~/go/bin
key: go-bin-packwiz-v1-${{ runner.os }}
- name: Setup Go
if: steps.cache-go.outputs.cache-hit != 'true'
uses: https://github.com/actions/setup-go@v5
with:
go-version: 'stable'
cache: true
- name: Install Packwiz
if: steps.cache-go.outputs.cache-hit != 'true'
run: go install github.com/packwiz/packwiz@latest
- name: Add Go bin to PATH
run: echo "$HOME/go/bin" >> $GITHUB_PATH
- name: Cache Packwiz Downloads
uses: actions/cache@v4
with:
path: ~/.cache/packwiz
key: packwiz-cache-${{ runner.os }}-${{ github.run_id }}
restore-keys: |
packwiz-cache-${{ runner.os }}-
- name: Run Updater
id: rust-update
continue-on-error: true
@@ -68,10 +80,10 @@ jobs:
chmod +x ./updater-bin/updater
./updater-bin/updater
- name: Run Shell Updater
- name: Run Shell Fallback
if: steps.rust-update.outcome == 'failure'
run: |
echo "Rust Updater failed. Falling back to Shell..."
echo "Rust updater failed, falling back to shell..."
chmod +x ./modpacks/update-refresh.sh
./modpacks/update-refresh.sh

View File

@@ -1,8 +1,41 @@
#!/bin/bash
echo Updating
(cd ./modpacks/simply && pw batch update -a -y && pw batch refresh -y) &
(cd ./modpacks/rc-plus && pw batch update -a -y && pw batch refresh -y) &
(cd ./modpacks/2k && pw batch update -a -y && pw batch refresh -y) &
(cd ./modpacks/rekindled && pw batch update -a -y && pw batch refresh -y) &
wait
echo Done
set -eu
PACKS=("simply" "rc-plus" "2k" "rekindled")
echo "Updating..."
pids=()
for pack in "${PACKS[@]}"; do
pack_dir="modpacks/$pack"
if [ ! -d "$pack_dir" ]; then
echo "warning: $pack_dir missing, skipping"
continue
fi
for subdir in "$pack_dir"/*-mr "$pack_dir"/*-cf; do
[ -d "$subdir" ] || continue
(
echo "[$subdir] updating"
if (cd "$subdir" && packwiz update -a -y); then
echo "[$subdir] ok"
else
echo "[$subdir] FAIL" >&2
exit 1
fi
) &
pids+=($!)
done
done
fail=0
for pid in "${pids[@]}"; do
wait "$pid" || fail=$((fail + 1))
done
if [ "$fail" -gt 0 ]; then
echo "$fail subdir(s) failed" >&2
exit 1
fi
echo "Done"

View File

@@ -3,6 +3,9 @@ name = "updater"
version = "26.4.0"
edition = "2024"
[dependencies]
anyhow = "1.0"
[[bin]]
name = "updater"
path = "updater.rs"

View File

@@ -1,87 +1,121 @@
use std::process::Command;
use std::sync::{Arc, Mutex};
use std::thread;
use anyhow::{bail, Context, Result};
use std::{
fs,
path::PathBuf,
process::Command,
sync::{Arc, Mutex},
thread,
};
fn main() {
let modpacks = vec!["simply", "rc-plus", "2k", "rekindled"];
let max_concurrent = 4;
const PACKS: &[&str] = &["simply", "rc-plus", "2k", "rekindled"];
const MAX_CONCURRENT: usize = 8;
let modpacks_queue = Arc::new(Mutex::new(modpacks.into_iter()));
let errors = Arc::new(Mutex::new(Vec::new()));
let mut workers = vec![];
println!("starting throttled parallel updates ({} at a time)...", max_concurrent);
for i in 0..max_concurrent {
let queue_clone = Arc::clone(&modpacks_queue);
let err_clone = Arc::clone(&errors);
let handle = thread::spawn(move || {
loop {
let pack = {
let mut queue = queue_clone.lock().unwrap();
queue.next()
};
let pack = match pack {
Some(p) => p,
None => break,
};
let path = format!("modpacks/{}", pack);
println!("[Worker {}] starting: {}", i, pack);
if !std::path::Path::new(&path).exists() {
let mut e = err_clone.lock().unwrap();
e.push(format!("directory missing: {}", path));
fn main() -> Result<()> {
let mut jobs: Vec<PathBuf> = Vec::new();
for pack in PACKS {
let pack_dir = PathBuf::from("modpacks").join(pack);
if !pack_dir.exists() {
eprintln!("warning: pack directory missing: {}", pack_dir.display());
continue;
}
let refresh = Command::new("pw")
.args(["batch", "refresh", "-y"])
.current_dir(&path)
.status();
let entries = fs::read_dir(&pack_dir)
.with_context(|| format!("failed to read {}", pack_dir.display()))?;
let update = Command::new("pw")
.args(["batch", "update", "-a", "-y"])
.current_dir(&path)
.status();
let failed = match (refresh, update) {
(Ok(s1), Ok(s2)) => !s1.success() || !s2.success(),
(Err(e), _) => {
println!("[Worker {}] refresh failed for {}: {}", i, pack, e);
true
},
(_, Err(e)) => {
println!("[Worker {}] update failed for {}: {}", i, pack, e);
true
for entry in entries {
let entry = entry.context("failed to read directory entry")?;
let path = entry.path();
if !path.is_dir() {
continue;
}
let Some(name) = path.file_name().and_then(|s| s.to_str()) else {
continue;
};
if failed {
let mut e = err_clone.lock().unwrap();
e.push(format!("failed: {}", pack));
} else {
println!("[Worker {}] done: {}", i, pack);
if name.ends_with("-mr") || name.ends_with("-cf") {
jobs.push(path);
}
}
});
workers.push(handle);
}
for handle in workers {
handle.join().unwrap();
if jobs.is_empty() {
println!("no packs to update.");
return Ok(());
}
let final_errors = errors.lock().unwrap();
if !final_errors.is_empty() {
eprintln!("\nsummary of failures");
for err in final_errors.iter() {
eprintln!("{}", err);
println!(
"queued {} subdir(s) across {} pack(s), running up to {} in parallel",
jobs.len(),
PACKS.len(),
MAX_CONCURRENT,
);
let jobs = Arc::new(Mutex::new(jobs.into_iter()));
let failures: Arc<Mutex<Vec<(PathBuf, String)>>> = Arc::new(Mutex::new(Vec::new()));
let mut handles = Vec::new();
for worker_id in 0..MAX_CONCURRENT {
let jobs = Arc::clone(&jobs);
let failures = Arc::clone(&failures);
handles.push(thread::spawn(move || {
loop {
let job = { jobs.lock().unwrap().next() };
let Some(path) = job else { break };
let label = path.display().to_string();
println!("[W{worker_id}] updating {label}");
let output = Command::new("packwiz")
.args(["update", "-a", "-y"])
.current_dir(&path)
.output();
match output {
Ok(o) if o.status.success() => {
println!("[W{worker_id}] ok: {label}");
}
std::process::exit(1);
} else {
Ok(o) => {
let stderr = String::from_utf8_lossy(&o.stderr).into_owned();
let stdout = String::from_utf8_lossy(&o.stdout).into_owned();
eprintln!("[W{worker_id}] FAIL {label} (exit {})", o.status);
if !stdout.is_empty() {
eprintln!(" stdout:\n{}", indent(&stdout, " "));
}
if !stderr.is_empty() {
eprintln!(" stderr:\n{}", indent(&stderr, " "));
}
let reason = if !stderr.is_empty() { stderr } else { stdout };
failures.lock().unwrap().push((path, reason));
}
Err(e) => {
eprintln!("[W{worker_id}] FAIL {label}: could not launch packwiz: {e}");
failures.lock().unwrap().push((path, e.to_string()));
}
}
}
}));
}
for h in handles {
h.join().expect("worker thread panicked");
}
let failures = failures.lock().unwrap();
if failures.is_empty() {
println!("\nall updates finished successfully.");
Ok(())
} else {
eprintln!("\n{} subdir(s) failed:", failures.len());
for (path, _reason) in failures.iter() {
eprintln!(" - {}", path.display());
}
bail!("{} update(s) failed", failures.len())
}
}
fn indent(s: &str, prefix: &str) -> String {
s.lines()
.map(|l| format!("{prefix}{l}"))
.collect::<Vec<_>>()
.join("\n")
}