horto_os_ui_shared/remote/runner/
reboot.rs1use super::super::process::{ProcessRunner, StdioMode};
4use super::super::ssh::SshSession;
5use super::options::{remote_progress, session_from, RemoteOptions};
6use crate::error::Result;
7
8pub fn remote_reboot(runner: &dyn ProcessRunner, opts: &RemoteOptions) -> Result<()> {
14 let session = session_from(opts)?;
15 finish_remote_reboot(runner, &session, "y", None)
16}
17
18pub fn remote_reboot_with_sudo_password(
26 runner: &dyn ProcessRunner,
27 opts: &RemoteOptions,
28 sudo_password: &str,
29) -> Result<()> {
30 let session = session_from(opts)?;
31 finish_remote_reboot(runner, &session, "y", Some(sudo_password))
32}
33
34#[must_use]
36pub fn wants_reboot_now(raw: &str) -> bool {
37 matches!(raw.trim().to_ascii_lowercase().as_str(), "y" | "yes")
38}
39
40pub fn offer_remote_reboot(runner: &dyn ProcessRunner, session: &SshSession) -> Result<()> {
42 use std::io::{self, IsTerminal, Write};
43
44 if !io::stdin().is_terminal() {
45 eprintln!("Reboot recommended for hostname/network changes. On the box: sudo reboot");
46 return Ok(());
47 }
48 eprint!("Reboot the box now to apply hostname/network changes? [y/N]: ");
49 let _ = io::stderr().flush();
50 let mut line = String::new();
51 io::stdin()
52 .read_line(&mut line)
53 .map_err(|e| crate::error::HortoError::msg(format!("read reboot prompt: {e}")))?;
54 finish_remote_reboot(runner, session, &line, None)
55}
56
57pub fn finish_remote_reboot(
58 runner: &dyn ProcessRunner,
59 session: &SshSession,
60 answer: &str,
61 sudo_password: Option<&str>,
62) -> Result<()> {
63 if !wants_reboot_now(answer) {
64 eprintln!("Skipping reboot. Reboot the box later when convenient.");
65 return Ok(());
66 }
67 remote_progress(
68 &session.host.raw,
69 "reboot box (SSH + sudo; may ask password)",
70 );
71 eprintln!("Rebooting...");
72 let used_stdin = sudo_password.is_some();
73 let result = sudo_password.map_or_else(
74 || session.exec(runner, "sudo reboot", StdioMode::Inherit),
75 |pass| {
76 let mut feed = String::with_capacity(pass.len() + 1);
77 feed.push_str(pass);
78 feed.push('\n');
79 let out = session.exec_stdin_reboot(runner, "sudo -S reboot", feed.as_bytes());
80 feed.clear();
81 out
82 },
83 );
84 match result {
85 Ok(_) => Ok(()),
86 Err(e) => {
87 if used_stdin && !ssh_drop_after_reboot(&e) {
88 return Err(e);
89 }
90 eprintln!("reboot issued (SSH session closed is expected): {e}");
92 Ok(())
93 }
94 }
95}
96
97pub fn ssh_drop_after_reboot(err: &crate::error::HortoError) -> bool {
98 let s = err.to_string().to_ascii_lowercase();
99 s.contains("connection closed")
100 || s.contains("connection reset")
101 || s.contains("broken pipe")
102 || s.contains("exit 255")
103}