- 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
733 lines
18 KiB
Markdown
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.
|