mirror of
https://git.proxmox.com/git/pve-installer
synced 2025-04-28 12:51:31 +00:00
auto-installer: add fetch answer binary
it is supposed to be run first and fetch an answer file. The initial implementation searches for a partition/filesystem called 'proxmoxinst' or 'PROXMOXINST' with an 'answer.toml' file in the root directory. Once it has an answer file, it will call the 'proxmox-auto-installer' and pipe in the contents via stdin. Tested-by: Christoph Heiss <c.heiss@proxmox.com> Reviewed-by: Christoph Heiss <c.heiss@proxmox.com> Signed-off-by: Aaron Lauterer <a.lauterer@proxmox.com> Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
This commit is contained in:
parent
781f879edc
commit
2949f82539
4
Makefile
4
Makefile
@ -20,6 +20,7 @@ PREFIX = /usr
|
||||
BINDIR = $(PREFIX)/bin
|
||||
USR_BIN := \
|
||||
proxmox-tui-installer\
|
||||
proxmox-fetch-answer\
|
||||
proxmox-auto-installer
|
||||
|
||||
COMPILED_BINS := \
|
||||
@ -121,7 +122,8 @@ $(COMPILED_BINS): cargo-build
|
||||
.PHONY: cargo-build
|
||||
cargo-build:
|
||||
$(CARGO) build --package proxmox-tui-installer --bin proxmox-tui-installer \
|
||||
--package proxmox-auto-installer --bin proxmox-auto-installer $(CARGO_BUILD_ARGS)
|
||||
--package proxmox-auto-installer --bin proxmox-auto-installer \
|
||||
--bin proxmox-fetch-answer $(CARGO_BUILD_ARGS)
|
||||
|
||||
%-banner.png: %-banner.svg
|
||||
rsvg-convert -o $@ $<
|
||||
|
71
proxmox-auto-installer/src/bin/proxmox-fetch-answer.rs
Normal file
71
proxmox-auto-installer/src/bin/proxmox-fetch-answer.rs
Normal file
@ -0,0 +1,71 @@
|
||||
use anyhow::{anyhow, Error, Result};
|
||||
use log::{error, info, LevelFilter};
|
||||
use proxmox_auto_installer::{fetch_plugins::partition::FetchFromPartition, log::AutoInstLogger};
|
||||
use std::io::Write;
|
||||
use std::process::{Command, ExitCode, Stdio};
|
||||
|
||||
static LOGGER: AutoInstLogger = AutoInstLogger;
|
||||
|
||||
pub fn init_log() -> Result<()> {
|
||||
AutoInstLogger::init("/tmp/fetch_answer.log")?;
|
||||
log::set_logger(&LOGGER)
|
||||
.map(|()| log::set_max_level(LevelFilter::Info))
|
||||
.map_err(|err| anyhow!(err))
|
||||
}
|
||||
|
||||
fn fetch_answer() -> Result<String> {
|
||||
match FetchFromPartition::get_answer() {
|
||||
Ok(answer) => return Ok(answer),
|
||||
Err(err) => info!("Fetching answer file from partition failed: {err}"),
|
||||
}
|
||||
// TODO: add more options to get an answer file, e.g. download from url where url could be
|
||||
// fetched via txt records on predefined subdomain, kernel param, dhcp option, ...
|
||||
|
||||
Err(Error::msg("Could not find any answer file!"))
|
||||
}
|
||||
|
||||
fn main() -> ExitCode {
|
||||
if let Err(err) = init_log() {
|
||||
panic!("could not initialize logging: {err}");
|
||||
}
|
||||
|
||||
info!("Fetching answer file");
|
||||
let answer = match fetch_answer() {
|
||||
Ok(answer) => answer,
|
||||
Err(err) => {
|
||||
error!("Aborting: {}", err);
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
};
|
||||
|
||||
let mut child = match Command::new("proxmox-auto-installer")
|
||||
.stdout(Stdio::inherit())
|
||||
.stdin(Stdio::piped())
|
||||
.stderr(Stdio::null())
|
||||
.spawn()
|
||||
{
|
||||
Ok(child) => child,
|
||||
Err(err) => panic!("Failed to start automatic installation: {err}"),
|
||||
};
|
||||
|
||||
let mut stdin = child.stdin.take().expect("Failed to open stdin");
|
||||
std::thread::spawn(move || {
|
||||
stdin
|
||||
.write_all(answer.as_bytes())
|
||||
.expect("Failed to write to stdin");
|
||||
});
|
||||
|
||||
match child.wait() {
|
||||
Ok(status) => {
|
||||
if status.success() {
|
||||
ExitCode::SUCCESS
|
||||
} else {
|
||||
ExitCode::FAILURE // Will be trapped
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Auto installer exited: {err}");
|
||||
ExitCode::FAILURE
|
||||
}
|
||||
}
|
||||
}
|
2
proxmox-auto-installer/src/fetch_plugins/mod.rs
Normal file
2
proxmox-auto-installer/src/fetch_plugins/mod.rs
Normal file
@ -0,0 +1,2 @@
|
||||
pub mod partition;
|
||||
mod utils;
|
32
proxmox-auto-installer/src/fetch_plugins/partition.rs
Normal file
32
proxmox-auto-installer/src/fetch_plugins/partition.rs
Normal file
@ -0,0 +1,32 @@
|
||||
use anyhow::{Error, Result};
|
||||
use std::{fs::read_to_string, path::Path};
|
||||
use log::info;
|
||||
|
||||
use crate::fetch_plugins::utils::mount_proxmoxinst_part;
|
||||
|
||||
static ANSWER_FILE: &str = "answer.toml";
|
||||
|
||||
pub struct FetchFromPartition;
|
||||
|
||||
impl FetchFromPartition {
|
||||
/// Returns the contents of the answer file
|
||||
pub fn get_answer() -> Result<String> {
|
||||
info!("Checking for answer file on partition.");
|
||||
let mount_path = mount_proxmoxinst_part()?;
|
||||
let answer = Self::get_answer_file(&mount_path)?;
|
||||
info!("Found answer file on partition.");
|
||||
Ok(answer)
|
||||
}
|
||||
|
||||
/// Searches for answer file and returns contents if found
|
||||
fn get_answer_file(mount_path: &str) -> Result<String> {
|
||||
let answer_path = Path::new(mount_path).join(ANSWER_FILE);
|
||||
match answer_path.try_exists() {
|
||||
Ok(true) => Ok(read_to_string(answer_path)?),
|
||||
_ => Err(Error::msg(format!(
|
||||
"could not find answer file expected at: {}",
|
||||
answer_path.display()
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
81
proxmox-auto-installer/src/fetch_plugins/utils.rs
Normal file
81
proxmox-auto-installer/src/fetch_plugins/utils.rs
Normal file
@ -0,0 +1,81 @@
|
||||
use anyhow::{bail, Error, Result};
|
||||
use log::{info, warn};
|
||||
use std::{
|
||||
fs::{self, create_dir_all},
|
||||
path::{Path, PathBuf},
|
||||
process::Command,
|
||||
};
|
||||
|
||||
static ANSWER_MP: &str = "/mnt/answer";
|
||||
static PARTLABEL: &str = "proxmoxinst";
|
||||
static SEARCH_PATH: &str = "/dev/disk/by-label";
|
||||
|
||||
/// Searches for upper and lower case existence of the partlabel in the search_path
|
||||
///
|
||||
/// # Arguemnts
|
||||
/// * `partlabel_source` - Partition Label, used as upper and lower case
|
||||
/// * `search_path` - Path where to search for the partiiton label
|
||||
pub fn scan_partlabels(partlabel_source: &str, search_path: &str) -> Result<PathBuf> {
|
||||
let partlabel = partlabel_source.to_uppercase();
|
||||
let path = Path::new(search_path).join(&partlabel);
|
||||
match path.try_exists() {
|
||||
Ok(true) => {
|
||||
info!("Found partition with label '{partlabel}'");
|
||||
return Ok(path);
|
||||
}
|
||||
Ok(false) => info!("Did not detect partition with label '{partlabel}'"),
|
||||
Err(err) => info!("Encountered issue, accessing '{}': {}", path.display(), err),
|
||||
}
|
||||
|
||||
let partlabel = partlabel_source.to_lowercase();
|
||||
let path = Path::new(search_path).join(&partlabel);
|
||||
match path.try_exists() {
|
||||
Ok(true) => {
|
||||
info!("Found partition with label '{partlabel}'");
|
||||
return Ok(path);
|
||||
}
|
||||
Ok(false) => info!("Did not detect partition with label '{partlabel}'"),
|
||||
Err(err) => info!("Encountered issue, accessing '{}': {}", path.display(), err),
|
||||
}
|
||||
bail!("Could not detect upper or lower case labels for '{partlabel_source}'");
|
||||
}
|
||||
|
||||
/// Will search and mount a partition/FS labeled proxmoxinst in lower or uppercase to ANSWER_MP;
|
||||
pub fn mount_proxmoxinst_part() -> Result<String> {
|
||||
if let Ok(true) = check_if_mounted(ANSWER_MP) {
|
||||
info!("Skipping: '{ANSWER_MP}' is already mounted.");
|
||||
return Ok(ANSWER_MP.into());
|
||||
}
|
||||
let part_path = scan_partlabels(PARTLABEL, SEARCH_PATH)?;
|
||||
info!("Mounting partition at {ANSWER_MP}");
|
||||
// create dir for mountpoint
|
||||
create_dir_all(ANSWER_MP)?;
|
||||
match Command::new("mount")
|
||||
.args(["-o", "ro"])
|
||||
.arg(part_path)
|
||||
.arg(ANSWER_MP)
|
||||
.output()
|
||||
{
|
||||
Ok(output) => {
|
||||
if output.status.success() {
|
||||
Ok(ANSWER_MP.into())
|
||||
} else {
|
||||
warn!("Error mounting: {}", String::from_utf8(output.stderr)?);
|
||||
Ok(ANSWER_MP.into())
|
||||
}
|
||||
}
|
||||
Err(err) => Err(Error::msg(format!("Error mounting: {err}"))),
|
||||
}
|
||||
}
|
||||
|
||||
fn check_if_mounted(target_path: &str) -> Result<bool> {
|
||||
let mounts = fs::read_to_string("/proc/mounts")?;
|
||||
for line in mounts.lines() {
|
||||
if let Some(mp) = line.split(' ').nth(1) {
|
||||
if mp == target_path {
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(false)
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
pub mod answer;
|
||||
pub mod fetch_plugins;
|
||||
pub mod log;
|
||||
pub mod udevinfo;
|
||||
pub mod utils;
|
||||
|
Loading…
Reference in New Issue
Block a user