horto_os_ui_shared/ops/
runner.rs1use crate::context::{require_root_for_apply, ApplyMode, HostContext};
4use crate::error::{HortoError, Result};
5use crate::pipeline::{self, SetupKind};
6use crate::resume;
7use crate::step::Step;
8
9pub fn setup_run(ctx: &mut HostContext, kind: SetupKind) -> Result<()> {
15 require_root_for_apply(ctx.mode)?;
16 ctx.setup_kind = kind;
17 for step in pipeline::pipeline(kind) {
18 run_one(ctx, *step)?;
19 }
20 Ok(())
21}
22
23pub fn setup_step(ctx: &mut HostContext, kind: SetupKind, id: &str) -> Result<()> {
30 require_root_for_apply(ctx.mode)?;
31 ctx.setup_kind = kind;
32 let step = pipeline::lookup(id).ok_or_else(|| HortoError::UnknownStep(id.into()))?;
33 let in_pipe = pipeline::pipeline(kind).iter().any(|s| s.id() == id);
35 if !in_pipe {
36 return Err(HortoError::NotInPipeline {
37 step: id.into(),
38 kind: kind.as_str().into(),
39 });
40 }
41 check_deps(ctx, step)?;
42 run_one(ctx, step)
43}
44
45fn check_deps(ctx: &HostContext, step: &dyn Step) -> Result<()> {
46 let state = resume::load(&ctx.paths.resume_file()).unwrap_or_default();
47 let mut missing = Vec::new();
48 for dep in step.depends_on() {
49 let Some(dep_step) = pipeline::lookup(dep) else {
50 continue;
51 };
52 let st = resume::status_for(&state, dep_step);
53 let done = matches!(st, resume::StepStatus::Done) || dep_step.is_done(ctx);
54 if !done {
55 missing.push(*dep);
56 }
57 }
58 if missing.is_empty() {
59 Ok(())
60 } else {
61 Err(HortoError::MissingDependency(
62 step.id().into(),
63 missing.join(", "),
64 ))
65 }
66}
67
68fn run_one(ctx: &mut HostContext, step: &dyn Step) -> Result<()> {
69 ctx.log(format!("==> {} ({})", step.title(), step.id()));
70 ctx.planned.clear();
71 if ctx.mode == ApplyMode::DryRun {
72 step.plan(ctx)?;
73 step.apply(ctx)?;
75 return Ok(());
76 }
77 match step.apply(ctx) {
78 Ok(()) => {
79 resume::record_ok(&ctx.paths, step)?;
80 if step.needs_reboot_after() {
81 ctx.log("Note: a reboot is recommended after this step.");
82 }
83 Ok(())
84 }
85 Err(e) => {
86 tracing::error!(step = step.id(), error = %e, "step failed");
87 let _ = resume::mark_failed(&ctx.paths, step.id(), step.step_version());
88 Err(e)
89 }
90 }
91}