horto_os_ui_mcp/
embedded_ops.rs1use anyhow::{bail, Result};
4use horto_os_ui_shared::{
5 backup_status, docker_rebuild, doctor, list_containers, list_timestamped_etc_backups,
6 setup_run, setup_status as engine_setup_status, setup_step, ApplyMode, HostContext,
7 NonInteractivePrompts, SetupKind,
8};
9
10const CONFIRM_DOCKER_REBUILD: &str = "docker-rebuild";
11
12fn ctx(apply: bool) -> HostContext {
13 let mode = if apply {
14 ApplyMode::Apply
15 } else {
16 ApplyMode::DryRun
17 };
18 HostContext::new(mode, SetupKind::Full).with_prompts(Box::new(NonInteractivePrompts))
19}
20
21const fn kind(full: bool) -> SetupKind {
22 if full {
23 SetupKind::Full
24 } else {
25 SetupKind::Minimal
26 }
27}
28
29fn capture_logs(host: &HostContext) -> String {
30 let mut out = host.logs.join("\n");
31 if !host.planned.is_empty() {
32 if !out.is_empty() {
33 out.push('\n');
34 }
35 for p in &host.planned {
36 out.push_str("[plan] ");
37 out.push_str(&p.summary);
38 out.push('\n');
39 }
40 }
41 if out.trim().is_empty() {
42 out = "ok".into();
43 }
44 out
45}
46
47pub fn setup_status_report(apply: bool, full: bool) -> Result<String> {
53 let c = ctx(apply);
54 let report = engine_setup_status(&c, kind(full));
55 Ok(serde_json::to_string_pretty(&report)?)
56}
57
58pub fn setup_run_embedded(apply: bool, full: bool, skip_piper: bool) -> Result<String> {
64 let mut c = ctx(apply);
65 c.skip_piper = skip_piper;
66 setup_run(&mut c, kind(full)).map_err(|e| anyhow::anyhow!("{e}"))?;
67 Ok(capture_logs(&c))
68}
69
70pub fn setup_step_embedded(step_id: &str, apply: bool, full: bool) -> Result<String> {
76 let mut c = ctx(apply);
77 setup_step(&mut c, kind(full), step_id).map_err(|e| anyhow::anyhow!("{e}"))?;
78 Ok(capture_logs(&c))
79}
80
81pub fn doctor_embedded() -> Result<String> {
87 let c = ctx(false);
88 let report = doctor(&c);
89 Ok(serde_json::to_string_pretty(&report)?)
90}
91
92pub fn docker_status_embedded() -> Result<String> {
98 let list = list_containers().map_err(|e| anyhow::anyhow!("{e}"))?;
99 Ok(serde_json::to_string_pretty(&list)?)
100}
101
102pub fn docker_rebuild_embedded(confirm: &str) -> Result<String> {
108 if confirm != CONFIRM_DOCKER_REBUILD {
109 bail!("confirm must be exactly `{CONFIRM_DOCKER_REBUILD}`");
110 }
111 let assets = std::path::Path::new("/srv/docker");
112 docker_rebuild(assets).map_err(|e| anyhow::anyhow!("{e}"))?;
113 Ok("docker rebuild finished".into())
114}
115
116pub fn backup_list_embedded() -> Result<String> {
122 let c = ctx(false);
123 let list = list_timestamped_etc_backups(&c);
124 Ok(serde_json::to_string_pretty(&list)?)
125}
126
127pub fn backup_disk_status_embedded() -> Result<String> {
133 let c = ctx(false);
134 let status = backup_status(&c);
135 Ok(serde_json::to_string_pretty(&status)?)
136}
137
138#[cfg(test)]
139mod tests {
140 use super::*;
141
142 #[test]
143 fn docker_rebuild_rejects_bad_confirm() {
144 let err = docker_rebuild_embedded("nope").expect_err("confirm");
145 assert!(err.to_string().contains("docker-rebuild"));
146 }
147
148 #[test]
149 fn setup_status_plan_serializes() {
150 let text = setup_status_report(false, true).expect("status");
151 assert!(text.contains('{') || text.contains("steps") || !text.is_empty());
152 }
153}