diff --git a/ci/Dockerfile.fcos b/ci/Dockerfile.fcos index 428304657..9a90e886e 100644 --- a/ci/Dockerfile.fcos +++ b/ci/Dockerfile.fcos @@ -7,4 +7,5 @@ RUN make test-bin-archive FROM quay.io/fedora/fedora-coreos:testing-devel COPY --from=builder /src/target/bootc.tar.zst /tmp +COPY ci/usr usr RUN tar -xvf /tmp/bootc.tar.zst && ostree container commit diff --git a/ci/usr/lib/bootc/install/50-test-kargs.toml b/ci/usr/lib/bootc/install/50-test-kargs.toml new file mode 100644 index 000000000..9eedae0cb --- /dev/null +++ b/ci/usr/lib/bootc/install/50-test-kargs.toml @@ -0,0 +1,2 @@ +[install] +kargs = ["localtestkarg=somevalue", "otherlocalkarg=42"] diff --git a/docs/install.md b/docs/install.md index 9b461b020..6d18b3efa 100644 --- a/docs/install.md +++ b/docs/install.md @@ -96,21 +96,32 @@ in that case you will need to specify `--skip-fetch-check`. ### Operating system install configuration required -The container image **MUST** define its default install configuration. For example, -create `/usr/lib/bootc/install/00-exampleos.toml` with the contents: +The container image **MUST** define its default install configuration. A key choice +that bootc by default leaves up to the operating system image is the root filesystem +type. + +To enable `bootc install` as part of your OS/distribution base image, +create a file named `/usr/lib/bootc/install/00-.toml` with the contents of the form: ```toml [install] root-fs-type = "xfs" ``` -At the current time, `root-fs-type` is the only available configuration option, and it **MUST** be set. +The `root-fs-type` value **MUST** be set. Configuration files found in this directory will be merged, with higher alphanumeric values taking precedence. If for example you are building a derived container image from the above OS, you could create a `50-myos.toml` that sets `root-fs-type = "btrfs"` which will override the prior setting. +Other available options, also under the `[install]` section: + +`kargs`: This allows setting kernel arguments which apply only at the time of `bootc install`. +This option is particularly useful when creating derived/layered images; for example, a cloud +image may want to have its default `console=` set, in contrast with a default base image. +The values in this field are space separated. + ## Installing an "unconfigured" image The bootc project aims to support generic/general-purpose operating diff --git a/lib/Cargo.toml b/lib/Cargo.toml index 766d009e5..2992f69ec 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -34,6 +34,7 @@ regex = "1.7.1" rustix = { "version" = "0.38", features = ["thread", "fs", "system", "process"] } schemars = { version = "0.8.6", features = ["chrono"] } serde = { features = ["derive"], version = "1.0.125" } +serde_ignored = "0.1.9" serde_json = "1.0.64" serde_yaml = "0.9.17" serde_with = ">= 1.9.4, < 2" diff --git a/lib/src/install.rs b/lib/src/install.rs index 0fb81efbc..367b04320 100644 --- a/lib/src/install.rs +++ b/lib/src/install.rs @@ -381,7 +381,10 @@ pub(crate) mod config { #[derive(Debug, Clone, Serialize, Deserialize, Default)] #[serde(rename = "install", rename_all = "kebab-case", deny_unknown_fields)] pub(crate) struct InstallConfiguration { + /// Root filesystem type pub(crate) root_fs_type: Option, + /// Kernel arguments, applied at installation time + pub(crate) kargs: Option>, } impl InstallConfiguration { @@ -392,7 +395,12 @@ pub(crate) mod config { *s = Some(o); } } - mergeopt(&mut self.root_fs_type, other.root_fs_type) + mergeopt(&mut self.root_fs_type, other.root_fs_type); + if let Some(other_kargs) = other.kargs { + self.kargs + .get_or_insert_with(|| Default::default()) + .extend(other_kargs) + } } } @@ -405,10 +413,18 @@ pub(crate) mod config { let mut config: Option = None; for (_name, path) in fragments { let buf = std::fs::read_to_string(&path)?; - let c: InstallConfigurationToplevel = - toml::from_str(&buf).with_context(|| format!("Parsing {path:?}"))?; + let mut unused = std::collections::HashSet::new(); + let de = toml::Deserializer::new(&buf); + let c: InstallConfigurationToplevel = serde_ignored::deserialize(de, |path| { + unused.insert(path.to_string()); + }) + .with_context(|| format!("Parsing {path:?}"))?; + for key in unused { + eprintln!("warning: {path:?}: Unknown key {key}"); + } if let Some(config) = config.as_mut() { if let Some(install) = c.install { + tracing::debug!("Merging install config: {install:?}"); config.merge(install); } } else { @@ -434,10 +450,43 @@ root-fs-type = "xfs" let other = InstallConfigurationToplevel { install: Some(InstallConfiguration { root_fs_type: Some(Filesystem::Ext4), + kargs: None, }), }; install.merge(other.install.unwrap()); assert_eq!(install.root_fs_type.unwrap(), Filesystem::Ext4); + + let c: InstallConfigurationToplevel = toml::from_str( + r##"[install] +root-fs-type = "ext4" +kargs = ["console=ttyS0", "foo=bar"] +"##, + ) + .unwrap(); + let mut install = c.install.unwrap(); + assert_eq!(install.root_fs_type.unwrap(), Filesystem::Ext4); + let other = InstallConfigurationToplevel { + install: Some(InstallConfiguration { + root_fs_type: None, + kargs: Some( + ["console=tty0", "nosmt"] + .into_iter() + .map(ToOwned::to_owned) + .collect(), + ), + }), + }; + install.merge(other.install.unwrap()); + assert_eq!(install.root_fs_type.unwrap(), Filesystem::Ext4); + assert_eq!( + install.kargs, + Some( + ["console=ttyS0", "foo=bar", "console=tty0", "nosmt"] + .into_iter() + .map(ToOwned::to_owned) + .collect() + ) + ) } } diff --git a/lib/src/install/baseline.rs b/lib/src/install/baseline.rs index a69f6190b..5a0edb7fa 100644 --- a/lib/src/install/baseline.rs +++ b/lib/src/install/baseline.rs @@ -350,6 +350,14 @@ pub(crate) fn install_create_rootfs( .into_iter() .flatten() .chain([rootarg, RW_KARG.to_string(), bootarg].into_iter()) + .chain( + state + .install_config + .kargs + .iter() + .flatten() + .map(ToOwned::to_owned), + ) .collect::>(); mount::mount(&rootdev, &rootfs)?; diff --git a/tests/kolainst/install b/tests/kolainst/install index d6731a409..291a4edec 100755 --- a/tests/kolainst/install +++ b/tests/kolainst/install @@ -19,12 +19,24 @@ cd $(mktemp -d) case "${AUTOPKGTEST_REBOOT_MARK:-}" in "") - podman run --rm -ti --privileged --pid=host -v /usr/bin/bootc:/usr/bin/bootc ${IMAGE} bootc install --target-no-signature-verification --karg=foo=bar ${DEV} + mkdir -p ~/.config/containers + cp -a /etc/ostree/auth.json ~/.config/containers + mkdir -p usr/{lib,bin} + cp -a /usr/lib/bootc usr/lib + cp -a /usr/bin/bootc usr/bin + cat > Dockerfile << EOF + FROM ${IMAGE} + COPY usr usr +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} # 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} mount /dev/vda3 /var/mnt grep foo=bar /var/mnt/loader/entries/*.conf + grep localtestkarg=somevalue /var/mnt/loader/entries/*.conf grep -Ee '^linux /boot/ostree' /var/mnt/loader/entries/*.conf umount /var/mnt echo "ok install"