]> git.proxmox.com Git - pve-installer.git/blob - proxmox-auto-installer/src/bin/proxmox-auto-installer.rs
auto installer: report every progress update and include text
[pve-installer.git] / proxmox-auto-installer / src / bin / proxmox-auto-installer.rs
1 use anyhow::{bail, format_err, Result};
2 use log::{error, info, LevelFilter};
3 use std::{
4 env,
5 io::{BufRead, BufReader, Write},
6 path::PathBuf,
7 process::ExitCode,
8 };
9
10 use proxmox_installer_common::setup::{
11 installer_setup, read_json, spawn_low_level_installer, LocaleInfo, RuntimeInfo, SetupInfo,
12 };
13
14 use proxmox_auto_installer::{
15 answer::Answer,
16 log::AutoInstLogger,
17 udevinfo::UdevInfo,
18 utils::{parse_answer, LowLevelMessage},
19 };
20
21 static LOGGER: AutoInstLogger = AutoInstLogger;
22
23 pub fn init_log() -> Result<()> {
24 AutoInstLogger::init("/tmp/auto_installer.log")?;
25 log::set_logger(&LOGGER)
26 .map(|()| log::set_max_level(LevelFilter::Info))
27 .map_err(|err| format_err!(err))
28 }
29
30 fn auto_installer_setup(in_test_mode: bool) -> Result<(Answer, UdevInfo)> {
31 let base_path = if in_test_mode { "./testdir" } else { "/" };
32 let mut path = PathBuf::from(base_path);
33
34 path.push("run");
35 path.push("proxmox-installer");
36
37 let udev_info: UdevInfo = {
38 let mut path = path.clone();
39 path.push("run-env-udev.json");
40
41 read_json(&path)
42 .map_err(|err| format_err!("Failed to retrieve udev info details: {err}"))?
43 };
44
45 let mut buffer = String::new();
46 let lines = std::io::stdin().lock().lines();
47 for line in lines {
48 buffer.push_str(&line.unwrap());
49 buffer.push('\n');
50 }
51
52 let answer: Answer =
53 toml::from_str(&buffer).map_err(|err| format_err!("Failed parsing answer file: {err}"))?;
54
55 Ok((answer, udev_info))
56 }
57
58 fn main() -> ExitCode {
59 if let Err(err) = init_log() {
60 panic!("could not initilize logging: {}", err);
61 }
62
63 let in_test_mode = match env::args().nth(1).as_deref() {
64 Some("-t") => true,
65 // Always force the test directory in debug builds
66 _ => cfg!(debug_assertions),
67 };
68 info!("Starting auto installer");
69
70 let (setup_info, locales, runtime_info) = match installer_setup(in_test_mode) {
71 Ok(result) => result,
72 Err(err) => {
73 error!("Installer setup error: {err}");
74 return ExitCode::FAILURE;
75 }
76 };
77
78 let (answer, udevadm_info) = match auto_installer_setup(in_test_mode) {
79 Ok(result) => result,
80 Err(err) => {
81 error!("Autoinstaller setup error: {err}");
82 return ExitCode::FAILURE;
83 }
84 };
85
86 match run_installation(&answer, &locales, &runtime_info, &udevadm_info, &setup_info) {
87 Ok(_) => info!("Installation done."),
88 Err(err) => {
89 error!("Installation failed: {err}");
90 return exit_failure(answer.global.reboot_on_error);
91 }
92 }
93
94 // TODO: (optionally) do a HTTP post with basic system info, like host SSH public key(s) here
95
96 ExitCode::SUCCESS
97 }
98
99 /// When we exit with a failure, the installer will not automatically reboot.
100 /// Default value for reboot_on_error is false
101 fn exit_failure(reboot_on_error: bool) -> ExitCode {
102 if reboot_on_error {
103 ExitCode::SUCCESS
104 } else {
105 ExitCode::FAILURE
106 }
107 }
108
109 fn run_installation(
110 answer: &Answer,
111 locales: &LocaleInfo,
112 runtime_info: &RuntimeInfo,
113 udevadm_info: &UdevInfo,
114 setup_info: &SetupInfo,
115 ) -> Result<()> {
116 let config = parse_answer(answer, udevadm_info, runtime_info, locales, setup_info)?;
117 info!("Calling low-level installer");
118
119 let mut child = match spawn_low_level_installer(false) {
120 Ok(child) => child,
121 Err(err) => {
122 bail!("Low level installer could not be started: {}", err);
123 }
124 };
125
126 let mut inner = || -> Result<()> {
127 let reader = child
128 .stdout
129 .take()
130 .map(BufReader::new)
131 .ok_or(format_err!("failed to get stdout reader"))?;
132 let mut writer = child
133 .stdin
134 .take()
135 .ok_or(format_err!("failed to get stdin writer"))?;
136
137 serde_json::to_writer(&mut writer, &config)
138 .map_err(|err| format_err!("failed to serialize install config: {err}"))?;
139 writeln!(writer).map_err(|err| format_err!("failed to write install config: {err}"))?;
140
141 for line in reader.lines() {
142 let line = match line {
143 Ok(line) => line,
144 Err(_) => break,
145 };
146 let msg = match serde_json::from_str::<LowLevelMessage>(&line) {
147 Ok(msg) => msg,
148 Err(_) => {
149 // Not a fatal error, so don't abort the installation by returning
150 continue;
151 }
152 };
153
154 match msg.clone() {
155 LowLevelMessage::Info { message } => info!("{message}"),
156 LowLevelMessage::Error { message } => error!("{message}"),
157 LowLevelMessage::Prompt { query } => {
158 bail!("Got interactive prompt I cannot answer: {query}")
159 }
160 LowLevelMessage::Progress { ratio, text } => {
161 let percentage = ratio * 100.;
162 info!("progress {percentage:>5.1} % - {text}");
163 }
164 LowLevelMessage::Finished { state, message } => {
165 if state == "err" {
166 bail!("{message}");
167 }
168 info!("Finished: '{state}' {message}");
169 }
170 };
171 }
172 Ok(())
173 };
174 inner().map_err(|err| format_err!("low level installer returned early: {err}"))
175 }