Skip to content
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.

1 change: 1 addition & 0 deletions crates/system-reinstall-bootc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ bootc-utils = { package = "bootc-internal-utils", path = "../utils", version = "
anstream = { workspace = true }
anyhow = { workspace = true }
clap = { workspace = true, features = ["derive"] }
fn-error-context = { workspace = true }
indoc = { workspace = true }
log = { workspace = true }
rustix = { workspace = true }
Expand Down
2 changes: 2 additions & 0 deletions crates/system-reinstall-bootc/src/btrfs.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use anyhow::Result;
use bootc_mount::Filesystem;
use fn_error_context::context;

#[context("check_root_siblings")]
pub(crate) fn check_root_siblings() -> Result<Vec<String>> {
let mounts = bootc_mount::run_findmnt(&[], None)?;
let problem_filesystems: Vec<String> = mounts
Expand Down
2 changes: 2 additions & 0 deletions crates/system-reinstall-bootc/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use std::{fs::File, io::BufReader};

use anyhow::{Context, Result};
use bootc_utils::PathQuotedDisplay;
use fn_error_context::context;
use serde::{Deserialize, Serialize};

mod cli;
Expand All @@ -17,6 +18,7 @@ pub(crate) struct ReinstallConfig {
}

impl ReinstallConfig {
#[context("load")]
pub fn load() -> Result<Option<Self>> {
let Some(config) = std::env::var_os(CONFIG_VAR) else {
return Ok(None);
Expand Down
13 changes: 8 additions & 5 deletions crates/system-reinstall-bootc/src/lvm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ use std::process::Command;

use anyhow::Result;
use bootc_mount::run_findmnt;
use bootc_utils::CommandRunExt;
use bootc_utils::{CommandRunExt, ResultExt};
use fn_error_context::context;
use serde::Deserialize;

#[derive(Debug, Deserialize)]
Expand All @@ -23,8 +24,9 @@ pub(crate) struct LogicalVolume {
vg_name: String,
}

#[context("parse_volumes")]
pub(crate) fn parse_volumes(group: Option<&str>) -> Result<Vec<LogicalVolume>> {
if which::which("podman").is_err() {
if which::which("lvs").is_err() {
tracing::debug!("lvs binary not found. Skipping logical volume check.");
return Ok(Vec::<LogicalVolume>::new());
}
Expand All @@ -46,6 +48,7 @@ pub(crate) fn parse_volumes(group: Option<&str>) -> Result<Vec<LogicalVolume>> {
.collect())
}

#[context("check_root_siblings")]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These strings are intended to be somewhat human readable. Not a blocker but how about "Looking at root sibling mounts" or something?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I just had claude generate context for all of the Result-returning functions in the crate so we would at least have some idea where to look for the problem. Not great but at least more useful than No such file or directory with absolutely no context at all.

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

Expand All @@ -54,7 +57,7 @@ pub(crate) fn check_root_siblings() -> Result<Vec<String>> {
let siblings: Vec<String> = all_volumes
.iter()
.filter(|lv| {
let mount = run_findmnt(&["-S", &lv.lv_path], None).unwrap_or_default();
let mount = run_findmnt(&["-S", &lv.lv_path], None).log_err_default();
if let Some(fs) = mount.filesystems.first() {
&fs.target == "/"
} else {
Expand All @@ -63,14 +66,14 @@ pub(crate) fn check_root_siblings() -> Result<Vec<String>> {
})
.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 = run_findmnt(&["-S", &r.lv_path], None).log_err_default();
let mount_path = if let Some(fs) = mount.filesystems.first() {
&fs.target
} else {
""
};

if mount_path != "/" {
if mount_path != "/" && !mount_path.is_empty() {
acc.push(format!(
"Type: LVM, Mount Point: {}, LV: {}, VG: {}, Size: {}",
mount_path, r.lv_name, r.vg_name, r.lv_size
Expand Down
2 changes: 2 additions & 0 deletions crates/system-reinstall-bootc/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
use anyhow::{ensure, Context, Result};
use bootc_utils::CommandRunExt;
use clap::Parser;
use fn_error_context::context;
use rustix::process::getuid;

mod btrfs;
Expand All @@ -29,6 +30,7 @@ struct Opts {
// Note if we ever add any other options here,
}

#[context("run")]
fn run() -> Result<()> {
// We historically supported an environment variable providing a config to override the image, so
// keep supporting that. I'm considering deprecating that though.
Expand Down
5 changes: 5 additions & 0 deletions crates/system-reinstall-bootc/src/podman.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ use crate::prompt;
use super::ROOT_KEY_MOUNT_POINT;
use anyhow::{ensure, Context, Result};
use bootc_utils::CommandRunExt;
use fn_error_context::context;
use std::process::Command;
use which::which;

#[context("bootc_has_clean")]
fn bootc_has_clean(image: &str) -> Result<bool> {
let output = Command::new("podman")
.args([
Expand All @@ -22,6 +24,7 @@ fn bootc_has_clean(image: &str) -> Result<bool> {
Ok(stdout_str.contains("--cleanup"))
}

#[context("reinstall_command")]
pub(crate) fn reinstall_command(image: &str, ssh_key_file: &str) -> Result<Command> {
let mut podman_command_and_args = [
// We use podman to run the bootc container. This might change in the future to remove the
Expand Down Expand Up @@ -108,6 +111,7 @@ fn image_exists_command(image: &str) -> Command {
command
}

#[context("pull_if_not_present")]
pub(crate) fn pull_if_not_present(image: &str) -> Result<()> {
let result = image_exists_command(image).status()?;

Expand Down Expand Up @@ -136,6 +140,7 @@ const fn podman_install_script_path() -> &'static str {
}
}

#[context("ensure_podman_installed")]
pub(crate) fn ensure_podman_installed() -> Result<()> {
if which("podman").is_ok() {
return Ok(());
Expand Down
8 changes: 8 additions & 0 deletions crates/system-reinstall-bootc/src/prompt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ macro_rules! println_flush {

use crate::{btrfs, lvm, prompt, users::get_all_users_keys};
use anyhow::{ensure, Context, Result};
use fn_error_context::context;

use crossterm::event::{self, Event};
use std::time::Duration;
Expand All @@ -19,6 +20,7 @@ const NO_SSH_PROMPT: &str = "None of the users on this system found have authori
if your image doesn't use cloud-init or other means to set up users, \
you may not be able to log in after reinstalling. Do you want to continue?";

#[context("prompt_single_user")]
fn prompt_single_user(user: &crate::users::UserKeys) -> Result<Vec<&crate::users::UserKeys>> {
let prompt = indoc::formatdoc! {
"Found only one user ({user}) with {num_keys} SSH authorized keys.
Expand All @@ -32,6 +34,7 @@ fn prompt_single_user(user: &crate::users::UserKeys) -> Result<Vec<&crate::users
Ok(if answer { vec![&user] } else { vec![] })
}

#[context("prompt_user_selection")]
fn prompt_user_selection(
all_users: &[crate::users::UserKeys],
) -> Result<Vec<&crate::users::UserKeys>> {
Expand All @@ -55,6 +58,7 @@ fn prompt_user_selection(
.collect())
}

#[context("reboot")]
pub(crate) fn reboot() -> Result<()> {
let delay_seconds = 10;
println_flush!(
Expand All @@ -79,6 +83,7 @@ pub(crate) fn reboot() -> Result<()> {

/// Temporary safety mechanism to stop devs from running it on their dev machine. TODO: Discuss
/// final prompting UX in https://github.com/bootc-dev/bootc/discussions/1060
#[context("temporary_developer_protection_prompt")]
pub(crate) fn temporary_developer_protection_prompt() -> Result<()> {
// Print an empty line so that the warning stands out from the rest of the output
println_flush!();
Expand All @@ -94,6 +99,7 @@ pub(crate) fn temporary_developer_protection_prompt() -> Result<()> {
Ok(())
}

#[context("ask_yes_no")]
pub(crate) fn ask_yes_no(prompt: &str, default: bool) -> Result<bool> {
dialoguer::Confirm::new()
.with_prompt(prompt)
Expand All @@ -114,6 +120,7 @@ pub(crate) fn press_enter() {
}
}

#[context("mount_warning")]
pub(crate) fn mount_warning() -> Result<()> {
let mut mounts = btrfs::check_root_siblings()?;
mounts.extend(lvm::check_root_siblings()?);
Expand All @@ -138,6 +145,7 @@ pub(crate) fn mount_warning() -> Result<()> {
/// The keys are stored in a temporary file which is passed to
/// the podman run invocation to be used by
/// `bootc install to-existing-root --root-ssh-authorized-keys`
#[context("get_ssh_keys")]
pub(crate) fn get_ssh_keys(temp_key_file_path: &str) -> Result<()> {
let users = get_all_users_keys()?;
if users.is_empty() {
Expand Down
9 changes: 9 additions & 0 deletions crates/system-reinstall-bootc/src/users.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use anyhow::{Context, Result};
use bootc_utils::CommandRunExt;
use bootc_utils::PathQuotedDisplay;
use fn_error_context::context;
use openssh_keys::PublicKey;
use rustix::fs::Uid;
use rustix::process::geteuid;
Expand All @@ -17,13 +18,15 @@ use std::os::unix::process::CommandExt;
use std::process::Command;
use uzers::os::unix::UserExt;

#[context("loginctl_users")]
fn loginctl_users() -> Result<BTreeSet<String>> {
let loginctl_raw_output = loginctl_run_compat()?;

loginctl_parse(loginctl_raw_output)
}

/// See [`test::test_parse_lsblk`] for example loginctl output
#[context("loginctl_parse")]
fn loginctl_parse(users: Value) -> Result<BTreeSet<String>> {
users
.as_array()
Expand All @@ -47,6 +50,7 @@ fn loginctl_parse(users: Value) -> Result<BTreeSet<String>> {
}

/// Run `loginctl` with some compatibility maneuvers to get JSON output
#[context("loginctl_run_compat")]
fn loginctl_run_compat() -> Result<Value> {
let mut command = Command::new("loginctl");
command.arg("list-sessions").arg("--output").arg("json");
Expand All @@ -71,6 +75,7 @@ struct UidChange {
}

impl UidChange {
#[context("new")]
fn new(change_to_uid: Uid) -> Result<Self> {
let (uid, euid) = (getuid(), geteuid());
set_thread_res_uid(uid, change_to_uid, euid).context("setting effective uid failed")?;
Expand Down Expand Up @@ -115,6 +120,7 @@ struct SshdConfig<'a> {
}

impl<'a> SshdConfig<'a> {
#[context("parse")]
pub fn parse(sshd_output: &'a str) -> Result<SshdConfig<'a>> {
let config = sshd_output
.lines()
Expand All @@ -138,6 +144,7 @@ impl<'a> SshdConfig<'a> {
}
}

#[context("get_keys_from_files")]
fn get_keys_from_files(user: &uzers::User, keyfiles: &Vec<&str>) -> Result<Vec<PublicKey>> {
let home_dir = user.home_dir();
let mut user_authorized_keys: Vec<PublicKey> = Vec::new();
Expand Down Expand Up @@ -170,6 +177,7 @@ fn get_keys_from_files(user: &uzers::User, keyfiles: &Vec<&str>) -> Result<Vec<P
Ok(user_authorized_keys)
}

#[context("get_keys_from_command")]
fn get_keys_from_command(command: &str, command_user: &str) -> Result<Vec<PublicKey>> {
let user_config = uzers::get_user_by_name(command_user).context(format!(
"authorized_keys_command_user {command_user} not found"
Expand All @@ -184,6 +192,7 @@ fn get_keys_from_command(command: &str, command_user: &str) -> Result<Vec<Public
Ok(keys)
}

#[context("get_all_users_keys")]
pub(crate) fn get_all_users_keys() -> Result<Vec<UserKeys>> {
let loginctl_user_names = loginctl_users().context("enumerate users")?;

Expand Down