- 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
462 lines
13 KiB
Markdown
462 lines
13 KiB
Markdown
# 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
|
|
|
|
```rust
|
|
// 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 metadata
|
|
- `selinux_state`: SELinux configuration state
|
|
- `target_imgref`: Target image reference for updates
|
|
- `prepareroot_config`: OSTree prepareroot configuration
|
|
- `install_config`: Installation-specific configuration
|
|
|
|
### 2. Source Information
|
|
|
|
```rust
|
|
// 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 reference
|
|
- `digest`: Image digest for verification
|
|
- `selinux`: SELinux policy presence
|
|
- `in_host_mountns`: Host mount namespace access
|
|
|
|
### 3. Root Setup
|
|
|
|
```rust
|
|
// 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 information
|
|
- `physical_root`: Target filesystem directory
|
|
- `rootfs_uuid`: Root filesystem UUID
|
|
- `boot`: Boot filesystem mount specification
|
|
- `kargs`: Kernel arguments
|
|
|
|
## Installation Flow Analysis
|
|
|
|
### 1. Preparation Phase
|
|
|
|
#### Container Environment Detection
|
|
```rust
|
|
// 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**:
|
|
1. Validate podman runtime
|
|
2. Extract image reference from container environment
|
|
3. Resolve image digest
|
|
4. Detect SELinux policy presence
|
|
|
|
#### Privilege Validation
|
|
```rust
|
|
// 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**:
|
|
1. Check process ID (not PID 1)
|
|
2. Verify host user namespace access
|
|
3. Validate root privileges
|
|
|
|
### 2. OSTree Initialization
|
|
|
|
#### Repository Setup
|
|
```rust
|
|
// 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**:
|
|
1. Check for existing OSTree repository
|
|
2. Initialize repository if needed
|
|
3. Configure repository settings
|
|
4. Set up stateroot
|
|
5. Configure SELinux labeling
|
|
|
|
#### Repository Configuration
|
|
```rust
|
|
// 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=none`
|
|
- `sysroot.bootprefix=true`
|
|
- `sysroot.readonly=true`
|
|
|
|
### 3. Container Image Deployment
|
|
|
|
#### Image Pulling
|
|
```rust
|
|
// 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**:
|
|
1. Prepare image for pulling
|
|
2. Check disk space requirements
|
|
3. Pull image into OSTree repository
|
|
4. Handle already present images
|
|
|
|
#### Deployment
|
|
```rust
|
|
// 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
|
|
```rust
|
|
// 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
|
|
```rust
|
|
// 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)**:
|
|
1. Root filesystem kargs
|
|
2. Install configuration kargs
|
|
3. Container image kargs.d files
|
|
4. CLI-specified kargs
|
|
|
|
### 6. Filesystem Finalization
|
|
|
|
#### Optimization Process
|
|
```rust
|
|
// 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**:
|
|
1. Trim filesystem (fstrim)
|
|
2. Remount read-only
|
|
3. Freeze and thaw filesystem
|
|
4. Flush journal
|
|
|
|
## Error Handling Patterns
|
|
|
|
### 1. Contextual Error Handling
|
|
```rust
|
|
// 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
|
|
```rust
|
|
// 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
|
|
```rust
|
|
// 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
|
|
```rust
|
|
// 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
|
|
```rust
|
|
// 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.
|