Skip to content

Reinstall mount warning #1299

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

29 changes: 14 additions & 15 deletions mount/src/mount.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,29 +45,28 @@ pub struct Filesystem {
pub children: Option<Vec<Filesystem>>,
}

#[derive(Deserialize, Debug)]
#[derive(Deserialize, Debug, Default)]
pub struct Findmnt {
pub filesystems: Vec<Filesystem>,
}

fn run_findmnt(args: &[&str], path: &str) -> Result<Findmnt> {
let o: Findmnt = Command::new("findmnt")
.args([
"-J",
"-v",
// If you change this you probably also want to change the Filesystem struct above
"--output=SOURCE,TARGET,MAJ:MIN,FSTYPE,OPTIONS,UUID",
])
.args(args)
.arg(path)
.log_debug()
.run_and_parse_json()?;
pub fn run_findmnt(args: &[&str], path: Option<&str>) -> Result<Findmnt> {
let mut cmd = Command::new("findmnt");
cmd.args([
"-J",
"-v",
// If you change this you probably also want to change the Filesystem struct above
"--output=SOURCE,TARGET,MAJ:MIN,FSTYPE,OPTIONS,UUID",
])
.args(args)
.args(path);
let o: Findmnt = cmd.log_debug().run_and_parse_json()?;
Ok(o)
}

// Retrieve a mounted filesystem from a device given a matching path
fn findmnt_filesystem(args: &[&str], path: &str) -> Result<Filesystem> {
let o = run_findmnt(args, path)?;
let o = run_findmnt(args, Some(path))?;
o.filesystems
.into_iter()
.next()
Expand All @@ -90,7 +89,7 @@ pub fn inspect_filesystem_by_uuid(uuid: &str) -> Result<Filesystem> {
// Check if a specified device contains an already mounted filesystem
// in the root mount namespace
pub fn is_mounted_in_pid1_mountns(path: &str) -> Result<bool> {
let o = run_findmnt(&["-N"], "1")?;
let o = run_findmnt(&["-N"], Some("1"))?;

let mounted = o.filesystems.iter().any(|fs| is_source_mounted(path, fs));

Expand Down
1 change: 1 addition & 0 deletions system-reinstall-bootc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ platforms = ["*-unknown-linux-gnu"]

[dependencies]
anyhow = { workspace = true }
bootc-mount = { path = "../mount" }
bootc-utils = { path = "../utils" }
clap = { workspace = true, features = ["derive"] }
crossterm = "0.29.0"
Expand Down
27 changes: 27 additions & 0 deletions system-reinstall-bootc/src/btrfs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
use anyhow::Result;
use bootc_mount::Filesystem;

pub(crate) fn check_root_siblings() -> Result<Vec<String>> {
let mounts = bootc_mount::run_findmnt(&[], None)?;
let problem_filesystems: Vec<String> = mounts
.filesystems
.iter()
.filter(|fs| fs.target == "/")
.flat_map(|root| {
let children: Vec<&Filesystem> = root
.children
.iter()
.flatten()
.filter(|child| child.source == root.source)
.collect();
children
})
.map(|zs| {
format!(
"Type: {}, Mount Point: {}, Source: {}",
zs.fstype, zs.target, zs.source
)
})
.collect();
Ok(problem_filesystems)
}
84 changes: 84 additions & 0 deletions system-reinstall-bootc/src/lvm.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
use std::process::Command;

use anyhow::Result;
use bootc_mount::run_findmnt;
use bootc_utils::CommandRunExt;
use serde::Deserialize;

#[derive(Debug, Deserialize)]
pub(crate) struct Lvs {
report: Vec<LvsReport>,
}

#[derive(Debug, Deserialize)]
pub(crate) struct LvsReport {
lv: Vec<LogicalVolume>,
}

#[derive(Debug, Deserialize, Clone)]
pub(crate) struct LogicalVolume {
lv_name: String,
lv_size: String,
lv_path: String,
vg_name: String,
}

pub(crate) fn parse_volumes(group: Option<&str>) -> Result<Vec<LogicalVolume>> {
if which::which("podman").is_err() {
tracing::debug!("lvs binary not found. Skipping logical volume check.");
return Ok(Vec::<LogicalVolume>::new());
}

let mut cmd = Command::new("lvs");
cmd.args([
"--reportformat=json",
"-o",
"lv_name,lv_size,lv_path,vg_name",
])
.args(group);

let output: Lvs = cmd.run_and_parse_json()?;

Ok(output
.report
.iter()
.flat_map(|r| r.lv.iter().cloned())
.collect())
}

pub(crate) fn check_root_siblings() -> Result<Vec<String>> {
let all_volumes = parse_volumes(None)?;

// first look for a lv mounted to '/'
// then gather all the sibling lvs in the vg along with their mount points
let siblings: Vec<String> = all_volumes
.iter()
.filter(|lv| {
let mount = run_findmnt(&["-S", &lv.lv_path], None).unwrap_or_default();
if let Some(fs) = mount.filesystems.first() {
&fs.target == "/"
} else {
false
}
})
.flat_map(|root_lv| parse_volumes(Some(root_lv.vg_name.as_str())).unwrap_or_default())
.try_fold(Vec::new(), |mut acc, r| -> anyhow::Result<_> {
let mount = run_findmnt(&["-S", &r.lv_path], None)?;
let mount_path = if let Some(fs) = mount.filesystems.first() {
&fs.target
} else {
""
};

if mount_path != "/" {
acc.push(format!(
"Type: LVM, Mount Point: {}, LV: {}, VG: {}, Size: {}",
mount_path, r.lv_name, r.vg_name, r.lv_size
))
};

Ok(acc)
})?;

Ok(siblings)
}
7 changes: 7 additions & 0 deletions system-reinstall-bootc/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ use anyhow::{ensure, Context, Result};
use bootc_utils::CommandRunExt;
use rustix::process::getuid;

mod btrfs;
mod config;
mod lvm;
mod podman;
mod prompt;
pub(crate) mod users;
Expand Down Expand Up @@ -40,6 +42,8 @@ fn run() -> Result<()> {

prompt::get_ssh_keys(ssh_key_file_path)?;

prompt::mount_warning()?;

let mut reinstall_podman_command =
podman::reinstall_command(&config.bootc_image, ssh_key_file_path);

Expand All @@ -48,6 +52,9 @@ fn run() -> Result<()> {
println!();
println!("{}", reinstall_podman_command.to_string_pretty());

println!();
println!("After reboot, the current root will be available in the /sysroot directory. Existing mounts will not be automatically mounted by the bootc system unless they are defined in the bootc image. Some automatic cleanup of the previous root will be performed.");

prompt::temporary_developer_protection_prompt()?;

reinstall_podman_command
Expand Down
30 changes: 29 additions & 1 deletion system-reinstall-bootc/src/prompt.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{prompt, users::get_all_users_keys};
use crate::{btrfs, lvm, prompt, users::get_all_users_keys};
use anyhow::{ensure, Context, Result};

use crossterm::event::{self, Event};
Expand Down Expand Up @@ -92,6 +92,34 @@ pub(crate) fn ask_yes_no(prompt: &str, default: bool) -> Result<bool> {
.context("prompting")
}

pub(crate) fn press_enter() {
println!();
println!("Press <enter> to continue.");

loop {
if let Event::Key(_) = event::read().unwrap() {
break;
}
}
}

pub(crate) fn mount_warning() -> Result<()> {
let mut mounts = btrfs::check_root_siblings()?;
mounts.extend(lvm::check_root_siblings()?);

if !mounts.is_empty() {
println!();
println!("NOTICE: the following mounts are left unchanged by this tool and will not be automatically mounted unless specified in the bootc image. Consult the bootc documentation to determine the appropriate action for your system.");
println!();
for m in mounts {
println!("{m}");
}
press_enter();
}

Ok(())
}

/// Gather authorized keys for all user's of the host system
/// prompt the user to select which users's keys will be imported
/// into the target system's root user's authorized_keys file
Expand Down