From c67b2a4cfba29bf235c87b25ff9d66cf5b040bc0 Mon Sep 17 00:00:00 2001 From: igneum-josh <337424239+igneum-josh@users.noreply.github.com> Date: Sun, 4 Oct 2026 17:54:02 +0100 Subject: [PATCH] Igneum Miner jobs: the wsl-prover probe logs why it fails, tries the configured then the default WSL user with cargo and sp1 on PATH, and accepts the shipped wsl2\bin prover; publish-jobs --requires none|"" = no requirement PC 2 on 0.3.3 logged 'needs wsl-prover' although the toolchain is there as user josh. Every negative probe now lands in the engine log with its exit code and the first 200 characters of output (it reaches the intake). The probe sources ~/.cargo/env and sets ~/.cargo/bin and ~/.sp1/bin itself (a login shell from a console-less process need not), runs as the job's wsl_user or IGNEUM_APP_WSL_USER first and the distro default user second, and a payload with wsl2\bin\igneum-prove-host next to the app meets the requirement when the distro answers. publish-jobs.sh: --requires none or "" publishes an empty list (none was a literal requirement, "" fell back to the kind's default). Co-Authored-By: Claude Fable 5.1 --- app/igneum-app/src/jobrun.rs | 99 +++++++++++++++++++++++++++++------ packaging/ota/publish-jobs.sh | 9 ++-- 2 files changed, 89 insertions(+), 19 deletions(-) diff --git a/app/igneum-app/src/jobrun.rs b/app/igneum-app/src/jobrun.rs index d4417fbf8..09bea99e3 100644 --- a/app/igneum-app/src/jobrun.rs +++ b/app/igneum-app/src/jobrun.rs @@ -269,20 +269,34 @@ impl Jobs { let now = crate::platform::unix_now(); let platform = manifest::platform_name(); let probes: Mutex> = Mutex::new(std::collections::HashMap::new()); - let have = |req: &str| -> bool { - if let Some(v) = probes.lock().unwrap().get(req) { - return *v; - } - let v = probe(req); - probes.lock().unwrap().insert(req.to_string(), v); - v - }; + let env_user = std::env::var("IGNEUM_APP_WSL_USER").unwrap_or_default(); let mut runnable = Vec::new(); let mut pending = Vec::new(); for j in &f.jobs { if seen.contains(&j.id) { continue; } + // the probe runs once per requirement and WSL user per poll; every negative result is logged + // with its exit code and output (PC 2, 0.3.3: "needs wsl-prover" on a machine that had it) + let user = { let u = j.str_param("wsl_user"); if u.is_empty() { env_user.clone() } else { u } }; + let have = |req: &str| -> bool { + let key = format!("{req}|{user}"); + if let Some(v) = probes.lock().unwrap().get(&key) { + return *v; + } + let v = match probe(req, &user) { + Ok(how) => { + shared2.log(&format!("probe {req}: ok ({how})")); + true + } + Err(e) => { + shared2.log(&format!("probe {req}{}: NOT met: {e}", if user.is_empty() { String::new() } else { format!(" (user {user})") })); + false + } + }; + probes.lock().unwrap().insert(key, v); + v + }; match jobs::eligibility(j, &machine_id, platform, now, &have) { Eligibility::Run => runnable.push(j.clone()), Eligibility::Needs(m) => pending.push((j.id.clone(), m)), @@ -573,15 +587,70 @@ fn fetch_jobs(url: &str, dir: &Path) -> Result { Ok(f) } -/// Does this machine meet a requirement? Probed on the fetch thread, once per poll. -fn probe(req: &str) -> bool { +/// What the toolchain probe runs inside the distro: the cargo and sp1 paths set by hand (a login shell from a +/// process without a console need not source ~/.cargo/env), then the three things the prover needs. +const PROVER_PROBE: &str = "export PATH=\"$HOME/.cargo/bin:$HOME/.sp1/bin:$PATH\"; [ -f \"$HOME/.cargo/env\" ] && . \"$HOME/.cargo/env\"; command -v cargo && ls -d \"$HOME/.sp1\" \"$HOME/igneum-prove\" && echo \"user $(id -un) home $HOME\""; + +fn short_out(s: &str) -> String { + let t: String = s.replace(['\r', '\n'], " ").split_whitespace().collect::>().join(" "); + t.chars().take(200).collect() +} + +/// Does this machine meet a requirement? Probed on the fetch thread, once per poll and WSL user. Ok carries how it +/// was met, Err the exit code and the first 200 characters of the output, for the engine log. +fn probe(req: &str, wsl_user: &str) -> Result { + let wsl = crate::platform::tool("wsl"); match req { - "nvidia" => run_capture(Command::new(crate::platform::tool("nvidia-smi")).arg("-L"), Duration::from_secs(20)).0 == Some(0), - "wsl" => cfg!(windows) && run_capture(Command::new(crate::platform::tool("wsl")).arg("--status"), Duration::from_secs(30)).0 == Some(0), - "wsl-prover" => { - cfg!(windows) && run_capture(Command::new(crate::platform::tool("wsl")).args(["-d", DEFAULT_DISTRO, "--", "bash", "-lc", "command -v cargo && ls ~/.sp1 && ls ~/igneum-prove"]), Duration::from_secs(90)).0 == Some(0) + "nvidia" => { + let (code, out) = run_capture(Command::new(crate::platform::tool("nvidia-smi")).arg("-L"), Duration::from_secs(20)); + if code == Some(0) { Ok(short_out(&out)) } else { Err(format!("nvidia-smi -L exit {code:?}: {}", short_out(&out))) } } - _ => false, + "wsl" => { + if !cfg!(windows) { + return Err("not Windows".into()); + } + let (code, out) = run_capture(Command::new(&wsl).arg("--status"), Duration::from_secs(30)); + if code == Some(0) { Ok("wsl --status".into()) } else { Err(format!("wsl --status exit {code:?}: {}", short_out(&out))) } + } + "wsl-prover" => { + if !cfg!(windows) { + return Err("not Windows".into()); + } + // the configured user first (the job's wsl_user or IGNEUM_APP_WSL_USER), then the distro's default user + let mut tried = Vec::new(); + let mut users: Vec<&str> = Vec::new(); + if !wsl_user.is_empty() { + users.push(wsl_user); + } + users.push(""); + for u in users { + let mut c = Command::new(&wsl); + c.args(["-d", DEFAULT_DISTRO]); + if !u.is_empty() { + c.args(["-u", u]); + } + c.args(["--", "bash", "-lc", PROVER_PROBE]); + let (code, out) = run_capture(&mut c, Duration::from_secs(90)); + if code == Some(0) { + return Ok(format!("toolchain in {DEFAULT_DISTRO}{}: {}", if u.is_empty() { " (default user)".to_string() } else { format!(" as {u}") }, short_out(&out))); + } + tried.push(format!("{}: exit {code:?}: {}", if u.is_empty() { "default user".to_string() } else { format!("user {u}") }, short_out(&out))); + } + // the payload ships a Linux prover next to the app (wsl2\bin\igneum-prove-host): that meets it too, + // provided WSL answers at all + if let Some(dir) = std::env::current_exe().ok().and_then(|p| p.parent().map(|d| d.to_path_buf())) { + let shipped = dir.join("wsl2").join("bin").join("igneum-prove-host"); + if shipped.is_file() { + let (code, out) = run_capture(Command::new(&wsl).args(["-d", DEFAULT_DISTRO, "--", "true"]), Duration::from_secs(60)); + if code == Some(0) { + return Ok(format!("shipped prover {} and {DEFAULT_DISTRO} answers", shipped.display())); + } + tried.push(format!("shipped prover present but {DEFAULT_DISTRO} does not answer: exit {code:?}: {}", short_out(&out))); + } + } + Err(tried.join(" | ")) + } + _ => Err("unknown requirement".into()), } } diff --git a/packaging/ota/publish-jobs.sh b/packaging/ota/publish-jobs.sh index e02efca0d..498702230 100755 --- a/packaging/ota/publish-jobs.sh +++ b/packaging/ota/publish-jobs.sh @@ -15,7 +15,8 @@ # packaging/ota/publish-jobs.sh add --kind update-now --target all # packaging/ota/publish-jobs.sh add --kind shard-benchmark --target 1ccfe586 [--zip ~/Desktop/igneum-prove-wsl2.zip] \ # [--fixtures "block-338-shard1 block-341-shards2 block-344-shards4"] [--cap-minutes 90] [--distro Ubuntu-24.04] [--wsl-user josh] -# common: --target [--platform windows|mac|any] [--requires wsl-prover,nvidia] +# common: --target [--platform windows|mac|any] [--requires wsl-prover,nvidia | none] +# (shard-benchmark defaults to --requires wsl-prover; --requires none or "" publishes with no requirement) # [--id custom-id] [--title "..."] [--expires-hours 48] [--deploy] # packaging/ota/publish-jobs.sh list what is published (expired jobs marked) # packaging/ota/publish-jobs.sh remove [--deploy] @@ -35,7 +36,7 @@ TOKEN_FILE="$HOME/.config/igneum/dl-token" SIGNER="$ROOT/app/igneum-app/target/release/igneum-ota-sign" CMD="${1:-}"; [ $# -gt 0 ] && shift -KIND="" TARGET="" PLATFORM="" REQUIRES="" ID="" TITLE="" EXPIRES_H="48" DEPLOY=0 BASE="" DEST="" +KIND="" TARGET="" PLATFORM="" REQUIRES="" REQUIRES_SET=0 ID="" TITLE="" EXPIRES_H="48" DEPLOY=0 BASE="" DEST="" SCRIPT="" SHELL_KIND="" ELEVATED=0 STOP_MINERS=0 TIMEOUT_MIN="" FILE="" URL="" SHA="" SIZE="" DIR="" TO="" EXTRACT=0 EXTRACT_DIR="" FRESH=0 GLOBS=() COMMAND="" WHAT="" @@ -48,7 +49,7 @@ while [ $# -gt 0 ]; do --kind) KIND="$2"; shift 2 ;; --target) TARGET="$2"; shift 2 ;; --platform) PLATFORM="$2"; shift 2 ;; - --requires) REQUIRES="$2"; shift 2 ;; + --requires) REQUIRES="$2"; REQUIRES_SET=1; [ "$REQUIRES" = none ] && REQUIRES=""; shift 2 ;; --id) ID="$2"; shift 2 ;; --title) TITLE="$2"; shift 2 ;; --expires-hours) EXPIRES_H="$2"; shift 2 ;; @@ -183,7 +184,7 @@ print(json.dumps(d))' "$COMMAND" "${GLOBS[@]:-}")" [ -n "$ZIP" ] || ZIP="$HOME/Desktop/igneum-prove-wsl2.zip" read -r ZURL ZSHA ZSIZE < <(hosted_file "$ZIP") [ -n "$PLATFORM" ] || PLATFORM=windows - [ -n "$REQUIRES" ] || REQUIRES=wsl-prover + [ "$REQUIRES_SET" = 1 ] || REQUIRES=wsl-prover PARAMS="$(python3 -c 'import json,sys; a=sys.argv; d={"zip_url": a[1], "sha256": a[2], "size": int(a[3])} if a[4]: d["fixtures"]=a[4].split() if a[5]: d["cap_minutes"]=int(a[5])