Skip to content

Commit 5140bd4

Browse files
authored
Merge pull request #505 from cgwalters/install-to-filesystem-warning-more
install: Add prominent warning+timeout when targeting host root
2 parents dd09f47 + 2094895 commit 5140bd4

File tree

3 files changed

+53
-10
lines changed

3 files changed

+53
-10
lines changed

.github/workflows/ci.yml

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ jobs:
111111
- name: Install
112112
run: sudo tar -C / -xvf bootc.tar.zst
113113
- name: Integration tests
114-
run: sudo podman run --rm -ti --privileged -v /run/systemd:/run/systemd -v /:/run/host -v /usr/bin/bootc:/usr/bin/bootc --pid=host quay.io/fedora/fedora-coreos:testing-devel bootc internal-tests run-privileged-integration
114+
run: sudo podman run --rm --privileged -v /run/systemd:/run/systemd -v /:/run/host -v /usr/bin/bootc:/usr/bin/bootc --pid=host quay.io/fedora/fedora-coreos:testing-devel bootc internal-tests run-privileged-integration
115115
container-tests:
116116
if: ${{ !contains(github.event.pull_request.labels.*.name, 'control/skip-ci') }}
117117
name: "Container testing"
@@ -146,18 +146,18 @@ jobs:
146146
set -xeuo pipefail
147147
image=quay.io/centos-bootc/centos-bootc-dev:stream9
148148
echo 'ssh-ed25519 ABC0123 [email protected]' > test_authorized_keys
149-
sudo podman run --rm -ti --privileged -v ./test_authorized_keys:/test_authorized_keys --env RUST_LOG=debug -v /:/target -v /var/lib/containers:/var/lib/containers -v ./usr/bin/bootc:/usr/bin/bootc --pid=host --security-opt label=disable \
150-
${image} bootc install to-filesystem \
149+
sudo podman run --rm --privileged -v ./test_authorized_keys:/test_authorized_keys --env RUST_LOG=debug -v /:/target -v /var/lib/containers:/var/lib/containers -v ./usr/bin/bootc:/usr/bin/bootc --pid=host --security-opt label=disable \
150+
${image} bootc install to-filesystem --acknowledge-destructive \
151151
--karg=foo=bar --disable-selinux --replace=alongside --root-ssh-authorized-keys=/test_authorized_keys /target
152152
ls -al /boot/loader/
153153
sudo grep foo=bar /boot/loader/entries/*.conf
154154
grep authorized_keys /ostree/deploy/default/deploy/*/etc/tmpfiles.d/bootc-root-ssh.conf
155155
# TODO fix https://github.com/containers/bootc/pull/137
156156
sudo chattr -i /ostree/deploy/default/deploy/*
157157
sudo rm /ostree/deploy/default -rf
158-
sudo podman run --rm -ti --privileged --env RUST_LOG=debug -v /:/target -v /var/lib/containers:/var/lib/containers -v ./usr/bin/bootc:/usr/bin/bootc --pid=host --security-opt label=disable \
159-
${image} bootc install to-existing-root
160-
sudo podman run --rm -ti --privileged -v /:/target -v ./usr/bin/bootc:/usr/bin/bootc --pid=host --security-opt label=disable ${image} bootc internal-tests verify-selinux /target/ostree --warn
158+
sudo podman run --rm --privileged --env RUST_LOG=debug -v /:/target -v /var/lib/containers:/var/lib/containers -v ./usr/bin/bootc:/usr/bin/bootc --pid=host --security-opt label=disable \
159+
${image} bootc install to-existing-root --acknowledge-destructive
160+
sudo podman run --rm --privileged -v /:/target -v ./usr/bin/bootc:/usr/bin/bootc --pid=host --security-opt label=disable ${image} bootc internal-tests verify-selinux /target/ostree --warn
161161
install-to-existing-root:
162162
if: ${{ !contains(github.event.pull_request.labels.*.name, 'control/skip-ci') }}
163163
name: "Test install-to-existing-root"
@@ -177,7 +177,7 @@ jobs:
177177
# so we bind mount an empty directory over /usr/lib/bootc/install.
178178
empty=$(mktemp -d)
179179
image=quay.io/centos-bootc/centos-bootc-dev:stream9
180-
sudo podman run --rm -ti --privileged --env RUST_LOG=debug -v /:/target -v /var/lib/containers:/var/lib/containers -v ./usr/bin/bootc:/usr/bin/bootc -v ${empty}:/usr/lib/bootc/install --pid=host --security-opt label=disable \
180+
sudo podman run --rm --privileged --env RUST_LOG=debug -v /:/target -v /var/lib/containers:/var/lib/containers -v ./usr/bin/bootc:/usr/bin/bootc -v ${empty}:/usr/lib/bootc/install --pid=host --security-opt label=disable \
181181
${image} bootc install to-existing-root
182182
install-to-loopback:
183183
if: ${{ !contains(github.event.pull_request.labels.*.name, 'control/skip-ci') }}
@@ -197,5 +197,5 @@ jobs:
197197
image=quay.io/centos-bootc/centos-bootc-dev:stream9
198198
tmpdisk=$(mktemp -p /var/tmp)
199199
truncate -s 20G ${tmpdisk}
200-
sudo podman run --rm -ti --privileged --env RUST_LOG=debug -v /dev:/dev -v /:/target -v /var/lib/containers:/var/lib/containers -v ./usr/bin/bootc:/usr/bin/bootc --pid=host --security-opt label=disable \
200+
sudo podman run --rm --privileged --env RUST_LOG=debug -v /dev:/dev -v /:/target -v /var/lib/containers:/var/lib/containers -v ./usr/bin/bootc:/usr/bin/bootc --pid=host --security-opt label=disable \
201201
-v ${tmpdisk}:/disk ${image} bootc install to-disk --via-loopback /disk

lib/src/install.rs

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ use std::path::Path;
1717
use std::process::Command;
1818
use std::str::FromStr;
1919
use std::sync::Arc;
20+
use std::time::Duration;
2021

2122
use anyhow::Ok;
2223
use anyhow::{anyhow, Context, Result};
@@ -230,6 +231,10 @@ pub(crate) struct InstallTargetFilesystemOpts {
230231
#[clap(long)]
231232
pub(crate) replace: Option<ReplaceMode>,
232233

234+
/// If the target is the running system's root filesystem, this will skip any warnings.
235+
#[clap(long)]
236+
pub(crate) acknowledge_destructive: bool,
237+
233238
/// The default mode is to "finalize" the target filesystem by invoking `fstrim` and similar
234239
/// operations, and finally mounting it readonly. This option skips those operations. It
235240
/// is then the responsibility of the invoking code to perform those operations.
@@ -269,6 +274,10 @@ pub(crate) struct InstallToExistingRootOpts {
269274
#[clap(flatten)]
270275
pub(crate) config_opts: InstallConfigOpts,
271276

277+
/// Accept that this is a destructive action and skip a warning timer.
278+
#[clap(long)]
279+
pub(crate) acknowledge_destructive: bool,
280+
272281
/// Path to the mounted root; it's expected to invoke podman with
273282
/// `-v /:/target`, then supplying this argument is unnecessary.
274283
#[clap(default_value = "/target")]
@@ -1373,6 +1382,34 @@ fn find_root_args_to_inherit(cmdline: &[&str], root_info: &Filesystem) -> Result
13731382
Ok(RootMountInfo { mount_spec, kargs })
13741383
}
13751384

1385+
fn warn_on_host_root(rootfs_fd: &Dir) -> Result<()> {
1386+
// Seconds for which we wait while warning
1387+
const DELAY_SECONDS: u64 = 20;
1388+
1389+
let host_root_dfd = &Dir::open_ambient_dir("/proc/1/root", cap_std::ambient_authority())?;
1390+
let host_root_devstat = rustix::fs::fstatvfs(host_root_dfd)?;
1391+
let target_devstat = rustix::fs::fstatvfs(rootfs_fd)?;
1392+
if host_root_devstat.f_fsid != target_devstat.f_fsid {
1393+
tracing::debug!("Not the host root");
1394+
return Ok(());
1395+
}
1396+
let dashes = "----------------------------";
1397+
let timeout = Duration::from_secs(DELAY_SECONDS);
1398+
eprintln!("{dashes}");
1399+
crate::utils::medium_visibility_warning(
1400+
"WARNING: This operation will OVERWRITE THE BOOTED HOST ROOT FILESYSTEM and is NOT REVERSIBLE.",
1401+
);
1402+
eprintln!("Waiting {timeout:?} to continue; interrupt (Control-C) to cancel.");
1403+
eprintln!("{dashes}");
1404+
1405+
let bar = indicatif::ProgressBar::new_spinner();
1406+
bar.enable_steady_tick(Duration::from_millis(100));
1407+
std::thread::sleep(timeout);
1408+
bar.finish();
1409+
1410+
Ok(())
1411+
}
1412+
13761413
/// Implementation of the `bootc install to-filsystem` CLI command.
13771414
#[context("Installing to filesystem")]
13781415
pub(crate) async fn install_to_filesystem(
@@ -1391,7 +1428,12 @@ pub(crate) async fn install_to_filesystem(
13911428
let rootfs_fd = Dir::open_ambient_dir(root_path, cap_std::ambient_authority())
13921429
.with_context(|| format!("Opening target root directory {root_path}"))?;
13931430
if let Some(false) = ostree_ext::mountutil::is_mountpoint(&rootfs_fd, ".")? {
1394-
anyhow::bail!("Not a root mountpoint: {root_path}");
1431+
anyhow::bail!("Not a mountpoint: {root_path}");
1432+
}
1433+
1434+
// Check to see if this happens to be the real host root
1435+
if !fsopts.acknowledge_destructive {
1436+
warn_on_host_root(&rootfs_fd)?;
13951437
}
13961438

13971439
// Gather global state, destructuring the provided options
@@ -1554,6 +1596,7 @@ pub(crate) async fn install_to_existing_root(opts: InstallToExistingRootOpts) ->
15541596
boot_mount_spec: None,
15551597
replace: opts.replace,
15561598
skip_finalize: true,
1599+
acknowledge_destructive: opts.acknowledge_destructive,
15571600
},
15581601
source_opts: opts.source_opts,
15591602
target_opts: opts.target_opts,

tests/kolainst/install

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ case "${AUTOPKGTEST_REBOOT_MARK:-}" in
2929
COPY usr usr
3030
EOF
3131
podman build -t localhost/testimage .
32-
podman run --rm -ti --privileged --pid=host --env RUST_LOG=error,bootc_lib::install=debug \
32+
podman run --rm --privileged --pid=host --env RUST_LOG=error,bootc_lib::install=debug \
3333
localhost/testimage bootc install to-disk --skip-fetch-check --karg=foo=bar ${DEV}
3434
# In theory we could e.g. wipe the bootloader setup on the primary disk, then reboot;
3535
# but for now let's just sanity test that the install command executes.

0 commit comments

Comments
 (0)