1pub mod adapter;
8pub mod benchmark_logs;
9pub mod benchmark_runners;
10pub mod benchmarks;
11pub mod bootstrap;
12pub mod component_status;
13pub mod config;
14pub mod core;
15pub mod gpu;
16pub mod hardware;
17pub mod installer;
18pub mod installers;
19pub mod logging;
20pub mod orchestrator;
21pub mod platform;
22pub mod state;
23pub mod telemetry;
24pub mod uninstall;
25pub mod verification;
26
27#[cfg(test)]
36pub mod test_support {
37 use std::sync::{Mutex, MutexGuard, OnceLock};
38
39 static GLOBAL_ENV_LOCK: OnceLock<Mutex<()>> = OnceLock::new();
40
41 pub fn env_lock() -> &'static Mutex<()> {
43 GLOBAL_ENV_LOCK.get_or_init(|| Mutex::new(()))
44 }
45
46 pub fn lock_env() -> MutexGuard<'static, ()> {
55 env_lock()
56 .lock()
57 .unwrap_or_else(|poison| poison.into_inner())
58 }
59}
60
61#[cfg(feature = "tui")]
62pub mod app;
63
64#[cfg(feature = "tui")]
65pub mod widgets;
66
67use std::path::PathBuf;
68
69pub fn detect_scripts_dir() -> String {
71 let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
72 let scripts = cwd.join("scripts");
73 if scripts.exists() {
74 return scripts.to_string_lossy().to_string();
75 }
76 let parent_scripts = cwd.join("..").join("scripts");
77 if parent_scripts.exists() {
78 return parent_scripts.to_string_lossy().to_string();
79 }
80 "./scripts".to_string()
81}
82
83pub fn set_repo_root(scripts_dir: &str) {
85 let scripts_path = PathBuf::from(scripts_dir);
86 if let Some(root) = scripts_path.parent() {
87 if root.join("stans_ml_stack").exists() {
88 std::env::set_var("MLSTACK_REPO_ROOT", root);
89 }
90 }
91}
92
93#[cfg(feature = "tui")]
99pub fn run_tui() -> anyhow::Result<()> {
100 use crate::app::App;
101 use crossterm::execute;
102 use crossterm::terminal::{
103 disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen,
104 };
105 use ratatui::backend::CrosstermBackend;
106 use ratatui::Terminal;
107 use std::io;
108 use std::time::{Duration, Instant};
109
110 std::panic::set_hook(Box::new(|info| {
111 let _ = disable_raw_mode();
112 let mut stdout = io::stdout();
113 let _ = execute!(stdout, LeaveAlternateScreen, crossterm::cursor::Show);
114 eprintln!("Rusty-Stack crashed: {info}");
115 }));
116
117 enable_raw_mode()?;
118 let mut stdout = io::stdout();
119 let use_alt_screen = std::env::var("MLSTACK_NO_ALT_SCREEN").is_err();
120 if use_alt_screen {
121 execute!(stdout, EnterAlternateScreen)?;
122 }
123 let backend = CrosstermBackend::new(stdout);
124 let mut terminal = Terminal::new(backend)?;
125 if let Err(e) = terminal.hide_cursor() {
135 let _ = disable_raw_mode();
136 if use_alt_screen {
137 let _ = execute!(terminal.backend_mut(), LeaveAlternateScreen);
138 }
139 return Err(e.into());
140 }
141
142 let scripts_dir = detect_scripts_dir();
143 set_repo_root(&scripts_dir);
144 let mut app = App::new(scripts_dir);
145
146 let tick_rate = Duration::from_millis(100);
147 let mut last_tick = Instant::now();
148
149 let res = run_app(&mut terminal, &mut app, tick_rate, &mut last_tick);
150
151 disable_raw_mode()?;
152 if use_alt_screen {
153 execute!(terminal.backend_mut(), LeaveAlternateScreen)?;
154 }
155 terminal.show_cursor()?;
156
157 if let Err(err) = res {
158 eprintln!("Error: {err:?}");
159 }
160
161 Ok(())
162}
163
164#[cfg(feature = "tui")]
166fn run_app<B: ratatui::backend::Backend>(
167 terminal: &mut ratatui::Terminal<B>,
168 app: &mut crate::app::App,
169 tick_rate: std::time::Duration,
170 last_tick: &mut std::time::Instant,
171) -> anyhow::Result<()>
172where
173 <B as ratatui::backend::Backend>::Error: Send + Sync + 'static,
174{
175 use crate::state::Stage;
176 use crossterm::event::{self, Event, KeyCode, KeyModifiers};
177
178 loop {
179 terminal.draw(|frame| app.draw(frame))?;
180
181 let timeout = tick_rate
182 .checked_sub(last_tick.elapsed())
183 .unwrap_or_else(|| std::time::Duration::from_secs(0));
184
185 if event::poll(timeout)? {
186 match event::read()? {
187 Event::Key(key) => {
188 if key.code == KeyCode::Char('c')
189 && key.modifiers.contains(KeyModifiers::CONTROL)
190 {
191 return Ok(());
192 }
193 if key.code == KeyCode::Char('q') && app.stage == Stage::Recovery {
194 return Ok(());
195 }
196 app.handle_key(key);
197 }
198 Event::Resize(_, _) => {
199 terminal.autoresize()?;
200 }
201 _ => {}
202 }
203 }
204
205 if last_tick.elapsed() >= tick_rate {
206 app.on_tick();
207 *last_tick = std::time::Instant::now();
208 }
209
210 if app.should_exit {
211 return Ok(());
212 }
213 }
214}