]> git.proxmox.com Git - pve-installer.git/blob - proxmox-auto-installer/src/bin/proxmox-auto-installer.rs
auto-installer: add auto-installer binary
[pve-installer.git] / proxmox-auto-installer / src / bin / proxmox-auto-installer.rs
1 use anyhow::{anyhow, bail, Error, 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,
19 utils::{parse_answer, LowLevelMessage},
20 };
21
22 static LOGGER: AutoInstLogger = AutoInstLogger;
23
24 pub fn init_log() -> Result<()> {
25 AutoInstLogger::init("/tmp/auto_installer.log")?;
26 log::set_logger(&LOGGER)
27 .map(|()| log::set_max_level(LevelFilter::Info))
28 .map_err(|err| anyhow!(err))
29 }
30
31 fn auto_installer_setup(in_test_mode: bool) -> Result<(Answer, UdevInfo)> {
32 let base_path = if in_test_mode { "./testdir" } else { "/" };
33 let mut path = PathBuf::from(base_path);
34
35 path.push("run");
36 path.push("proxmox-installer");
37
38 let udev_info: UdevInfo = {
39 let mut path = path.clone();
40 path.push("run-env-udev.json");
41
42 read_json(&path).map_err(|err| anyhow!("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| anyhow!("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 utils::run_cmds("Pre", &answer.global.pre_commands) {
87 Ok(_) => (),
88 Err(err) => {
89 error!("Error when running Pre-Commands: {}", err);
90 return exit_failure(answer.global.reboot_on_error);
91 }
92 };
93 match run_installation(&answer, &locales, &runtime_info, &udevadm_info, &setup_info) {
94 Ok(_) => info!("Installation done."),
95 Err(err) => {
96 error!("Installation failed: {err}");
97 return exit_failure(answer.global.reboot_on_error);
98 }
99 }
100 match utils::run_cmds("Post", &answer.global.post_commands) {
101 Ok(_) => (),
102 Err(err) => {
103 error!("Error when running Post-Commands: {}", err);
104 return exit_failure(answer.global.reboot_on_error);
105 }
106 };
107 ExitCode::SUCCESS
108 }
109
110 /// When we exit with a failure, the installer will not automatically reboot.
111 /// Default value for reboot_on_error is false
112 fn exit_failure(reboot_on_error: bool) -> ExitCode {
113 if reboot_on_error {
114 ExitCode::SUCCESS
115 } else {
116 ExitCode::FAILURE
117 }
118 }
119
120 fn run_installation(
121 answer: &Answer,
122 locales: &LocaleInfo,
123 runtime_info: &RuntimeInfo,
124 udevadm_info: &UdevInfo,
125 setup_info: &SetupInfo,
126 ) -> Result<()> {
127 let config = parse_answer(answer, udevadm_info, runtime_info, locales, setup_info)?;
128 info!("Calling low-level installer");
129
130 let mut child = match spawn_low_level_installer(false) {
131 Ok(child) => child,
132 Err(err) => {
133 bail!("Low level installer could not be started: {}", err);
134 }
135 };
136
137 let mut cur_counter = 111;
138 let mut inner = || -> Result<()> {
139 let reader = child
140 .stdout
141 .take()
142 .map(BufReader::new)
143 .ok_or(anyhow!("failed to get stdout reader"))?;
144 let mut writer = child
145 .stdin
146 .take()
147 .ok_or(anyhow!("failed to get stdin writer"))?;
148
149 serde_json::to_writer(&mut writer, &config)
150 .map_err(|err| anyhow!("failed to serialize install config: {err}"))?;
151 writeln!(writer).map_err(|err| anyhow!("failed to write install config: {err}"))?;
152
153 for line in reader.lines() {
154 let line = match line {
155 Ok(line) => line,
156 Err(_) => break,
157 };
158 let msg = match serde_json::from_str::<LowLevelMessage>(&line) {
159 Ok(msg) => msg,
160 Err(_) => {
161 // Not a fatal error, so don't abort the installation by returning
162 continue;
163 }
164 };
165
166 match msg.clone() {
167 LowLevelMessage::Info { message } => info!("{message}"),
168 LowLevelMessage::Error { message } => error!("{message}"),
169 LowLevelMessage::Prompt { query } => {
170 bail!("Got interactive prompt I cannot answer: {query}")
171 }
172 LowLevelMessage::Progress { ratio, text: _ } => {
173 let counter = (ratio * 100.).floor() as usize;
174 if counter != cur_counter {
175 cur_counter = counter;
176 info!("Progress: {counter:>3}%");
177 }
178 }
179 LowLevelMessage::Finished { state, message } => {
180 if state == "err" {
181 bail!("{message}");
182 }
183 info!("Finished: '{state}' {message}");
184 }
185 };
186 }
187 Ok(())
188 };
189 match inner() {
190 Err(err) => Err(Error::msg(format!(
191 "low level installer returned early: {err}"
192 ))),
193 _ => Ok(()),
194 }
195 }