- Complete documentation for all bootc commands and subcommands - Debian-specific adaptations and workarounds - Manual installation methods to bypass bootc reliability issues - Technical guides with Rust source code analysis - Flowcharts and external command references - Hidden command documentation (bootc internals, state, etc.) - Composefs integration analysis - Base image creation guides (with and without bootc binary) - Management scripts and automation - Comprehensive troubleshooting and examples
13 KiB
13 KiB
bootc Installation Source Code Analysis
Overview
This document provides a detailed analysis of the bootc installation process based on the source code examination. The analysis covers the key components, data structures, and execution flow of the installation system.
Core Data Structures
1. State Management
// From install.rs:373-390
pub(crate) struct State {
pub(crate) source: SourceInfo,
pub(crate) selinux_state: SELinuxFinalState,
pub(crate) config_opts: InstallConfigOpts,
pub(crate) target_imgref: ostree_container::OstreeImageReference,
pub(crate) prepareroot_config: HashMap<String, String>,
pub(crate) install_config: Option<config::InstallConfiguration>,
pub(crate) root_ssh_authorized_keys: Option<String>,
pub(crate) host_is_container: bool,
pub(crate) container_root: Dir,
pub(crate) tempdir: TempDir,
}
Key Components:
source: Container image information and metadataselinux_state: SELinux configuration statetarget_imgref: Target image reference for updatesprepareroot_config: OSTree prepareroot configurationinstall_config: Installation-specific configuration
2. Source Information
// From install.rs:360-370
pub(crate) struct SourceInfo {
pub(crate) imageref: ostree_container::ImageReference,
pub(crate) digest: Option<String>,
pub(crate) selinux: bool,
pub(crate) in_host_mountns: bool,
}
Purpose: Encapsulates information about the source container image Key Fields:
imageref: Container image referencedigest: Image digest for verificationselinux: SELinux policy presencein_host_mountns: Host mount namespace access
3. Root Setup
// From install.rs:928-942
pub(crate) struct RootSetup {
#[cfg(feature = "install-to-disk")]
luks_device: Option<String>,
device_info: bootc_blockdev::PartitionTable,
physical_root_path: Utf8PathBuf,
physical_root: Dir,
rootfs_uuid: Option<String>,
skip_finalize: bool,
boot: Option<MountSpec>,
kargs: Vec<String>,
}
Purpose: Manages target filesystem setup and configuration Key Fields:
device_info: Partition table informationphysical_root: Target filesystem directoryrootfs_uuid: Root filesystem UUIDboot: Boot filesystem mount specificationkargs: Kernel arguments
Installation Flow Analysis
1. Preparation Phase
Container Environment Detection
// From install.rs:520-538
pub(crate) fn from_container(
root: &Dir,
container_info: &ContainerExecutionInfo,
) -> Result<Self> {
if !container_info.engine.starts_with("podman") {
anyhow::bail!("Currently this command only supports being executed via podman");
}
if container_info.imageid.is_empty() {
anyhow::bail!("Invalid empty imageid");
}
let imageref = ostree_container::ImageReference {
transport: ostree_container::Transport::ContainerStorage,
name: container_info.image.clone(),
};
let digest = crate::podman::imageid_to_digest(&container_info.imageid)?;
Self::new(imageref, Some(digest), root, true)
}
Key Operations:
- Validate podman runtime
- Extract image reference from container environment
- Resolve image digest
- Detect SELinux policy presence
Privilege Validation
// From install.rs:1061-1082
fn require_host_pidns() -> Result<()> {
if rustix::process::getpid().is_init() {
anyhow::bail!("This command must be run with the podman --pid=host flag")
}
tracing::trace!("OK: we're not pid 1");
Ok(())
}
fn require_host_userns() -> Result<()> {
let proc1 = "/proc/1";
let pid1_uid = Path::new(proc1)
.metadata()
.with_context(|| format!("Querying {proc1}"))?
.uid();
ensure!(pid1_uid == 0, "{proc1} is owned by {pid1_uid}, not zero; this command must be run in the root user namespace");
tracing::trace!("OK: we're in a matching user namespace with pid1");
Ok(())
}
Validation Steps:
- Check process ID (not PID 1)
- Verify host user namespace access
- Validate root privileges
2. OSTree Initialization
Repository Setup
// From install.rs:591-694
async fn initialize_ostree_root(state: &State, root_setup: &RootSetup) -> Result<(Storage, bool)> {
let sepolicy = state.load_policy()?;
let sepolicy = sepolicy.as_ref();
let rootfs_dir = &root_setup.physical_root;
let cancellable = gio::Cancellable::NONE;
let stateroot = state.stateroot();
let has_ostree = rootfs_dir.try_exists("ostree/repo")?;
if !has_ostree {
Task::new("Initializing ostree layout", "ostree")
.args(["admin", "init-fs", "--modern", "."])
.cwd(rootfs_dir)?
.run()?;
} else {
println!("Reusing extant ostree layout");
let path = ".".into();
let _ = crate::utils::open_dir_remount_rw(rootfs_dir, path)
.context("remounting target as read-write")?;
crate::utils::remove_immutability(rootfs_dir, path)?;
}
// ... repository configuration
}
Key Operations:
- Check for existing OSTree repository
- Initialize repository if needed
- Configure repository settings
- Set up stateroot
- Configure SELinux labeling
Repository Configuration
// From install.rs:624-629
for (k, v) in DEFAULT_REPO_CONFIG.iter() {
Command::new("ostree")
.args(["config", "--repo", "ostree/repo", "set", k, v])
.cwd_dir(rootfs_dir.try_clone()?)
.run_capture_stderr()?;
}
Default Configuration:
sysroot.bootloader=nonesysroot.bootprefix=truesysroot.readonly=true
3. Container Image Deployment
Image Pulling
// From install.rs:755-769
let pulled_image = match prepare_for_pull(repo, &spec_imgref, Some(&state.target_imgref))
.await?
{
PreparedPullResult::AlreadyPresent(existing) => existing,
PreparedPullResult::Ready(image_meta) => {
check_disk_space(root_setup.physical_root.as_fd(), &image_meta, &spec_imgref)?;
pull_from_prepared(&spec_imgref, false, ProgressWriter::default(), *image_meta).await?
}
};
Process:
- Prepare image for pulling
- Check disk space requirements
- Pull image into OSTree repository
- Handle already present images
Deployment
// From install.rs:826-836
let mut options = ostree_container::deploy::DeployOpts::default();
options.kargs = Some(kargs.as_slice());
options.target_imgref = Some(&state.target_imgref);
options.proxy_cfg = proxy_cfg;
options.skip_completion = true;
options.no_clean = has_ostree;
let imgstate = crate::utils::async_task_with_spinner(
"Deploying container image",
ostree_container::deploy::deploy(&sysroot, stateroot, &src_imageref, Some(options)),
)
.await?;
Deployment Options:
- Kernel arguments configuration
- Target image reference
- Proxy configuration
- Completion skipping
- Cleanup control
4. Bootloader Installation
Bootupd Integration
// From install.rs:1337-1343
crate::bootloader::install_via_bootupd(
&rootfs.device_info,
&rootfs.physical_root_path,
&state.config_opts,
&deployment_path.as_str(),
)?;
Architecture Support:
- x86_64/aarch64: GRUB2 via bootupd
- s390x: zipl implementation
- Other: Platform-specific
5. Kernel Argument Processing
Argument Collection
// From install.rs:773-825
let kargsd = crate::bootc_kargs::get_kargs_from_ostree_root(
&sysroot.repo(),
merged_ostree_root.downcast_ref().unwrap(),
std::env::consts::ARCH,
)?;
let kargsd = kargsd.iter().map(|s| s.as_str());
let install_config_kargs = state
.install_config
.as_ref()
.and_then(|c| c.kargs.as_ref())
.into_iter()
.flatten()
.map(|s| s.as_str());
let kargs = root_setup
.kargs
.iter()
.map(|v| v.as_str())
.chain(install_config_kargs)
.chain(kargsd)
.chain(state.config_opts.karg.iter().flatten().map(|v| v.as_str()))
.collect::<Vec<_>>();
Argument Sources (Priority Order):
- Root filesystem kargs
- Install configuration kargs
- Container image kargs.d files
- CLI-specified kargs
6. Filesystem Finalization
Optimization Process
// From install.rs:1033-1058
pub(crate) fn finalize_filesystem(
fsname: &str,
root: &Dir,
path: impl AsRef<Utf8Path>,
) -> Result<()> {
let path = path.as_ref();
// fstrim ensures the underlying block device knows about unused space
Task::new(format!("Trimming {fsname}"), "fstrim")
.args(["--quiet-unsupported", "-v", path.as_str()])
.cwd(root)?
.run()?;
// Remounting readonly will flush outstanding writes
Task::new(format!("Finalizing filesystem {fsname}"), "mount")
.cwd(root)?
.args(["-o", "remount,ro", path.as_str()])
.run()?;
// Finally, freezing (and thawing) the filesystem will flush the journal
for a in ["-f", "-u"] {
Command::new("fsfreeze")
.cwd_dir(root.try_clone()?)
.args([a, path.as_str()])
.run_capture_stderr()?;
}
Ok(())
}
Finalization Steps:
- Trim filesystem (fstrim)
- Remount read-only
- Freeze and thaw filesystem
- Flush journal
Error Handling Patterns
1. Contextual Error Handling
// From install.rs:393-404
#[context("Loading SELinux policy")]
pub(crate) fn load_policy(&self) -> Result<Option<ostree::SePolicy>> {
if !self.selinux_state.enabled() {
return Ok(None);
}
let r = lsm::new_sepolicy_at(&self.container_root)?
.ok_or_else(|| anyhow::anyhow!("SELinux enabled, but no policy found in root"))?;
tracing::debug!("Loaded SELinux policy: {}", r.csum().unwrap());
Ok(Some(r))
}
2. Resource Validation
// From install.rs:696-715
fn check_disk_space(
repo_fd: impl AsFd,
image_meta: &PreparedImportMeta,
imgref: &ImageReference,
) -> Result<()> {
let stat = rustix::fs::fstatvfs(repo_fd)?;
let bytes_avail: u64 = stat.f_bsize * stat.f_bavail;
if image_meta.bytes_to_fetch > bytes_avail {
anyhow::bail!(
"Insufficient free space for {image} (available: {bytes_avail} required: {bytes_to_fetch})",
bytes_avail = ostree_ext::glib::format_size(bytes_avail),
bytes_to_fetch = ostree_ext::glib::format_size(image_meta.bytes_to_fetch),
image = imgref.image,
);
}
Ok(())
}
3. Graceful Degradation
// From install.rs:1001-1029
pub(crate) fn reexecute_self_for_selinux_if_needed(
srcdata: &SourceInfo,
override_disable_selinux: bool,
) -> Result<SELinuxFinalState> {
if srcdata.selinux {
let host_selinux = crate::lsm::selinux_enabled()?;
let r = if override_disable_selinux {
println!("notice: Target has SELinux enabled, overriding to disable");
SELinuxFinalState::ForceTargetDisabled
} else if host_selinux {
setup_sys_mount("selinuxfs", SELINUXFS)?;
let g = crate::lsm::selinux_ensure_install_or_setenforce()?;
SELinuxFinalState::Enabled(g)
} else {
SELinuxFinalState::HostDisabled
};
Ok(r)
} else {
Ok(SELinuxFinalState::Disabled)
}
}
Performance Optimizations
1. Async Operations
// From install.rs:1538-1543
let rootfs = tokio::task::spawn_blocking(move || {
baseline::install_create_rootfs(&state, block_opts)
})
.await??;
2. Parallel Processing
- Container image pulling
- Filesystem operations
- Bootloader installation
3. Resource Management
- Temporary directory cleanup
- File descriptor management
- Memory usage optimization
Security Considerations
1. SELinux Integration
- Policy detection and loading
- Context preservation
- Labeling operations
2. Privilege Management
- Minimal privilege requirements
- Capability dropping
- Namespace isolation
3. Input Validation
- Command argument validation
- Path sanitization
- Resource limit enforcement
Testing and Validation
1. Unit Tests
// From install.rs:2064-2153
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn install_opts_serializable() {
let c: InstallToDiskOpts = serde_json::from_value(serde_json::json!({
"device": "/dev/vda"
}))
.unwrap();
assert_eq!(c.block_opts.device, "/dev/vda");
}
}
2. Integration Tests
- End-to-end installation testing
- Error condition testing
- Performance testing
3. Validation Steps
- Container image integrity
- Filesystem compatibility
- Bootloader support
- Kernel compatibility
This analysis provides a comprehensive understanding of the bootc installation process from a source code perspective, covering the key components, execution flow, and implementation details.