From b831efbe7378175583417e13a788459f43e4c28e Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Fri, 15 Dec 2023 10:04:08 -0500 Subject: [PATCH] install: Rename `install` -> `install to-disk`, peer with `to-filesystem` Take the current `install` verb and change it to be `bootc install to-disk`, and then `install-to-filesystem` becomes `bootc install to-filesystem` - they are obvious peers. The main motivation here is that in the end many use cases will want nontrivial filesystem customization, and while I like having a fully opinionated builtin flow to write to a target disk, we should really think of `to-filesystem` as a fully equal peer of `to-disk`. Signed-off-by: Colin Walters --- .github/workflows/ci.yml | 2 +- docs/index.md | 11 ++++++++--- docs/install.md | 35 +++++++++++++++++------------------ lib/src/cli.rs | 28 +++++++++++++++++++--------- lib/src/install.rs | 12 ++++++------ lib/src/install/baseline.rs | 2 +- lib/src/privtests.rs | 2 +- tests/kolainst/install | 4 ++-- 8 files changed, 55 insertions(+), 41 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 29cd28128..6dda696b4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -136,7 +136,7 @@ jobs: run: | set -xeuo pipefail sudo podman run --rm -ti --privileged -v /:/target -v ./usr/bin/bootc:/usr/bin/bootc --pid=host --security-opt label=disable \ - quay.io/centos-bootc/fedora-bootc-dev:eln bootc install-to-filesystem --target-no-signature-verification \ + quay.io/centos-bootc/fedora-bootc-dev:eln bootc install to-filesystem --target-no-signature-verification \ --karg=foo=bar --disable-selinux --replace=alongside /target ls -al /boot/loader/ sudo grep foo=bar /boot/loader/entries/*.conf diff --git a/docs/index.md b/docs/index.md index 1f8068b7e..15d49e6bc 100644 --- a/docs/index.md +++ b/docs/index.md @@ -89,10 +89,15 @@ First, build a derived container using any container build tooling. #### Using `bootc install` -The `bootc install` command will write the current container to a disk, and set it up for booting. +The `bootc install` command has two high level sub-commands; `to-disk` and `to-filesystem`. + +The `bootc install to-disk` handles basically everything in taking the current container +and writing it to a disk, and set it up for booting and future in-place upgrades. + In brief, the idea is that every container image shipping `bootc` also comes with a simple -installer that can set a system up to boot from it. Crucially, if you create a -*derivative* container image from a stock OS container image, it also automatically supports `bootc install`. +installer that can set a system up to boot from it. Crucially, if you create a +*derivative* container image from a stock OS container image, it also automatically +supports `bootc install`. For more information, please see [install.md](install.md). diff --git a/docs/install.md b/docs/install.md index 8277288c6..8dcad616d 100644 --- a/docs/install.md +++ b/docs/install.md @@ -17,13 +17,15 @@ The Linux kernel (and optionally initramfs) is embedded in the container image; is `/usr/lib/modules/$kver/vmlinuz`, and the initramfs should be in `initramfs.img` in that directory. -The `bootc install` and `boot install-to-filesystem` commands bridge the two worlds -of a standard, runnable OCI image and a bootable system by running tooling logic embedded +The `bootc install` command bridges the two worlds of a standard, runnable OCI image +and a bootable system by running tooling logic embedded in the container image to create the filesystem and bootloader setup dynamically. This requires running the container via `--privileged`; it uses the running Linux kernel on the host to write the file content from the running container image; not the kernel inside the container. +There are two sub-commands: `bootc install to-disk` and `boot install to-filesystem`. + However, nothing *else* (external) is required to perform a basic installation to disk. (The one exception to host requirements today is that the host must have `skopeo` installed. This is a bug; more information in @@ -44,8 +46,8 @@ image comes with a basic installer. ## Executing `bootc install` The two installation commands allow you to install the container image -either directly to a block device (`bootc install`) or to an existing -filesystem (`bootc install-to-filesystem`). +either directly to a block device (`bootc install to-disk`) or to an existing +filesystem (`bootc install to-filesystem`). The installation commands **MUST** be run **from** the container image that will be installed, using `--privileged` and a few @@ -56,7 +58,7 @@ to an existing system and install your container image. Failure to run Here's an example of using `bootc install` (root/elevated permission required): ```bash -podman run --rm --privileged --pid=host --security-opt label=type:unconfined_t bootc install --target-no-signature-verification /path/to/disk +podman run --rm --privileged --pid=host --security-opt label=type:unconfined_t bootc install to-disk --target-no-signature-verification /path/to/disk ``` Note that while `--privileged` is used, this command will not perform any @@ -68,7 +70,7 @@ The `--pid=host --security-opt label=type:unconfined_t` today make it more convenient for bootc to perform some privileged operations; in the future these requirement may be dropped. -Jump to the section for [`install-to-filesystem`](#more-advanced-installation) later +Jump to the section for [`install to-filesystem`](#more-advanced-installation) later in this document for additional information about that method. ### "day 2" updates, security and fetch configuration @@ -181,16 +183,16 @@ the files are underneath `/usr`. To rotate or change the set of keys, one would build a new container image. Client systems using `bootc upgrade` will transactionally update to this new system state. -## More advanced installation +## More advanced installation with `to-filesystem` -The basic `bootc install` logic is really a pretty small (but opinionated) wrapper +The basic `bootc install to-disk` logic is really a pretty small (but opinionated) wrapper for a set of lower level tools that can also be invoked independently. -The `bootc install` command is effectively: +The `bootc install to-disk` command is effectively: - `mkfs.$fs /dev/disk` - `mount /dev/disk /mnt` -- `bootc install-to-filesystem --karg=root=UUID= --imgref $self /mnt` +- `bootc install to-filesystem --karg=root=UUID= --imgref $self /mnt` There may be a bit more involved here; for example configuring `--block-setup tpm2-luks` will configure the root filesystem @@ -199,25 +201,22 @@ with LUKS bound to the TPM2 chip, currently via [systemd-cryptenroll](https://ww Some OS/distributions may not want to enable it at all; it can be configured off at build time via Cargo features. -### Using `bootc install-to-filesystem` - -As noted above, there is also `bootc install-to-filesystem`, which allows -an arbitrary process to create the root filesystem. +### Using `bootc install to-filesystem` The usual expected way for an external storage system to work is to provide `root=` type kernel arguments. At the current time a separate `/boot` filesystem is also required (mainly to enable LUKS) so you will also need to provide e.g. `--boot-mount-spec UUID=...`. -The `bootc install-to-filesystem` command allows an operating +The `bootc install to-filesystem` command allows an operating system or distribution to ship a separate installer that creates more complex block storage or filesystem setups, but reuses the "top half" of the logic. For example, a goal is to change [Anaconda](https://github.com/rhinstaller/anaconda/) to use this. -### Using `bootc install-to-filesystem --replace=alongside` +### Using `bootc install to-filesystem --replace=alongside` -This is a variant of `install-to-filesystem`, which maximizes convenience for using +This is a variant of `install to-filesystem`, which maximizes convenience for using an existing Linux system, converting it into the target container image. Note that the `/boot` (and `/boot/efi`) partitions *will be reinitialized* - so this is a somewhat destructive operation for the existing Linux installation. @@ -231,7 +230,7 @@ The core command should look like this (root/elevated permission required): podman run --rm --privileged -v /:/target \ --pid=host --security-opt label=type:unconfined_t \ \ - bootc install-to-filesystem --replace=alongside /target + bootc install to-filesystem --replace=alongside /target ``` At the current time, leftover data in `/` is **NOT** automatically cleaned up. This can diff --git a/lib/src/cli.rs b/lib/src/cli.rs index eaf724153..215feef06 100644 --- a/lib/src/cli.rs +++ b/lib/src/cli.rs @@ -95,6 +95,16 @@ pub(crate) struct StatusOpts { pub(crate) booted: bool, } +/// Options for internal testing +#[cfg(feature = "install")] +#[derive(Debug, clap::Subcommand)] +pub(crate) enum InstallOpts { + /// Install to the target block device + ToDisk(crate::install::InstallToDiskOpts), + /// Install to the target filesystem + ToFilesystem(crate::install::InstallToFilesystemOpts), +} + /// Options for man page generation #[derive(Debug, Parser)] pub(crate) struct ManOpts { @@ -112,7 +122,7 @@ pub(crate) enum TestingOpts { RunContainerIntegration {}, /// Block device setup for testing PrepTestInstallFilesystem { blockdev: Utf8PathBuf }, - /// e2e test of install-to-filesystem + /// e2e test of install to-filesystem TestInstallFilesystem { image: String, blockdev: Utf8PathBuf, @@ -150,17 +160,16 @@ pub(crate) enum Opt { /// Add a transient writable overlayfs on `/usr` that will be discarded on reboot. #[clap(alias = "usroverlay")] UsrOverlay, - /// Install to the target block device + /// Install the running container to a target + #[clap(subcommand)] #[cfg(feature = "install")] - Install(crate::install::InstallOpts), + Install(InstallOpts), /// Execute the given command in the host mount namespace #[cfg(feature = "install")] #[clap(hide = true)] #[command(external_subcommand)] ExecInHostMountNamespace(Vec), - /// Install to the target filesystem. - #[cfg(feature = "install")] - InstallToFilesystem(crate::install::InstallToFilesystemOpts), + /// Internal integration testing helpers. #[clap(hide(true), subcommand)] #[cfg(feature = "internal-testing-api")] @@ -454,9 +463,10 @@ async fn run_from_opt(opt: Opt) -> Result<()> { Opt::Edit(opts) => edit(opts).await, Opt::UsrOverlay => usroverlay().await, #[cfg(feature = "install")] - Opt::Install(opts) => crate::install::install(opts).await, - #[cfg(feature = "install")] - Opt::InstallToFilesystem(opts) => crate::install::install_to_filesystem(opts).await, + Opt::Install(opts) => match opts { + InstallOpts::ToDisk(opts) => crate::install::install_to_disk(opts).await, + InstallOpts::ToFilesystem(opts) => crate::install::install_to_filesystem(opts).await, + }, #[cfg(feature = "install")] Opt::ExecInHostMountNamespace(args) => { crate::install::exec_in_host_mountns(args.as_slice()) diff --git a/lib/src/install.rs b/lib/src/install.rs index ccf20f19c..3fb80eb92 100644 --- a/lib/src/install.rs +++ b/lib/src/install.rs @@ -2,7 +2,7 @@ //! //! This module supports installing a bootc-compatible image to //! a block device directly via the `install` verb, or to an externally -//! set up filesystem via `install-to-filesystem`. +//! set up filesystem via `install to-filesystem`. // This sub-module is the "basic" installer that handles creating basic block device // and filesystem setup. @@ -118,7 +118,7 @@ pub(crate) struct InstallConfigOpts { /// Perform an installation to a block device. #[derive(Debug, Clone, clap::Parser, Serialize, Deserialize)] -pub(crate) struct InstallOpts { +pub(crate) struct InstallToDiskOpts { #[clap(flatten)] #[serde(flatten)] pub(crate) block_opts: InstallBlockDeviceOpts, @@ -1021,8 +1021,8 @@ fn installation_complete() { println!("Installation complete!"); } -/// Implementation of the `bootc install` CLI command. -pub(crate) async fn install(opts: InstallOpts) -> Result<()> { +/// Implementation of the `bootc install to-disk` CLI command. +pub(crate) async fn install_to_disk(opts: InstallToDiskOpts) -> Result<()> { let block_opts = opts.block_opts; let state = prepare_install(opts.config_opts, opts.target_opts).await?; @@ -1113,7 +1113,7 @@ fn clean_boot_directories(rootfs: &Dir) -> Result<()> { Ok(()) } -/// Implementation of the `bootc install-to-filsystem` CLI command. +/// Implementation of the `bootc install to-filsystem` CLI command. pub(crate) async fn install_to_filesystem(opts: InstallToFilesystemOpts) -> Result<()> { // Gather global state, destructuring the provided options let state = prepare_install(opts.config_opts, opts.target_opts).await?; @@ -1252,7 +1252,7 @@ pub(crate) async fn install_to_filesystem(opts: InstallToFilesystemOpts) -> Resu #[test] fn install_opts_serializable() { - let c: InstallOpts = serde_json::from_value(serde_json::json!({ + let c: InstallToDiskOpts = serde_json::from_value(serde_json::json!({ "device": "/dev/vda" })) .unwrap(); diff --git a/lib/src/install/baseline.rs b/lib/src/install/baseline.rs index 5a0edb7fa..74724c893 100644 --- a/lib/src/install/baseline.rs +++ b/lib/src/install/baseline.rs @@ -3,7 +3,7 @@ //! This module handles creation of simple root filesystem setups. At the current time //! it's very simple - just a direct filesystem (e.g. xfs, ext4, btrfs etc.). It is //! intended to add opinionated handling of TPM2-bound LUKS too. But that's about it; -//! other more complex flows should set things up externally and use `bootc install-to-filesystem`. +//! other more complex flows should set things up externally and use `bootc install to-filesystem`. use std::borrow::Cow; use std::fmt::Display; diff --git a/lib/src/privtests.rs b/lib/src/privtests.rs index 056165385..f85855a28 100644 --- a/lib/src/privtests.rs +++ b/lib/src/privtests.rs @@ -152,7 +152,7 @@ fn test_install_filesystem(image: &str, blockdev: &Utf8Path) -> Result<()> { let mountpoint: &Utf8Path = mountpoint_dir.path().try_into().unwrap(); // And run the install - cmd!(sh, "podman run --rm --privileged --pid=host --env=RUST_LOG -v /usr/bin/bootc:/usr/bin/bootc -v {mountpoint}:/target-root {image} bootc install-to-filesystem --target-no-signature-verification /target-root").run()?; + cmd!(sh, "podman run --rm --privileged --pid=host --env=RUST_LOG -v /usr/bin/bootc:/usr/bin/bootc -v {mountpoint}:/target-root {image} bootc install to-filesystem --target-no-signature-verification /target-root").run()?; cmd!(sh, "umount -R {mountpoint}").run()?; diff --git a/tests/kolainst/install b/tests/kolainst/install index fc9085cab..4bfda0c58 100755 --- a/tests/kolainst/install +++ b/tests/kolainst/install @@ -30,7 +30,7 @@ case "${AUTOPKGTEST_REBOOT_MARK:-}" in EOF podman build -t localhost/testimage . podman run --rm -ti --privileged --pid=host --env RUST_LOG=error,bootc_lib::install=debug \ - localhost/testimage bootc install --target-no-signature-verification --skip-fetch-check --karg=foo=bar ${DEV} + localhost/testimage bootc install to-disk --target-no-signature-verification --skip-fetch-check --karg=foo=bar ${DEV} # In theory we could e.g. wipe the bootloader setup on the primary disk, then reboot; # but for now let's just sanity test that the install command executes. lsblk ${DEV} @@ -41,7 +41,7 @@ EOF umount /var/mnt echo "ok install" - # Now test install-to-filesystem + # Now test install to-filesystem # Wipe the device ls ${DEV}* | tac | xargs wipefs -af # This prepares the device and also runs podman directliy