- 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
18 KiB
bootc internals - Architecture and Implementation
Overview
This document provides a deep dive into the architecture and implementation details of the bootc internals system, covering the Rust code structure, design patterns, and integration points.
Architecture Overview
The bootc internals system is built on a modular architecture that provides internal-only operations for system maintenance, debugging, and integration. The system is designed to be:
- Hidden: Commands are not visible in regular help output
- Internal: Intended for system administrators and developers
- Modular: Each command is self-contained with clear responsibilities
- Integrated: Seamlessly integrates with existing bootc infrastructure
Core Components
1. Command Structure
The internals system is built around the InternalsOpts enum, which defines all available internal commands:
#[derive(Debug, clap::Subcommand, PartialEq, Eq)]
pub(crate) enum InternalsOpts {
SystemdGenerator {
normal_dir: Utf8PathBuf,
early_dir: Option<Utf8PathBuf>,
late_dir: Option<Utf8PathBuf>,
},
FixupEtcFstab,
PrintJsonSchema {
#[clap(long)]
of: SchemaType,
},
#[clap(subcommand)]
Fsverity(FsverityOpts),
Fsck,
Cleanup,
Relabel {
#[clap(long)]
as_path: Option<Utf8PathBuf>,
path: Utf8PathBuf,
},
OstreeExt { args: Vec<OsString> },
Cfs { args: Vec<OsString> },
OstreeContainer { args: Vec<OsString> },
TestComposefs,
LoopbackCleanupHelper { device: String },
AllocateCleanupLoopback { file_path: Utf8PathBuf },
BootcInstallCompletion { sysroot: Utf8PathBuf, stateroot: String },
Reboot,
#[cfg(feature = "rhsm")]
PublishRhsmFacts,
#[cfg(feature = "docgen")]
DumpCliJson,
DirDiff {
pristine_etc: Utf8PathBuf,
current_etc: Utf8PathBuf,
new_etc: Utf8PathBuf,
perform_merge: bool,
},
}
2. Command Routing
Commands are routed through the main CLI dispatcher in cli.rs:
match opt {
Opt::Internals(opts) => match opts {
InternalsOpts::SystemdGenerator { normal_dir, early_dir, late_dir } => {
let unit_dir = &Dir::open_ambient_dir(normal_dir, cap_std::ambient_authority())?;
crate::generator::generator(root, unit_dir)
}
InternalsOpts::Fsck => {
let sysroot = &get_storage().await?;
crate::fsck::fsck(&sysroot, std::io::stdout().lock()).await?;
Ok(())
}
// ... other commands
}
}
Module Architecture
1. Generator Module (generator.rs)
Purpose: Systemd integration and unit generation
Key Functions:
fstab_generator_impl()- Generate fstab editor servicegenerate_fstab_editor()- Create systemd unit filegenerator()- Main generator entry point
Implementation:
pub(crate) fn fstab_generator_impl(root: &Dir, unit_dir: &Dir) -> Result<bool> {
// Check if system is ostree-booted
if !is_ostree_booted_in(root)? {
return Ok(false);
}
// Check /etc/fstab for anaconda stamp
if let Some(fd) = root.open_optional("etc/fstab")? {
let mut from_anaconda = false;
for line in fd.lines() {
let line = line?;
if line.contains(BOOTC_EDITED_STAMP) {
return Ok(false); // Already processed
}
if line.contains(FSTAB_ANACONDA_STAMP) {
from_anaconda = true;
}
}
if from_anaconda {
generate_fstab_editor(unit_dir)?;
return Ok(true);
}
}
Ok(false)
}
Dependencies:
ostree_ext::container_utils::is_ostree_booted_incap_std::fs::Dirrustix::fs::StatVfsMountFlags
2. Filesystem Check Module (fsck.rs)
Purpose: System consistency checking
Key Functions:
fsck()- Main consistency check entry pointfsck_ok()- Return success resultfsck_err()- Return error result
Implementation:
pub async fn fsck(sysroot: &Storage, mut out: impl Write) -> Result<()> {
let ostree = sysroot.get_ostree()?;
let repo = ostree.repo();
// Run all registered fsck checks
for check in FSCK_CHECKS {
match check(sysroot).await {
Ok(Ok(())) => writeln!(out, "✓ {}", check.name())?,
Ok(Err(e)) => writeln!(out, "✗ {}: {}", check.name(), e)?,
Err(e) => writeln!(out, "✗ {}: {}", check.name(), e)?,
}
}
Ok(())
}
Dependencies:
linkme::distributed_slice- For check registrationostree_ext::composefs- For composefs operationsostree_ext::ostree- For OSTree operations
3. Composefs Control Module (cfsctl.rs)
Purpose: Composefs repository management
Key Functions:
run_from_iter()- Parse and execute cfsctl commandsApp::parse()- Parse command line argumentsCommandenum - Define available commands
Implementation:
pub async fn run_from_iter<I>(args: I) -> Result<()>
where
I: IntoIterator<Item = impl AsRef<OsStr>>,
{
let app = App::parse_from(args);
let repo = if app.user {
Repository::open_user()?
} else if app.system {
Repository::open_system()?
} else if let Some(repo_path) = app.repo {
Repository::open(repo_path)?
} else {
Repository::open_default()?
};
match app.cmd {
Command::Oci(oci_cmd) => match oci_cmd {
OciCommand::ImportLayer { sha256, name } => {
// Import layer implementation
}
OciCommand::LsLayer { name } => {
// List layer implementation
}
// ... other OCI commands
}
Command::CreateFs { name, output } => {
// Create filesystem implementation
}
Command::WriteBoot { name, output } => {
// Write boot entries implementation
}
}
}
Dependencies:
composefs::repository::Repositorycomposefs::fsverity::FsVerityHashValuecomposefs_boot::write_boot
4. Deploy Module (deploy.rs)
Purpose: Deployment operations and cleanup
Key Functions:
fixup_etc_fstab()- Fix /etc/fstab for composefscleanup()- Perform system cleanupswitch_origin_inplace()- In-place origin switching
Implementation:
pub(crate) fn fixup_etc_fstab(root: &Dir) -> Result<()> {
let fstab_path = root.open("etc/fstab")?;
let mut fstab_content = String::new();
fstab_path.read_to_string(&mut fstab_content)?;
let mut lines = fstab_content.lines().collect::<Vec<_>>();
let mut modified = false;
for line in lines.iter_mut() {
if line.contains("ro") && !line.contains("ro,") {
*line = line.replace("ro", "ro,");
modified = true;
}
}
if modified {
let mut fstab_file = root.create("etc/fstab")?;
for line in lines {
writeln!(fstab_file, "{}", line)?;
}
writeln!(fstab_file, "# {}", BOOTC_EDITED_STAMP)?;
}
Ok(())
}
Dependencies:
cap_std::fs::Dirstd::io::Writeostree_ext::ostree
5. Block Device Module (blockdev.rs)
Purpose: Loopback device management
Key Functions:
LoopbackDevice::new()- Create loopback devicerun_loopback_cleanup_helper()- Cleanup helperrun_allocate_cleanup_loopback()- Test allocation
Implementation:
pub struct LoopbackDevice {
device: String,
file: PathBuf,
}
impl LoopbackDevice {
pub fn new(file_path: &Path) -> Result<Self> {
let output = Command::new("losetup")
.args(["-f", "--show", file_path.to_str().unwrap()])
.output()?;
if !output.status.success() {
return Err(anyhow::anyhow!("Failed to create loopback device"));
}
let device = String::from_utf8(output.stdout)?;
let device = device.trim().to_string();
Ok(Self {
device,
file: file_path.to_path_buf(),
})
}
pub fn path(&self) -> &str {
&self.device
}
}
impl Drop for LoopbackDevice {
fn drop(&mut self) {
let _ = Command::new("losetup")
.args(["-d", &self.device])
.output();
}
}
Dependencies:
std::process::Commandstd::path::Pathanyhow::Result
Design Patterns
1. Command Pattern
Each internals command follows the command pattern, encapsulating a request as an object:
trait InternalCommand {
fn execute(&self) -> Result<()>;
}
impl InternalCommand for SystemdGenerator {
fn execute(&self) -> Result<()> {
let unit_dir = &Dir::open_ambient_dir(&self.normal_dir, cap_std::ambient_authority())?;
crate::generator::generator(&self.root, unit_dir)
}
}
2. Strategy Pattern
Different commands use different strategies for execution:
enum ExecutionStrategy {
Direct(DirectCommand),
Proxy(ProxyCommand),
Generator(GeneratorCommand),
Test(TestCommand),
}
impl ExecutionStrategy {
fn execute(&self) -> Result<()> {
match self {
ExecutionStrategy::Direct(cmd) => cmd.execute_direct(),
ExecutionStrategy::Proxy(cmd) => cmd.execute_proxy(),
ExecutionStrategy::Generator(cmd) => cmd.execute_generator(),
ExecutionStrategy::Test(cmd) => cmd.execute_test(),
}
}
}
3. Factory Pattern
Commands are created using a factory pattern:
struct CommandFactory;
impl CommandFactory {
fn create_command(opts: &InternalsOpts) -> Box<dyn InternalCommand> {
match opts {
InternalsOpts::SystemdGenerator { .. } => {
Box::new(SystemdGenerator::new(opts))
}
InternalsOpts::Fsck => {
Box::new(FsckCommand::new())
}
// ... other commands
}
}
}
Error Handling
1. Error Types
The system uses a hierarchical error structure:
#[derive(thiserror::Error, Debug)]
pub enum InternalError {
#[error("System error: {0}")]
System(String),
#[error("Permission error: {0}")]
Permission(String),
#[error("Resource error: {0}")]
Resource(String),
#[error("Configuration error: {0}")]
Configuration(String),
}
impl From<std::io::Error> for InternalError {
fn from(err: std::io::Error) -> Self {
InternalError::System(err.to_string())
}
}
2. Error Context
All operations use error context for better debugging:
#[context("Systemd generator")]
pub(crate) fn generator(root: &Dir, unit_dir: &Dir) -> Result<()> {
// Implementation with automatic error context
}
#[context("Filesystem check")]
pub async fn fsck(sysroot: &Storage, out: impl Write) -> Result<()> {
// Implementation with automatic error context
}
3. Error Recovery
The system implements error recovery strategies:
pub async fn execute_with_retry<F, T>(mut operation: F, max_retries: usize) -> Result<T>
where
F: FnMut() -> Result<T>,
{
let mut last_error = None;
for attempt in 0..max_retries {
match operation() {
Ok(result) => return Ok(result),
Err(e) => {
last_error = Some(e);
if attempt < max_retries - 1 {
tokio::time::sleep(Duration::from_millis(100 * (attempt + 1) as u64)).await;
}
}
}
}
Err(last_error.unwrap())
}
Security Considerations
1. Privilege Escalation
All internals commands require root privileges:
pub(crate) fn require_root(is_container: bool) -> Result<()> {
ensure!(
rustix::process::getuid().is_root(),
if is_container {
"The user inside the container from which you are running this command must be root"
} else {
"This command must be executed as the root user"
}
);
Ok(())
}
2. Input Validation
All inputs are validated before processing:
pub(crate) fn validate_path(path: &Path) -> Result<()> {
// Check for path traversal
if path.components().any(|c| c == Component::ParentDir) {
return Err(anyhow::anyhow!("Path traversal detected"));
}
// Check for absolute paths
if !path.is_absolute() {
return Err(anyhow::anyhow!("Path must be absolute"));
}
Ok(())
}
3. Resource Limits
Operations are limited to prevent resource exhaustion:
pub(crate) fn with_resource_limits<F, T>(operation: F) -> Result<T>
where
F: FnOnce() -> Result<T>,
{
// Set memory limit
let memory_limit = 1024 * 1024 * 1024; // 1GB
let mut limits = rlimit::ResourceLimits::default();
limits.set_memory_limit(memory_limit)?;
limits.apply()?;
// Execute operation
operation()
}
Performance Optimization
1. Async Operations
All I/O operations are asynchronous:
pub async fn cleanup_async(sysroot: &Storage) -> Result<()> {
let ostree = sysroot.get_ostree()?;
let repo = ostree.repo();
// Async repository operations
let deployments = repo.list_deployments().await?;
let unused_deployments = find_unused_deployments(deployments).await?;
for deployment in unused_deployments {
repo.remove_deployment(deployment).await?;
}
// Async garbage collection
repo.garbage_collect().await?;
Ok(())
}
2. Parallel Processing
Operations that can be parallelized are:
pub async fn parallel_checks(sysroot: &Storage) -> Result<()> {
let checks = vec![
check_ostree_repo(sysroot),
check_composefs_repo(sysroot),
check_deployments(sysroot),
check_configuration(sysroot),
];
let results = futures::future::join_all(checks).await;
for result in results {
result?;
}
Ok(())
}
3. Caching
Frequently accessed data is cached:
pub struct Cache {
deployments: Option<Vec<Deployment>>,
images: Option<Vec<Image>>,
last_update: Option<Instant>,
}
impl Cache {
pub async fn get_deployments(&mut self, sysroot: &Storage) -> Result<&Vec<Deployment>> {
if self.deployments.is_none() || self.should_refresh() {
self.deployments = Some(sysroot.get_deployments().await?);
self.last_update = Some(Instant::now());
}
Ok(self.deployments.as_ref().unwrap())
}
fn should_refresh(&self) -> bool {
self.last_update
.map(|last| last.elapsed() > Duration::from_secs(60))
.unwrap_or(true)
}
}
Testing Strategy
1. Unit Tests
Each module has comprehensive unit tests:
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_systemd_generator() {
let temp_dir = tempfile::tempdir().unwrap();
let unit_dir = Dir::open_ambient_dir(temp_dir.path(), cap_std::ambient_authority()).unwrap();
// Test generator functionality
let result = fstab_generator_impl(&root, &unit_dir);
assert!(result.is_ok());
}
#[test]
fn test_fsck_operations() {
let temp_sysroot = create_test_sysroot().unwrap();
// Test fsck functionality
let result = fsck(&temp_sysroot, Vec::new()).await;
assert!(result.is_ok());
}
}
2. Integration Tests
End-to-end integration tests:
#[tokio::test]
async fn test_internals_integration() {
// Setup test environment
let test_env = TestEnvironment::new().await.unwrap();
// Test systemd generator
let result = test_env.run_internals_command("systemd-generator", &["/tmp"]).await;
assert!(result.is_ok());
// Test fsck
let result = test_env.run_internals_command("fsck", &[]).await;
assert!(result.is_ok());
// Test cleanup
let result = test_env.run_internals_command("cleanup", &[]).await;
assert!(result.is_ok());
}
3. Performance Tests
Performance benchmarks:
#[bench]
fn bench_fsck_operations(b: &mut Bencher) {
let sysroot = create_test_sysroot().unwrap();
b.iter(|| {
let rt = tokio::runtime::Runtime::new().unwrap();
rt.block_on(fsck(&sysroot, Vec::new()))
});
}
Future Enhancements
1. Plugin System
A plugin system for extending internals functionality:
pub trait InternalPlugin {
fn name(&self) -> &str;
fn execute(&self, args: &[String]) -> Result<()>;
fn help(&self) -> &str;
}
pub struct PluginManager {
plugins: Vec<Box<dyn InternalPlugin>>,
}
impl PluginManager {
pub fn register_plugin(&mut self, plugin: Box<dyn InternalPlugin>) {
self.plugins.push(plugin);
}
pub fn execute_plugin(&self, name: &str, args: &[String]) -> Result<()> {
let plugin = self.plugins.iter()
.find(|p| p.name() == name)
.ok_or_else(|| anyhow::anyhow!("Plugin not found: {}", name))?;
plugin.execute(args)
}
}
2. Configuration Management
Centralized configuration for internals commands:
#[derive(Debug, Serialize, Deserialize)]
pub struct InternalsConfig {
pub fsck: FsckConfig,
pub cleanup: CleanupConfig,
pub generator: GeneratorConfig,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct FsckConfig {
pub enabled_checks: Vec<String>,
pub timeout_seconds: u64,
pub parallel_checks: bool,
}
3. Monitoring Integration
Integration with monitoring systems:
pub trait MetricsCollector {
fn record_command_execution(&self, command: &str, duration: Duration, success: bool);
fn record_error(&self, command: &str, error: &str);
fn record_resource_usage(&self, command: &str, memory: u64, cpu: f64);
}
pub struct PrometheusCollector {
registry: Registry,
}
impl MetricsCollector for PrometheusCollector {
fn record_command_execution(&self, command: &str, duration: Duration, success: bool) {
// Record metrics in Prometheus format
}
}
This architecture document provides a comprehensive understanding of the bootc internals system's design, implementation, and future direction.