Merge driver-hold-22 d58ebab0 into release-0.3.23 (the unattended driver install through the Power Helper task; the driver-install-task right)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
igneum-labs 2026-10-07 19:15:45 +00:00
commit ecc8fb5443
5 changed files with 283 additions and 1 deletions

View file

@ -0,0 +1,229 @@
//! The unattended driver install (the rights-at-install step `driver-install-task`, 7 October 2026, 19:5x BST; the project lead's
//! rule that evening: no PC job may need a click or a UAC prompt, and the Arc's 9034 retry on PC 2 was cancelled for
//! it). What the installer registers once (src/rights.rs on boot-start-22 takes the id into RIGHTS): the Igneum Power
//! Helper task (RunLevel Highest, the app's own exe with `--power-helper`) with a two-hour execution limit, so the
//! elevated helper can run a vendor's driver installer to its end. Nothing else: no second task, no new firewall rule.
//!
//! The protocol, on the helper's one command file: `<seq> driver <vendor>` with a vendor WORD only (nvidia | amd |
//! intel). The helper, elevated, resolves everything else itself from the signed table the manifest left at
//! `<app data>/drivers.json` and the file the app downloaded into `<app data>/drivers/`: the size, the sha256 and the
//! Authenticode signer must match the table and the signer must be one of the three vendors, or nothing runs. So a
//! writer of cmd.txt can never choose what runs elevated (the Power Helper's rule since 6 October 2026). The helper
//! runs the installer with the table's silent arguments, keeps its heartbeat during the run (cap 45 minutes), and
//! writes `<seq> <vendor> exit <code> reboot <0|1>` to `driver-result.txt` in its folder. The app holds the vendor's
//! cards before it asks (engine::driver_install), reads the result, and a "restart required" exit is the Restart now
//! button: the app never restarts the machine by itself. When the task is not registered (an install before 0.3.22
//! whose rights step has not run), the one-prompt path of src/drivers.rs stays as it was.
use std::path::{Path, PathBuf};
use std::time::{Duration, Instant};
/// The right's id and sentence for src/rights.rs RIGHTS (an id never changes meaning; a new need is a new id).
pub const RIGHT: (&str, &str) = ("driver-install-task", "the Igneum Power Helper task with a two-hour run limit: a vendor's driver installs unattended through it, the vendor's cards held, no prompt (src/driverinstall.rs)");
pub const RESULT_FILE: &str = "driver-result.txt";
pub const VENDORS: &[&str] = &["nvidia", "amd", "intel"];
/// The Authenticode subjects a driver installer may carry to run elevated (the table's `signer` must match one too).
pub const ALLOWED_SIGNERS: &[&str] = &["Intel Corporation", "NVIDIA Corporation", "Advanced Micro Devices"];
/// How long the helper waits for the installer, and how long the app waits for the helper's result line.
pub const INSTALL_CAP: Duration = Duration::from_secs(45 * 60);
pub const RESULT_WAIT: Duration = Duration::from_secs(50 * 60);
/// The installer's registration for this right: the Power Helper task as src/powertask.rs registers it, with the
/// execution limit raised from one hour to two (a 1 GB download that the app already did is not in it; the installer
/// itself runs 2 to 15 minutes, and the helper's own idle exit is 20 minutes).
pub fn register_script(exe: &Path) -> String {
crate::powertask::register_script(exe).replace("-ExecutionTimeLimit (New-TimeSpan -Hours 1)", "-ExecutionTimeLimit (New-TimeSpan -Hours 2)")
}
pub fn vendor_ok(v: &str) -> bool {
VENDORS.contains(&v)
}
/// The command line the app writes for the helper (the sequence from the one wire space).
pub fn command_line(seq: u64, vendor: &str) -> String {
format!("{seq} driver {vendor}\n")
}
/// The file the helper runs for a vendor: the table's URL's last path segment inside the app's drivers folder.
pub fn file_for(e: &crate::drivertable::VendorEntry, drivers_dir: &Path) -> PathBuf {
let name = e.url.rsplit('/').next().filter(|n| !n.is_empty() && !n.contains("..") && !n.contains('\\')).unwrap_or("driver.exe");
drivers_dir.join(name)
}
/// The elevated check before anything runs: size and sha256 equal to the table, the signer one of the vendors and the
/// table's own. Pure, so the box tests it.
pub fn verify(e: &crate::drivertable::VendorEntry, size: u64, sha256: &str, signer_subject: &str) -> Result<(), String> {
if size != e.size {
return Err(format!("size {size} is not the table's {}", e.size));
}
if !sha256.eq_ignore_ascii_case(&e.sha256) {
return Err("sha256 is not the table's: the file is not the one the manifest names".into());
}
if !ALLOWED_SIGNERS.iter().any(|s| signer_subject.contains(s)) {
return Err(format!("the signer '{signer_subject}' is not a driver vendor"));
}
if !e.signer.is_empty() && !signer_subject.contains(&e.signer) {
return Err(format!("the signer '{signer_subject}' is not the table's '{}'", e.signer));
}
Ok(())
}
/// The helper's result line and its reading.
pub fn result_line(seq: u64, vendor: &str, code: i64, reboot: bool) -> String {
format!("{seq} {vendor} exit {code} reboot {}\n", if reboot { 1 } else { 0 })
}
pub fn parse_result(text: &str, seq: u64) -> Option<(i64, bool)> {
text.lines().rev().find_map(|l| {
let p: Vec<&str> = l.split_whitespace().collect();
match p.as_slice() {
[s, _, "exit", c, "reboot", r] if s.parse::<u64>().ok() == Some(seq) => Some((c.parse().ok()?, *r == "1")),
_ => None,
}
})
}
/// Inside the elevated helper: resolve, verify, run, report. `dir` is the helper's folder (<app data>/app/sweep).
pub fn run_in_helper(dir: &Path, seq: u64, vendor: &str, log: &dyn Fn(&str)) {
let app_dir = dir.parent().map(|p| p.to_path_buf()).unwrap_or_else(|| dir.to_path_buf());
let outcome = run_in_helper_inner(&app_dir, dir, vendor, log);
let (code, reboot) = match outcome {
Ok(v) => v,
Err(e) => {
log(&format!("{seq} driver {vendor}: refused: {e}"));
(-2, false)
}
};
let _ = std::fs::OpenOptions::new().append(true).create(true).open(dir.join(RESULT_FILE)).and_then(|mut f| {
use std::io::Write;
f.write_all(result_line(seq, vendor, code, reboot).as_bytes())
});
log(&format!("{seq} driver {vendor}: exit {code} reboot {reboot}"));
}
fn run_in_helper_inner(app_dir: &Path, helper_dir: &Path, vendor: &str, log: &dyn Fn(&str)) -> Result<(i64, bool), String> {
if !vendor_ok(vendor) {
return Err(format!("'{vendor}' is not a vendor word"));
}
let text = std::fs::read_to_string(app_dir.join("drivers.json")).map_err(|e| format!("no driver table at {}: {e}", app_dir.join("drivers.json").display()))?;
let v: serde_json::Value = serde_json::from_str(&text).map_err(|e| format!("the driver table does not parse: {e}"))?;
let table = crate::drivertable::Table::parse(&v)?;
let e = table.entry(vendor).ok_or_else(|| format!("no {vendor} row in the table"))?.clone();
let file = file_for(&e, &app_dir.join("drivers"));
let size = std::fs::metadata(&file).map(|m| m.len()).map_err(|x| format!("no downloaded installer at {}: {x}", file.display()))?;
let sha = crate::manifest::sha256_file(&file).map_err(|x| x.to_string())?;
let subject = signer_subject(&file)?;
verify(&e, size, &sha, &subject)?;
log(&format!("driver {vendor}: {} verified (size, sha256, signer '{subject}'); running {} {}", file.display(), file.display(), e.args.join(" ")));
let mut c = std::process::Command::new(&file);
c.args(&e.args);
crate::platform::quiet(&mut c);
let mut child = c.spawn().map_err(|x| format!("the installer did not start: {x}"))?;
let started = Instant::now();
loop {
crate::powertask::beat(helper_dir, crate::platform::unix_now());
match child.try_wait() {
Ok(Some(st)) => {
let code = st.code().map(|c| c as i64).unwrap_or(-1);
let (reboot, _) = crate::drivertable::exit_meaning(code, &e);
return Ok((code, reboot));
}
Ok(None) => {
if started.elapsed() > INSTALL_CAP {
let _ = child.kill();
return Err(format!("the installer ran past {} minutes and was ended", INSTALL_CAP.as_secs() / 60));
}
std::thread::sleep(Duration::from_secs(2));
}
Err(x) => return Err(format!("waiting on the installer: {x}")),
}
}
}
#[cfg(windows)]
fn signer_subject(path: &Path) -> Result<String, String> {
let ps = format!("$s = Get-AuthenticodeSignature -LiteralPath '{}'; Write-Output ('status=' + $s.Status); Write-Output ('subject=' + $s.SignerCertificate.Subject)", path.display().to_string().replace('\'', "''"));
let mut c = std::process::Command::new(crate::platform::tool("powershell"));
c.args(["-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", &ps]);
let out = crate::detect::run_timeout(&mut c, None, Duration::from_secs(60)).ok_or("PowerShell did not answer the signature check")?;
let mut status = String::new();
let mut subject = String::new();
for l in out.lines() {
if let Some(v) = l.strip_prefix("status=") { status = v.trim().to_string(); } else if let Some(v) = l.strip_prefix("subject=") { subject = v.trim().to_string(); }
}
if status != "Valid" {
return Err(format!("the Authenticode signature is {status}, not Valid"));
}
Ok(subject)
}
#[cfg(not(windows))]
fn signer_subject(_path: &Path) -> Result<String, String> {
Err("the installer runs on Windows only".into())
}
/// The app's side: when the Power Helper task is registered, ask it and wait for the result line; None when it is not
/// (the caller falls back to the one-prompt path). `file` is the verified download.
pub fn via_helper(vendor: &str) -> Option<Result<(i64, bool), String>> {
if !cfg!(windows) || !crate::powertask::registered() {
return None;
}
let dir = crate::powertask::helper_dir();
Some((|| {
crate::powertask::ensure_running(&dir, Duration::from_secs(30))?;
let seq = crate::powertask::wire_seq();
let mut text = std::fs::read_to_string(dir.join("cmd.txt")).unwrap_or_default();
text.push_str(&command_line(seq, vendor));
std::fs::write(dir.join("cmd.txt"), text).map_err(|e| e.to_string())?;
let started = Instant::now();
loop {
let res = std::fs::read_to_string(dir.join(RESULT_FILE)).unwrap_or_default();
if let Some(r) = parse_result(&res, seq) {
return Ok(r);
}
if started.elapsed() > RESULT_WAIT {
return Err(format!("the Power Helper gave no result for the {vendor} install within {} minutes", RESULT_WAIT.as_secs() / 60));
}
if !crate::powertask::alive(&dir) && started.elapsed() > Duration::from_secs(120) {
return Err("the Power Helper stopped during the install (no heartbeat)".into());
}
std::thread::sleep(Duration::from_secs(3));
}
})())
}
#[cfg(test)]
mod tests {
use super::*;
fn intel() -> crate::drivertable::VendorEntry {
let v: serde_json::Value = serde_json::from_str(include_str!("../../../packaging/ota/drivers.json")).unwrap();
crate::drivertable::Table::parse(&v).unwrap().entry("intel").unwrap().clone()
}
/// Known-failed first: an installer that is not the table's file, or not signed by a driver vendor, must never run
/// elevated; the first cut of the unattended path had no such check.
#[test]
fn the_helper_refuses_a_file_that_is_not_the_tables_or_not_the_vendors() {
let e = intel();
assert!(verify(&e, e.size, &e.sha256, "CN=Intel Corporation, O=Intel Corporation, S=California, C=US").is_ok());
assert!(verify(&e, e.size + 1, &e.sha256, "CN=Intel Corporation").unwrap_err().starts_with("size"));
assert!(verify(&e, e.size, "00", "CN=Intel Corporation").unwrap_err().starts_with("sha256"));
assert!(verify(&e, e.size, &e.sha256, "CN=Some Miner Tools Ltd").unwrap_err().contains("not a driver vendor"));
assert!(verify(&e, e.size, &e.sha256, "CN=NVIDIA Corporation").unwrap_err().contains("not the table's"), "a vendor, but not this row's");
assert_eq!(file_for(&e, Path::new("D")).file_name().unwrap().to_str().unwrap(), "gfx_win_101.9034.exe");
}
#[test]
fn the_right_is_the_power_helper_task_with_a_two_hour_limit_and_the_protocol_round_trips() {
assert_eq!(RIGHT.0, "driver-install-task", "the id src/rights.rs's test already anticipates");
let s = register_script(Path::new("C:\\p\\Igneum Miner\\igneum-app.exe"));
assert!(s.contains("-ExecutionTimeLimit (New-TimeSpan -Hours 2)") && !s.contains("-Hours 1"), "{s}");
assert!(s.contains("-TaskName 'Igneum Power Helper'") && s.contains("--power-helper"), "the same task, the same action");
assert_eq!(command_line(305327, "intel"), "305327 driver intel\n");
let r = result_line(305327, "intel", 14, true);
assert_eq!(parse_result(&r, 305327), Some((14, true)));
assert_eq!(parse_result(&r, 305328), None, "another sequence's result is not this one");
assert_eq!(parse_result("305327 intel exit -2 reboot 0\n", 305327), Some((-2, false)), "a refusal reads as exit -2");
assert!(vendor_ok("amd") && !vendor_ok("apple") && !vendor_ok("C:\\x.exe"));
}
}

View file

@ -114,11 +114,21 @@ pub fn start_install(shared: &Arc<Shared>, e: VendorEntry, dir: PathBuf, dry_run
return;
}
}
shared2.send(Cmd::Driver(Event::Progress(0.7, format!("{}installing {} {}: Windows asks for permission once", if dry_run { "dry run: " } else { "" }, e.vendor.to_ascii_uppercase(), e.version))));
let unattended = !dry_run && cfg!(windows) && crate::powertask::registered();
shared2.send(Cmd::Driver(Event::Progress(0.7, format!("{}installing {} {}: {}", if dry_run { "dry run: " } else { "" }, e.vendor.to_ascii_uppercase(), e.version, if unattended { "through the Igneum Power Helper task, no prompt" } else { "Windows asks for permission once" }))));
if dry_run {
shared2.send(Cmd::Driver(Event::Installed(Ok((0, false, format!("dry run: would run {} {}", file.display(), e.args.join(" ")))))));
return;
}
// 0.3.22 (src/driverinstall.rs, the rights step driver-install-task): with the Power Helper task registered the
// elevated helper runs the installer unattended after its own verification of the file; no prompt
if unattended {
if let Some(r) = crate::driverinstall::via_helper(&e.vendor) {
let r = r.map(|(code, _)| { let (reboot, text) = exit_meaning(code, &e); (code, reboot, text) });
shared2.send(Cmd::Driver(Event::Installed(r)));
return;
}
}
let args = e.args.join(" ");
let mut c = crate::platform::elevated_command(&file.display().to_string(), &args);
let out = crate::detect::run_timeout(&mut c, None, Duration::from_secs(1800));

View file

@ -49,6 +49,7 @@ mod chainfacts;
mod card;
mod drivertable;
mod drivers;
mod driverinstall;
mod bootcheck;
mod boot;
mod rights;

View file

@ -91,6 +91,9 @@ pub enum HelperCmd {
/// re-point the task at the installed exe (no path argument: the helper finds the install folder itself, so a
/// writer of cmd.txt can never choose what runs elevated); 6 October 2026, run 6 registered a scratch copy
Reregister,
/// the unattended driver install (src/driverinstall.rs): a vendor WORD only; the helper resolves the file, hash,
/// signer and arguments from the signed table itself
Driver(String),
}
/// Parses one line: `<seq> <verb> [<digits>]` (the 0.3.9 form `<seq> <watts>` reads as a power limit; `quit` and
@ -115,6 +118,7 @@ pub fn parse_line(line: &str) -> Option<(u64, HelperCmd)> {
[_, "rmc"] => Some((seq, HelperCmd::MemReset)),
[_, "dev", d] if digits(d) => Some((seq, HelperCmd::Dev(d.to_string()))),
[_, "reregister"] => Some((seq, HelperCmd::Reregister)),
[_, "driver", v] if crate::driverinstall::vendor_ok(v) => Some((seq, HelperCmd::Driver(v.to_string()))),
_ => None,
}
}
@ -323,6 +327,11 @@ pub fn run_helper(dir: &Path) -> i32 {
let now = q.output().map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string()).unwrap_or_default();
log(&format!("{seq} reregister {}: the task now runs {now}", if ok { "ok" } else { "failed" }));
}
HelperCmd::Driver(v) => {
last_seq = seq;
crate::driverinstall::run_in_helper(dir, seq, &v, &log);
idle = Instant::now();
}
HelperCmd::Dev(d) => {
last_seq = seq;
idle = Instant::now();
@ -367,6 +376,20 @@ pub fn helper_dir() -> PathBuf {
mod tests {
use super::*;
/// Known-failed first (7 October 2026, 19:5x BST): the helper knew no driver verb, so a driver install needed an
/// elevated prompt. The verb carries a vendor WORD only: the helper resolves the file, the hash, the signer and the
/// arguments from the signed table itself, so a writer of cmd.txt can never choose what runs elevated.
#[test]
fn the_helper_takes_a_driver_verb_for_a_vendor_word_and_nothing_else() {
assert_eq!(parse_line("305327 driver intel"), Some((305327, HelperCmd::Driver("intel".into()))));
assert_eq!(parse_line("305328 driver nvidia"), Some((305328, HelperCmd::Driver("nvidia".into()))));
assert_eq!(parse_line("305329 driver amd"), Some((305329, HelperCmd::Driver("amd".into()))));
assert_eq!(parse_line("305330 driver C:\\evil.exe"), None, "a path is not a vendor word");
assert_eq!(parse_line("305331 driver apple"), None, "no installer for that vendor");
assert_eq!(parse_line("driver intel"), None, "a sequence number is required");
assert_eq!(smi_args("0", &HelperCmd::Driver("intel".into())), None, "a driver verb is never an nvidia-smi call");
}
#[test]
fn only_fixed_verbs_with_digit_arguments_parse() {
assert_eq!(parse_line("7 pl 460"), Some((7, HelperCmd::PowerLimit(460))));

View file

@ -55,6 +55,25 @@ Per tier: a miner loses the installing vendor's rate for the install (PC 2: the
the project lead's rule tonight: no PC job may need a click or a UAC prompt. The one-click install asks for one, so the 9034 retry on PC 2 is cancelled, 6733 stays (the worker mines at 11.0 MH/s on it with the Intel rotate rewrite), and the table's Intel row carries `min_version` 32.0.101.6733: an Arc on Windows' inbox driver reads "fine" with no button; a card with no driver at all is still offered 9034 (test `the_shipped_table_accepts_the_inbox_6733_driver_for_the_arc_b580_until_an_unattended_install_exists`). What follows: the install path moves off the elevated prompt onto the app's Power Helper task (the registered elevated task the tuner already uses), so a driver installs unattended at the next idle moment with the vendor's cards held; until then the one-click path stays as it is for a user at the keyboard. The 16:27Z minidump waits for the same unattended path (the Minidump folder refuses an unelevated read), not for a click.
## 3d. The rights-at-install step `driver-install-task` (7 October 2026, 19:5x BST; for boot-start-22's rights.rs)
Main's ask: the update-return lane's rights step (boot-start-22 3fbf4280, `src/rights.rs`, the manifest `rights.json`) takes a named right for the driver installer, so the next update asks once and a driver installs with no click ever again. The step, on this branch as `app/igneum-app/src/driverinstall.rs`:
| Part | What |
|---|---|
| Id and sentence | `driverinstall::RIGHT` = `("driver-install-task", "the Igneum Power Helper task with a two-hour run limit: a vendor's driver installs unattended through it, the vendor's cards held, no prompt")`; add it to `rights::RIGHTS` (the rights test already anticipates the id) |
| What the installer registers once | `driverinstall::register_script(exe)`: the Igneum Power Helper task as `powertask::register_script` registers it (RunLevel Highest, the app's own exe with `--power-helper`, no trigger, the signed-in user), with `-ExecutionTimeLimit` two hours instead of one. No second task, no new firewall rule. In the rights script it replaces the Power Helper line (same task name, `-Force`) |
| The elevated path | the Power Helper's one command file `<app data>/app/sweep/cmd.txt` gains `<seq> driver <vendor>` with a vendor WORD only (nvidia, amd, intel). The elevated helper resolves the file (`<app data>/drivers/<the table URL's file name>`), the size, the sha256 and the Authenticode signer from the signed table at `<app data>/drivers.json`; the signer must be Intel Corporation, NVIDIA Corporation or Advanced Micro Devices and the row's own; anything else is refused with exit -2 in the result line. So a writer of cmd.txt can never choose what runs elevated |
| The vendor installers' silent flags | the table rows: Intel `-s` (no `-b`; 14 and 1014 mean restart required), NVIDIA `-s -noreboot`, AMD `-install -silent`; the helper passes the row's `args` as they are |
| Restart handling | the helper reports `<seq> <vendor> exit <code> reboot <0|1>` in `driver-result.txt` (`exit_meaning` on the row's `reboot_codes`); the app shows "installed, Restart Windows to finish" with the Restart now button; the app never restarts the machine by itself |
| Cards | `engine::driver_install` holds every card of the vendor before the ask (3b) and gives them back on the result or on the card's return |
| Caps | the installer 45 minutes inside the helper (heartbeat kept), the app waits 50 for the result line; a helper that dies mid-install is a plain error on the row |
| Fallback | no task registered (an install before 0.3.22 whose rights step never ran): the one-prompt path of `drivers::start_install` as before, and the row's text says which path it took |
Taken (the rights lane, 20:1x BST): `driver-install-task` is in `rights::RIGHTS` on boot-start-22 332b82eb as a literal tuple; at the merge `rights::script()`'s Power Helper line becomes `driverinstall::register_script(exe)`. The rights step asks only in an interactive session that is not a job's (SESSIONNAME set, no `IGNEUM_JOB_*`), so the unattended install never meets a prompt once a person has installed once; a manifest from a build before this right lacks exactly it and asks once.
Tests (box, known-failed first): the helper took no `driver` verb (`the_helper_takes_a_driver_verb_for_a_vendor_word_and_nothing_else`, powertask.rs: a path and an unknown vendor are refused), `the_helper_refuses_a_file_that_is_not_the_tables_or_not_the_vendors`, `the_right_is_the_power_helper_task_with_a_two_hour_limit_and_the_protocol_round_trips`. Not run on a PC tonight (the project lead's rule); the first unattended install is PC 2's Arc 9034 once 0.3.22's rights step has run there.
## 4. Not done, and what follows
- The table's publish: `publish-manifest.sh --drivers packaging/ota/drivers.json` with the 0.3.21 manifest (the shipper's step); every row is measured.