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 [user]. 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 <noreply@anthropic.com>
This commit is contained in:
parent
89dd615fef
commit
c5c2a5a069
2 changed files with 89 additions and 19 deletions
|
|
@ -269,20 +269,34 @@ impl Jobs {
|
|||
let now = crate::platform::unix_now();
|
||||
let platform = manifest::platform_name();
|
||||
let probes: Mutex<std::collections::HashMap<String, bool>> = 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<jobs::JobsFile, String> {
|
|||
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::<Vec<_>>().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<String, String> {
|
||||
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()),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 [user]]
|
||||
# common: --target <id8 or id16, comma list, or all> [--platform windows|mac|any] [--requires wsl-prover,nvidia]
|
||||
# common: --target <id8 or id16, comma list, or all> [--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 <id> [--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])
|
||||
|
|
|
|||
Loading…
Reference in a new issue