bootc-docs/internals/bootc-internals-architecture.md
robojerk 526f1c1afd Initial commit: Comprehensive Debian bootc documentation
- 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
2025-09-15 14:02:28 -07:00

733 lines
18 KiB
Markdown

# 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:
```rust
#[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`:
```rust
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 service
- `generate_fstab_editor()` - Create systemd unit file
- `generator()` - Main generator entry point
**Implementation**:
```rust
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_in`
- `cap_std::fs::Dir`
- `rustix::fs::StatVfsMountFlags`
### 2. Filesystem Check Module (`fsck.rs`)
**Purpose**: System consistency checking
**Key Functions**:
- `fsck()` - Main consistency check entry point
- `fsck_ok()` - Return success result
- `fsck_err()` - Return error result
**Implementation**:
```rust
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 registration
- `ostree_ext::composefs` - For composefs operations
- `ostree_ext::ostree` - For OSTree operations
### 3. Composefs Control Module (`cfsctl.rs`)
**Purpose**: Composefs repository management
**Key Functions**:
- `run_from_iter()` - Parse and execute cfsctl commands
- `App::parse()` - Parse command line arguments
- `Command` enum - Define available commands
**Implementation**:
```rust
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::Repository`
- `composefs::fsverity::FsVerityHashValue`
- `composefs_boot::write_boot`
### 4. Deploy Module (`deploy.rs`)
**Purpose**: Deployment operations and cleanup
**Key Functions**:
- `fixup_etc_fstab()` - Fix /etc/fstab for composefs
- `cleanup()` - Perform system cleanup
- `switch_origin_inplace()` - In-place origin switching
**Implementation**:
```rust
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::Dir`
- `std::io::Write`
- `ostree_ext::ostree`
### 5. Block Device Module (`blockdev.rs`)
**Purpose**: Loopback device management
**Key Functions**:
- `LoopbackDevice::new()` - Create loopback device
- `run_loopback_cleanup_helper()` - Cleanup helper
- `run_allocate_cleanup_loopback()` - Test allocation
**Implementation**:
```rust
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::Command`
- `std::path::Path`
- `anyhow::Result`
## Design Patterns
### 1. Command Pattern
Each internals command follows the command pattern, encapsulating a request as an object:
```rust
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:
```rust
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:
```rust
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:
```rust
#[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:
```rust
#[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:
```rust
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:
```rust
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:
```rust
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:
```rust
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:
```rust
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:
```rust
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:
```rust
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:
```rust
#[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:
```rust
#[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:
```rust
#[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:
```rust
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:
```rust
#[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:
```rust
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.