Skip to content

Commit c8e1fb8

Browse files
authored
Merge pull request #1063 from omertuc/reinstallcli
cli: add `system-reinstall-bootc` binary
2 parents d45864d + e589fe1 commit c8e1fb8

File tree

18 files changed

+541
-23
lines changed

18 files changed

+541
-23
lines changed

Cargo.lock

Lines changed: 54 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
[workspace]
22
members = [
33
"cli",
4+
"system-reinstall-bootc",
45
"lib",
56
"ostree-ext",
67
"utils",

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ all:
99

1010
install:
1111
install -D -m 0755 -t $(DESTDIR)$(prefix)/bin target/release/bootc
12+
install -D -m 0755 -t $(DESTDIR)$(prefix)/bin target/release/system-reinstall-bootc
1213
install -d -m 0755 $(DESTDIR)$(prefix)/lib/bootc/bound-images.d
1314
install -d -m 0755 $(DESTDIR)$(prefix)/lib/bootc/kargs.d
1415
ln -s /sysroot/ostree/bootc/storage $(DESTDIR)$(prefix)/lib/bootc/storage

cli/Cargo.toml

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,16 @@ default-run = "bootc"
1010

1111
# See https://github.com/coreos/cargo-vendor-filterer
1212
[package.metadata.vendor-filter]
13-
# This list of platforms is not intended to be exclusive, feel free
14-
# to extend it. But missing a platform will only matter for the case where
15-
# a dependent crate is *only* built on that platform.
16-
platforms = ["x86_64-unknown-linux-gnu", "aarch64-unknown-linux-gnu", "powerpc64le-unknown-linux-gnu", "s390x-unknown-linux-gnu", "riscv64gc-unknown-linux-gnu"]
13+
# For now we only care about tier 1+2 Linux. (In practice, it's unlikely there is a tier3-only Linux dependency)
14+
platforms = ["*-unknown-linux-gnu"]
1715

1816
[dependencies]
1917
anyhow = { workspace = true }
2018
bootc-lib = { version = "1.0", path = "../lib" }
21-
clap = { workspace = true }
19+
bootc-utils = { path = "../utils" }
2220
tokio = { workspace = true, features = ["macros"] }
2321
log = "0.4.21"
2422
tracing = { workspace = true }
25-
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
2623

2724
[lints]
2825
workspace = true

cli/src/main.rs

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,14 @@
11
//! The main entrypoint for bootc, which just performs global initialization, and then
22
//! calls out into the library.
3-
43
use anyhow::Result;
54

65
/// The code called after we've done process global init and created
76
/// an async runtime.
87
async fn async_main() -> Result<()> {
9-
// Don't include timestamps and such because they're not really useful and
10-
// too verbose, and plus several log targets such as journald will already
11-
// include timestamps.
12-
let format = tracing_subscriber::fmt::format()
13-
.without_time()
14-
.with_target(false)
15-
.compact();
16-
// Log to stderr by default
17-
tracing_subscriber::fmt()
18-
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
19-
.event_format(format)
20-
.with_writer(std::io::stderr)
21-
.init();
22-
tracing::trace!("starting");
8+
bootc_utils::initialize_tracing();
9+
10+
tracing::trace!("starting bootc");
11+
2312
// As you can see, the role of this file is mostly to just be a shim
2413
// to call into the code that lives in the internal shared library.
2514
bootc_lib::cli::run_from_iter(std::env::args()).await

contrib/packaging/bootc.spec

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,14 @@ Provides: ostree-cli(ostree-container)
6262
%description
6363
%{summary}
6464

65+
%package reinstall
66+
Summary: Utility to reinstall the current system using bootc
67+
Requires: podman
68+
# The reinstall subpackage intentionally does not require bootc, as it pulls in many unnecessary dependencies
69+
70+
%description reinstall
71+
This package provides a utility to simplify reinstalling the current system to a given bootc image.
72+
6573
%prep
6674
%autosetup -p1 -a1
6775
# Default -v vendor config doesn't support non-crates.io deps (i.e. git)
@@ -71,12 +79,17 @@ cat vendor-config.toml >> .cargo/config.toml
7179
rm vendor-config.toml
7280

7381
%build
82+
# Build the main bootc binary
7483
%if 0%{?fedora} || 0%{?rhel} >= 10
7584
%cargo_build %{?with_rhsm:-f rhsm}
7685
%else
7786
%cargo_build %{?with_rhsm:--features rhsm}
7887
%endif
7988

89+
# Build the system reinstallation CLI binary
90+
%global cargo_args -p system-reinstall-bootc
91+
%cargo_build
92+
8093
%cargo_vendor_manifest
8194
# https://pagure.io/fedora-rust/rust-packaging/issue/33
8295
sed -i -e '/https:\/\//d' cargo-vendor.txt
@@ -110,5 +123,8 @@ make install-ostree-hooks DESTDIR=%{?buildroot}
110123
%{_docdir}/bootc/*
111124
%{_mandir}/man*/bootc*
112125

126+
%files reinstall
127+
%{_bindir}/system-reinstall-bootc
128+
113129
%changelog
114130
%autochangelog

system-reinstall-bootc/Cargo.toml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
[package]
2+
name = "system-reinstall-bootc"
3+
version = "0.1.9"
4+
edition = "2021"
5+
license = "MIT OR Apache-2.0"
6+
repository = "https://github.com/containers/bootc"
7+
readme = "README.md"
8+
publish = false
9+
# For now don't bump this above what is currently shipped in RHEL9.
10+
rust-version = "1.75.0"
11+
12+
# See https://github.com/coreos/cargo-vendor-filterer
13+
[package.metadata.vendor-filter]
14+
# For now we only care about tier 1+2 Linux. (In practice, it's unlikely there is a tier3-only Linux dependency)
15+
platforms = ["*-unknown-linux-gnu"]
16+
17+
[dependencies]
18+
anyhow = { workspace = true }
19+
bootc-utils = { path = "../utils" }
20+
clap = { workspace = true, features = ["derive"] }
21+
dialoguer = "0.11.0"
22+
log = "0.4.21"
23+
rustix = { workspace = true }
24+
serde = { workspace = true, features = ["derive"] }
25+
serde_json = { workspace = true }
26+
serde_yaml = "0.9.22"
27+
tracing = { workspace = true }
28+
uzers = "0.12.1"
29+
30+
[lints]
31+
workspace = true
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# The bootc container image to install
2+
bootc_image: quay.io/fedora/fedora-bootc:41
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
use clap::Parser;
2+
3+
#[derive(Parser)]
4+
pub(crate) struct Cli {
5+
/// The bootc container image to install, e.g. quay.io/fedora/fedora-bootc:41
6+
pub(crate) bootc_image: String,
7+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
use anyhow::{ensure, Context, Result};
2+
use clap::Parser;
3+
use serde::{Deserialize, Serialize};
4+
5+
mod cli;
6+
7+
#[derive(Debug, Deserialize, Serialize)]
8+
#[serde(deny_unknown_fields)]
9+
pub(crate) struct ReinstallConfig {
10+
/// The bootc image to install on the system.
11+
pub(crate) bootc_image: String,
12+
13+
/// The raw CLI arguments that were used to invoke the program. None if the config was loaded
14+
/// from a file.
15+
#[serde(skip_deserializing)]
16+
cli_flags: Option<Vec<String>>,
17+
}
18+
19+
impl ReinstallConfig {
20+
pub fn parse_from_cli(cli: cli::Cli) -> Self {
21+
Self {
22+
bootc_image: cli.bootc_image,
23+
cli_flags: Some(std::env::args().collect::<Vec<String>>()),
24+
}
25+
}
26+
27+
pub fn load() -> Result<Self> {
28+
Ok(match std::env::var("BOOTC_REINSTALL_CONFIG") {
29+
Ok(config_path) => {
30+
ensure_no_cli_args()?;
31+
32+
serde_yaml::from_slice(
33+
&std::fs::read(&config_path)
34+
.context("reading BOOTC_REINSTALL_CONFIG file {config_path}")?,
35+
)
36+
.context("parsing BOOTC_REINSTALL_CONFIG file {config_path}")?
37+
}
38+
Err(_) => ReinstallConfig::parse_from_cli(cli::Cli::parse()),
39+
})
40+
}
41+
}
42+
43+
fn ensure_no_cli_args() -> Result<()> {
44+
let num_args = std::env::args().len();
45+
46+
ensure!(
47+
num_args == 1,
48+
"BOOTC_REINSTALL_CONFIG is set, but there are {num_args} CLI arguments. BOOTC_REINSTALL_CONFIG is meant to be used with no arguments."
49+
);
50+
51+
Ok(())
52+
}

system-reinstall-bootc/src/main.rs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
//! The main entrypoint for the bootc system reinstallation CLI
2+
3+
use anyhow::{ensure, Context, Result};
4+
use bootc_utils::CommandRunExt;
5+
use rustix::process::getuid;
6+
7+
mod config;
8+
mod podman;
9+
mod prompt;
10+
pub(crate) mod users;
11+
12+
const ROOT_KEY_MOUNT_POINT: &str = "/bootc_authorized_ssh_keys/root";
13+
14+
fn run() -> Result<()> {
15+
bootc_utils::initialize_tracing();
16+
tracing::trace!("starting {}", env!("CARGO_PKG_NAME"));
17+
18+
// Rootless podman is not supported by bootc
19+
ensure!(getuid().is_root(), "Must run as the root user");
20+
21+
let config = config::ReinstallConfig::load().context("loading config")?;
22+
23+
let mut reinstall_podman_command =
24+
podman::command(&config.bootc_image, &prompt::get_root_key()?);
25+
26+
println!();
27+
28+
println!("Going to run command {:?}", reinstall_podman_command);
29+
30+
prompt::temporary_developer_protection_prompt()?;
31+
32+
reinstall_podman_command
33+
.run_with_cmd_context()
34+
.context("running reinstall command")?;
35+
36+
Ok(())
37+
}
38+
39+
fn main() {
40+
// In order to print the error in a custom format (with :#) our
41+
// main simply invokes a run() where all the work is done.
42+
// This code just captures any errors.
43+
if let Err(e) = run() {
44+
tracing::error!("{:#}", e);
45+
std::process::exit(1);
46+
}
47+
}

0 commit comments

Comments
 (0)