- Fix parallel execution logic to properly handle JoinHandle<Result<R, E>> types - Use join_all instead of try_join_all for proper Result handling - Fix double question mark (??) issue in parallel execution methods - Clean up unused imports in parallel and cache modules - Ensure all performance optimization modules compile successfully - Fix CI build failures caused by compilation errors
1229 lines
40 KiB
Markdown
1229 lines
40 KiB
Markdown
# 🚀 **apt-ostree Boot Management Architecture**
|
|
|
|
## 📋 **Overview**
|
|
|
|
This document outlines the boot management architecture for apt-ostree, based on analysis of how rpm-ostree implements initramfs management, kernel argument handling, and boot configuration. Boot management covers initramfs regeneration, kernel argument modification, and system boot configuration.
|
|
|
|
## 🏗️ **Architecture Overview**
|
|
|
|
### **Component Separation**
|
|
|
|
```
|
|
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
|
│ CLI Client │ │ Rust Core │ │ Rust Daemon │
|
|
│ (apt-ostree) │◄──►│ (DBus) │◄──►│ (aptostreed) │
|
|
│ │ │ │ │ │
|
|
│ • initramfs │ │ • Client Logic │ │ • Boot Config │
|
|
│ • kargs │ │ • DBus Client │ │ • Initramfs │
|
|
│ • boot config │ │ • Progress │ │ • Kernel Args │
|
|
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
|
```
|
|
|
|
### **Responsibility Distribution**
|
|
|
|
#### **CLI Client (`apt-ostree`)**
|
|
- **Command parsing** for initramfs and kargs subcommands
|
|
- **User interface** and progress display
|
|
- **DBus communication** with daemon
|
|
- **Argument validation** and processing
|
|
|
|
#### **Daemon (`apt-ostreed`)**
|
|
- **Initramfs regeneration** and management
|
|
- **Kernel argument** modification
|
|
- **Boot configuration** updates
|
|
- **System integration** with bootloader
|
|
|
|
## 🔍 **rpm-ostree Implementation Analysis**
|
|
|
|
### **Initramfs Commands Structure**
|
|
|
|
Based on `rpmostree-builtin-initramfs.cxx`, rpm-ostree provides these initramfs options:
|
|
|
|
```c
|
|
static GOptionEntry option_entries[]
|
|
= { { "os", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING, &opt_osname,
|
|
"Operate on provided OSNAME", "OSNAME" },
|
|
{ "stateroot", 0, 0, G_OPTION_ARG_STRING, &opt_osname, "Operate on provided STATEROOT",
|
|
"STATEROOT" },
|
|
{ "enable", 0, 0, G_OPTION_ARG_NONE, &opt_enable,
|
|
"Enable regenerating initramfs locally using dracut", NULL },
|
|
{ "arg", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_add_arg,
|
|
"Append ARG to the dracut arguments", "ARG" },
|
|
{ "disable", 0, 0, G_OPTION_ARG_NONE, &opt_disable,
|
|
"Disable regenerating initramfs locally", NULL },
|
|
{ "reboot", 'r', 0, G_OPTION_ARG_NONE, &opt_reboot,
|
|
"Initiate a reboot after operation is complete", NULL },
|
|
{ "lock-finalization", 0, 0, G_OPTION_ARG_NONE, &opt_lock_finalization,
|
|
"Prevent automatic deployment finalization on shutdown", NULL },
|
|
{ NULL } };
|
|
```
|
|
|
|
### **Kernel Arguments Commands Structure**
|
|
|
|
Based on `rpmostree-builtin-kargs.cxx`, rpm-ostree provides these kargs options:
|
|
|
|
```c
|
|
static GOptionEntry option_entries[] = {
|
|
{ "os", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING, &opt_osname, "Operate on provided OSNAME",
|
|
"OSNAME" },
|
|
{ "stateroot", 0, 0, G_OPTION_ARG_STRING, &opt_osname, "Operate on provided STATEROOT",
|
|
"STATEROOT" },
|
|
{ "deploy-index", 0, 0, G_OPTION_ARG_STRING, &opt_deploy_index,
|
|
"Modify the kernel args from a specific deployment based on index. Index is in the form of a "
|
|
"number (e.g. 0 means the first deployment in the list)",
|
|
"INDEX" },
|
|
{ "reboot", 0, 0, G_OPTION_ARG_NONE, &opt_reboot, "Initiate a reboot after operation is complete",
|
|
NULL },
|
|
{ "append", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_kernel_append_strings,
|
|
"Append kernel argument; useful with e.g. console= that can be used multiple times. empty "
|
|
"value for an argument is allowed",
|
|
"KEY=VALUE" },
|
|
{ "replace", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_kernel_replace_strings,
|
|
"Replace existing kernel argument, the user is also able to replace an argument with KEY=VALUE "
|
|
"if only one value exist for that argument ",
|
|
"KEY=VALUE=NEWVALUE" },
|
|
{ "delete", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_kernel_delete_strings,
|
|
"Delete a specific kernel argument key/val pair or an entire argument with a single key/value "
|
|
"pair",
|
|
"KEY=VALUE" },
|
|
{ "append-if-missing", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_kernel_append_if_missing_strings,
|
|
"Like --append, but does nothing if the key is already present", "KEY=VALUE" },
|
|
{ "delete-if-present", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_kernel_delete_if_present_strings,
|
|
"Like --delete, but does nothing if the key is already missing", "KEY=VALUE" },
|
|
{ "unchanged-exit-77", 0, 0, G_OPTION_ARG_NONE, &opt_unchanged_exit_77,
|
|
"If no kernel args changed, exit 77", NULL },
|
|
{ "import-proc-cmdline", 0, 0, G_OPTION_ARG_NONE, &opt_import_proc_cmdline,
|
|
"Instead of modifying old kernel arguments, we modify args from current /proc/cmdline (the "
|
|
"booted deployment)",
|
|
NULL },
|
|
{ "editor", 0, 0, G_OPTION_ARG_NONE, &opt_editor, "Use an editor to modify the kernel arguments",
|
|
NULL },
|
|
{ "lock-finalization", 0, 0, G_OPTION_ARG_NONE, &opt_lock_finalization,
|
|
"Prevent automatic deployment finalization on shutdown", NULL },
|
|
{ NULL }
|
|
};
|
|
```
|
|
|
|
### **Key Insights from rpm-ostree**
|
|
|
|
1. **Initramfs Management**: Uses dracut for initramfs generation
|
|
2. **Kernel Arguments**: Comprehensive argument manipulation (append, replace, delete)
|
|
3. **Deployment Indexing**: Can target specific deployments by index
|
|
4. **Boot Integration**: Direct integration with system bootloader
|
|
5. **Finalization Control**: Locking mechanism for deployment finalization
|
|
|
|
## 🚀 **apt-ostree Implementation Strategy**
|
|
|
|
### **1. CLI Command Structure**
|
|
|
|
```rust
|
|
// src/main.rs - Initramfs command handling
|
|
async fn initramfs_commands(args: &[String]) -> AptOstreeResult<()> {
|
|
if args.is_empty() {
|
|
show_initramfs_help();
|
|
return Ok(());
|
|
}
|
|
|
|
let mut osname = None;
|
|
let mut enable = false;
|
|
let mut disable = false;
|
|
let mut add_args = Vec::new();
|
|
let mut reboot = false;
|
|
let mut lock_finalization = false;
|
|
|
|
// Parse arguments
|
|
let mut i = 0;
|
|
while i < args.len() {
|
|
match args[i].as_str() {
|
|
"--os" => {
|
|
if i + 1 < args.len() {
|
|
osname = Some(args[i + 1].clone());
|
|
i += 2;
|
|
} else {
|
|
return Err(AptOstreeError::InvalidArgument("--os requires a value".to_string()));
|
|
}
|
|
}
|
|
"--stateroot" => {
|
|
if i + 1 < args.len() {
|
|
osname = Some(args[i + 1].clone());
|
|
i += 2;
|
|
} else {
|
|
return Err(AptOstreeError::InvalidArgument("--stateroot requires a value".to_string()));
|
|
}
|
|
}
|
|
"--enable" => {
|
|
enable = true;
|
|
i += 1;
|
|
}
|
|
"--disable" => {
|
|
disable = true;
|
|
i += 1;
|
|
}
|
|
"--arg" => {
|
|
if i + 1 < args.len() {
|
|
add_args.push(args[i + 1].clone());
|
|
i += 2;
|
|
} else {
|
|
return Err(AptOstreeError::InvalidArgument("--arg requires a value".to_string()));
|
|
}
|
|
}
|
|
"--reboot" | "-r" => {
|
|
reboot = true;
|
|
i += 1;
|
|
}
|
|
"--lock-finalization" => {
|
|
lock_finalization = true;
|
|
i += 1;
|
|
}
|
|
_ => {
|
|
return Err(AptOstreeError::InvalidArgument(
|
|
format!("Unknown option: {}", args[i]),
|
|
));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Validate arguments
|
|
if enable && disable {
|
|
return Err(AptOstreeError::InvalidArgument(
|
|
"Cannot use --enable and --disable together".to_string(),
|
|
));
|
|
}
|
|
|
|
if !enable && !disable && add_args.is_empty() {
|
|
// Show current initramfs status
|
|
show_initramfs_status(osname.as_deref()).await?;
|
|
} else {
|
|
// Modify initramfs configuration
|
|
modify_initramfs_config(
|
|
osname.as_deref(),
|
|
enable,
|
|
disable,
|
|
&add_args,
|
|
reboot,
|
|
lock_finalization,
|
|
).await?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
// src/main.rs - Kernel arguments command handling
|
|
async fn kargs_commands(args: &[String]) -> AptOstreeResult<()> {
|
|
if args.is_empty() {
|
|
show_kargs_help();
|
|
return Ok(());
|
|
}
|
|
|
|
let mut osname = None;
|
|
let mut deploy_index = None;
|
|
let mut reboot = false;
|
|
let mut lock_finalization = false;
|
|
let mut unchanged_exit_77 = false;
|
|
let mut import_proc_cmdline = false;
|
|
let mut editor = false;
|
|
|
|
let mut append_args = Vec::new();
|
|
let mut replace_args = Vec::new();
|
|
let mut delete_args = Vec::new();
|
|
let mut append_if_missing_args = Vec::new();
|
|
let mut delete_if_present_args = Vec::new();
|
|
|
|
// Parse arguments
|
|
let mut i = 0;
|
|
while i < args.len() {
|
|
match args[i].as_str() {
|
|
"--os" => {
|
|
if i + 1 < args.len() {
|
|
osname = Some(args[i + 1].clone());
|
|
i += 2;
|
|
} else {
|
|
return Err(AptOstreeError::InvalidArgument("--os requires a value".to_string()));
|
|
}
|
|
}
|
|
"--stateroot" => {
|
|
if i + 1 < args.len() {
|
|
osname = Some(args[i + 1].clone());
|
|
i += 2;
|
|
} else {
|
|
return Err(AptOstreeError::InvalidArgument("--stateroot requires a value".to_string()));
|
|
}
|
|
}
|
|
"--deploy-index" => {
|
|
if i + 1 < args.len() {
|
|
deploy_index = Some(args[i + 1].clone());
|
|
i += 2;
|
|
} else {
|
|
return Err(AptOstreeError::InvalidArgument("--deploy-index requires a value".to_string()));
|
|
}
|
|
}
|
|
"--reboot" => {
|
|
reboot = true;
|
|
i += 1;
|
|
}
|
|
"--append" => {
|
|
if i + 1 < args.len() {
|
|
append_args.push(args[i + 1].clone());
|
|
i += 2;
|
|
} else {
|
|
return Err(AptOstreeError::InvalidArgument("--append requires a value".to_string()));
|
|
}
|
|
}
|
|
"--replace" => {
|
|
if i + 1 < args.len() {
|
|
replace_args.push(args[i + 1].clone());
|
|
i += 2;
|
|
} else {
|
|
return Err(AptOstreeError::InvalidArgument("--replace requires a value".to_string()));
|
|
}
|
|
}
|
|
"--delete" => {
|
|
if i + 1 < args.len() {
|
|
delete_args.push(args[i + 1].clone());
|
|
i += 2;
|
|
} else {
|
|
return Err(AptOstreeError::InvalidArgument("--delete requires a value".to_string()));
|
|
}
|
|
}
|
|
"--append-if-missing" => {
|
|
if i + 1 < args.len() {
|
|
append_if_missing_args.push(args[i + 1].clone());
|
|
i += 2;
|
|
} else {
|
|
return Err(AptOstreeError::InvalidArgument("--append-if-missing requires a value".to_string()));
|
|
}
|
|
}
|
|
"--delete-if-present" => {
|
|
if i + 1 < args.len() {
|
|
delete_if_present_args.push(args[i + 1].clone());
|
|
i += 2;
|
|
} else {
|
|
return Err(AptOstreeError::InvalidArgument("--delete-if-present requires a value".to_string()));
|
|
}
|
|
}
|
|
"--unchanged-exit-77" => {
|
|
unchanged_exit_77 = true;
|
|
i += 1;
|
|
}
|
|
"--import-proc-cmdline" => {
|
|
import_proc_cmdline = true;
|
|
i += 1;
|
|
}
|
|
"--editor" => {
|
|
editor = true;
|
|
i += 1;
|
|
}
|
|
"--lock-finalization" => {
|
|
lock_finalization = true;
|
|
i += 1;
|
|
}
|
|
_ => {
|
|
return Err(AptOstreeError::InvalidArgument(
|
|
format!("Unknown option: {}", args[i]),
|
|
));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Validate arguments
|
|
if append_args.is_empty() && replace_args.is_empty() && delete_args.is_empty()
|
|
&& append_if_missing_args.is_empty() && delete_if_present_args.is_empty() && !editor {
|
|
// Show current kernel arguments
|
|
show_kernel_arguments(osname.as_deref(), deploy_index.as_deref()).await?;
|
|
} else {
|
|
// Modify kernel arguments
|
|
modify_kernel_arguments(
|
|
osname.as_deref(),
|
|
deploy_index.as_deref(),
|
|
&append_args,
|
|
&replace_args,
|
|
&delete_args,
|
|
&append_if_missing_args,
|
|
&delete_if_present_args,
|
|
editor,
|
|
import_proc_cmdline,
|
|
reboot,
|
|
lock_finalization,
|
|
unchanged_exit_77,
|
|
).await?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
```
|
|
|
|
### **2. Initramfs Management System**
|
|
|
|
#### **Core Initramfs Manager**
|
|
|
|
```rust
|
|
// src/boot/initramfs_manager.rs
|
|
pub struct InitramfsManager {
|
|
ostree_manager: Arc<OstreeManager>,
|
|
boot_config: Arc<RwLock<BootConfiguration>>,
|
|
dracut_manager: Arc<DracutManager>,
|
|
}
|
|
|
|
impl InitramfsManager {
|
|
pub async fn enable_initramfs_regeneration(
|
|
&self,
|
|
osname: Option<&str>,
|
|
additional_args: &[String],
|
|
lock_finalization: bool,
|
|
) -> Result<(), Error> {
|
|
let osname = osname.unwrap_or("debian");
|
|
|
|
// Get current deployment
|
|
let deployment = self.ostree_manager.get_booted_deployment().await?;
|
|
|
|
// Create staging deployment
|
|
let staging_ref = self.ostree_manager.create_staging_deployment().await?;
|
|
|
|
// Enable initramfs regeneration
|
|
self.boot_config
|
|
.write()
|
|
.await
|
|
.enable_initramfs_regeneration(&staging_ref, additional_args)
|
|
.await?;
|
|
|
|
// Lock finalization if requested
|
|
if lock_finalization {
|
|
self.boot_config
|
|
.write()
|
|
.await
|
|
.lock_deployment_finalization(&staging_ref)
|
|
.await?;
|
|
}
|
|
|
|
// Commit staging deployment
|
|
let commit_hash = self.ostree_manager.commit_staging_deployment(
|
|
&staging_ref,
|
|
"Enable initramfs regeneration",
|
|
).await?;
|
|
|
|
// Update boot configuration
|
|
self.ostree_manager.set_default_deployment(&commit_hash).await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn disable_initramfs_regeneration(
|
|
&self,
|
|
osname: Option<&str>,
|
|
lock_finalization: bool,
|
|
) -> Result<(), Error> {
|
|
let osname = osname.unwrap_or("debian");
|
|
|
|
// Get current deployment
|
|
let deployment = self.ostree_manager.get_booted_deployment().await?;
|
|
|
|
// Create staging deployment
|
|
let staging_ref = self.ostree_manager.create_staging_deployment().await?;
|
|
|
|
// Disable initramfs regeneration
|
|
self.boot_config
|
|
.write()
|
|
.await
|
|
.disable_initramfs_regeneration(&staging_ref)
|
|
.await?;
|
|
|
|
// Lock finalization if requested
|
|
if lock_finalization {
|
|
self.boot_config
|
|
.write()
|
|
.await
|
|
.lock_deployment_finalization(&staging_ref)
|
|
.await?;
|
|
}
|
|
|
|
// Commit staging deployment
|
|
let commit_hash = self.ostree_manager.commit_staging_deployment(
|
|
&staging_ref,
|
|
"Disable initramfs regeneration",
|
|
).await?;
|
|
|
|
// Update boot configuration
|
|
self.ostree_manager.set_default_deployment(&commit_hash).await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn regenerate_initramfs(
|
|
&self,
|
|
osname: Option<&str>,
|
|
additional_args: &[String],
|
|
) -> Result<(), Error> {
|
|
let osname = osname.unwrap_or("debian");
|
|
|
|
// Get current deployment
|
|
let deployment = self.ostree_manager.get_booted_deployment().await?;
|
|
|
|
// Extract deployment to temporary directory
|
|
let temp_dir = tempfile::tempdir()?;
|
|
let deployment_path = temp_dir.path();
|
|
|
|
self.ostree_manager
|
|
.checkout_deployment(&deployment.checksum, deployment_path)
|
|
.await?;
|
|
|
|
// Generate initramfs using dracut
|
|
let initramfs_path = self.dracut_manager
|
|
.generate_initramfs(deployment_path, additional_args)
|
|
.await?;
|
|
|
|
// Update deployment with new initramfs
|
|
let staging_ref = self.ostree_manager.create_staging_deployment().await?;
|
|
|
|
// Copy new initramfs to staging
|
|
let staging_initramfs = self.ostree_manager
|
|
.get_staging_path(&staging_ref)
|
|
.join("boot/initramfs.img");
|
|
|
|
tokio::fs::copy(&initramfs_path, &staging_initramfs).await?;
|
|
|
|
// Commit staging deployment
|
|
let commit_hash = self.ostree_manager.commit_staging_deployment(
|
|
&staging_ref,
|
|
"Regenerate initramfs",
|
|
).await?;
|
|
|
|
// Update boot configuration
|
|
self.ostree_manager.set_default_deployment(&commit_hash).await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn get_initramfs_status(&self, osname: Option<&str>) -> Result<InitramfsStatus, Error> {
|
|
let osname = osname.unwrap_or("debian");
|
|
|
|
// Get current deployment
|
|
let deployment = self.ostree_manager.get_booted_deployment().await?;
|
|
|
|
// Check initramfs configuration
|
|
let config = self.boot_config
|
|
.read()
|
|
.await
|
|
.get_deployment_config(&deployment.checksum)
|
|
.await?;
|
|
|
|
Ok(InitramfsStatus {
|
|
enabled: config.initramfs_regeneration_enabled,
|
|
additional_args: config.initramfs_args.clone(),
|
|
last_generated: config.last_initramfs_generation,
|
|
finalization_locked: config.finalization_locked,
|
|
})
|
|
}
|
|
}
|
|
```
|
|
|
|
#### **Dracut Integration**
|
|
|
|
```rust
|
|
// src/boot/dracut_manager.rs
|
|
pub struct DracutManager {
|
|
dracut_path: PathBuf,
|
|
kernel_modules_path: PathBuf,
|
|
}
|
|
|
|
impl DracutManager {
|
|
pub async fn generate_initramfs(
|
|
&self,
|
|
deployment_path: &Path,
|
|
additional_args: &[String],
|
|
) -> Result<PathBuf, Error> {
|
|
// Build dracut command
|
|
let mut cmd = tokio::process::Command::new(&self.dracut_path);
|
|
|
|
// Add standard arguments
|
|
cmd.arg("--force") // Force regeneration
|
|
.arg("--verbose") // Verbose output
|
|
.arg("--no-hostonly") // Don't include host-specific files
|
|
.arg("--no-hostonly-cmdline"); // Don't include host-specific cmdline
|
|
|
|
// Add deployment-specific arguments
|
|
cmd.arg("--kerneldir")
|
|
.arg(deployment_path.join("usr/lib/modules"));
|
|
|
|
cmd.arg("--rootfs")
|
|
.arg(deployment_path);
|
|
|
|
// Add additional arguments
|
|
for arg in additional_args {
|
|
cmd.arg(arg);
|
|
}
|
|
|
|
// Set output path
|
|
let output_path = deployment_path.join("boot/initramfs.img");
|
|
cmd.arg(&output_path);
|
|
|
|
// Execute dracut
|
|
let output = cmd.output().await?;
|
|
|
|
if !output.status.success() {
|
|
return Err(Error::DracutExecutionFailed {
|
|
stderr: String::from_utf8_lossy(&output.stderr).to_string(),
|
|
exit_code: output.status.code(),
|
|
});
|
|
}
|
|
|
|
Ok(output_path)
|
|
}
|
|
|
|
pub async fn validate_initramfs(&self, initramfs_path: &Path) -> Result<bool, Error> {
|
|
// Check if initramfs file exists and is not empty
|
|
if !initramfs_path.exists() {
|
|
return Ok(false);
|
|
}
|
|
|
|
let metadata = tokio::fs::metadata(initramfs_path).await?;
|
|
if metadata.len() == 0 {
|
|
return Ok(false);
|
|
}
|
|
|
|
// Validate initramfs format (basic check)
|
|
let mut file = tokio::fs::File::open(initramfs_path).await?;
|
|
let mut buffer = [0u8; 8];
|
|
file.read_exact(&mut buffer).await?;
|
|
|
|
// Check for common initramfs signatures
|
|
let is_valid = buffer.starts_with(b"\x1f\x8b") || // gzip
|
|
buffer.starts_with(b"\x89LZO") || // lzo
|
|
buffer.starts_with(b"070701"); // cpio
|
|
|
|
Ok(is_valid)
|
|
}
|
|
}
|
|
```
|
|
|
|
### **3. Kernel Arguments Management**
|
|
|
|
#### **Core Kernel Arguments Manager**
|
|
|
|
```rust
|
|
// src/boot/kernel_args_manager.rs
|
|
pub struct KernelArgsManager {
|
|
ostree_manager: Arc<OstreeManager>,
|
|
boot_config: Arc<RwLock<BootConfiguration>>,
|
|
}
|
|
|
|
impl KernelArgsManager {
|
|
pub async fn modify_kernel_arguments(
|
|
&self,
|
|
osname: Option<&str>,
|
|
deploy_index: Option<&str>,
|
|
append_args: &[String],
|
|
replace_args: &[String],
|
|
delete_args: &[String],
|
|
append_if_missing_args: &[String],
|
|
delete_if_present_args: &[String],
|
|
import_proc_cmdline: bool,
|
|
) -> Result<KernelArgsModification, Error> {
|
|
let osname = osname.unwrap_or("debian");
|
|
|
|
// Determine target deployment
|
|
let target_deployment = if let Some(index_str) = deploy_index {
|
|
let index: usize = index_str.parse()
|
|
.map_err(|_| Error::InvalidDeployIndex(index_str.to_string()))?;
|
|
self.ostree_manager.get_deployment_by_index(index).await?
|
|
} else {
|
|
self.ostree_manager.get_booted_deployment().await?
|
|
};
|
|
|
|
// Get current kernel arguments
|
|
let current_args = if import_proc_cmdline {
|
|
self.get_current_proc_cmdline().await?
|
|
} else {
|
|
self.get_deployment_kernel_args(&target_deployment.checksum).await?
|
|
};
|
|
|
|
// Parse current arguments
|
|
let mut kernel_args = KernelArgs::from_string(¤t_args)?;
|
|
|
|
// Apply modifications
|
|
let mut modifications = KernelArgsModification::new();
|
|
|
|
// Append arguments
|
|
for arg in append_args {
|
|
let parsed_arg = KernelArg::from_string(arg)?;
|
|
kernel_args.append(parsed_arg.clone());
|
|
modifications.added_args.push(parsed_arg);
|
|
}
|
|
|
|
// Replace arguments
|
|
for arg in replace_args {
|
|
let parsed_arg = KernelArg::from_string(arg)?;
|
|
let replaced = kernel_args.replace(parsed_arg.clone())?;
|
|
modifications.replaced_args.push((replaced, parsed_arg));
|
|
}
|
|
|
|
// Delete arguments
|
|
for arg in delete_args {
|
|
let parsed_arg = KernelArg::from_string(arg)?;
|
|
let deleted = kernel_args.delete(&parsed_arg)?;
|
|
if let Some(deleted) = deleted {
|
|
modifications.deleted_args.push(deleted);
|
|
}
|
|
}
|
|
|
|
// Append if missing
|
|
for arg in append_if_missing_args {
|
|
let parsed_arg = KernelArg::from_string(arg)?;
|
|
if !kernel_args.contains(&parsed_arg) {
|
|
kernel_args.append(parsed_arg.clone());
|
|
modifications.added_args.push(parsed_arg);
|
|
}
|
|
}
|
|
|
|
// Delete if present
|
|
for arg in delete_if_present_args {
|
|
let parsed_arg = KernelArg::from_string(arg)?;
|
|
if let Some(deleted) = kernel_args.delete(&parsed_arg)? {
|
|
modifications.deleted_args.push(deleted);
|
|
}
|
|
}
|
|
|
|
// Create staging deployment
|
|
let staging_ref = self.ostree_manager.create_staging_deployment().await?;
|
|
|
|
// Update kernel arguments in staging
|
|
self.boot_config
|
|
.write()
|
|
.await
|
|
.set_deployment_kernel_args(&staging_ref, &kernel_args)
|
|
.await?;
|
|
|
|
// Commit staging deployment
|
|
let commit_hash = self.ostree_manager.commit_staging_deployment(
|
|
&staging_ref,
|
|
"Modify kernel arguments",
|
|
).await?;
|
|
|
|
// Update boot configuration
|
|
self.ostree_manager.set_default_deployment(&commit_hash).await?;
|
|
|
|
Ok(modifications)
|
|
}
|
|
|
|
pub async fn get_kernel_arguments(
|
|
&self,
|
|
osname: Option<&str>,
|
|
deploy_index: Option<&str>,
|
|
) -> Result<KernelArgs, Error> {
|
|
let osname = osname.unwrap_or("debian");
|
|
|
|
// Determine target deployment
|
|
let target_deployment = if let Some(index_str) = deploy_index {
|
|
let index: usize = index_str.parse()
|
|
.map_err(|_| Error::InvalidDeployIndex(index_str.to_string()))?;
|
|
self.ostree_manager.get_deployment_by_index(index).await?
|
|
} else {
|
|
self.ostree_manager.get_booted_deployment().await?
|
|
};
|
|
|
|
// Get kernel arguments from deployment
|
|
let args = self.get_deployment_kernel_args(&target_deployment.checksum).await?;
|
|
|
|
Ok(KernelArgs::from_string(&args)?)
|
|
}
|
|
|
|
async fn get_current_proc_cmdline(&self) -> Result<String, Error> {
|
|
let cmdline = tokio::fs::read_to_string("/proc/cmdline").await?;
|
|
Ok(cmdline.trim().to_string())
|
|
}
|
|
|
|
async fn get_deployment_kernel_args(&self, commit_hash: &str) -> Result<String, Error> {
|
|
// Extract deployment to temporary directory
|
|
let temp_dir = tempfile::tempdir()?;
|
|
let deployment_path = temp_dir.path();
|
|
|
|
self.ostree_manager
|
|
.checkout_deployment(commit_hash, deployment_path)
|
|
.await?;
|
|
|
|
// Read kernel arguments from boot configuration
|
|
let boot_config_path = deployment_path.join("boot/loader/entries/ostree.conf");
|
|
if boot_config_path.exists() {
|
|
let content = tokio::fs::read_to_string(&boot_config_path).await?;
|
|
if let Some(args) = self.extract_kernel_args_from_config(&content).await? {
|
|
return Ok(args);
|
|
}
|
|
}
|
|
|
|
// Fallback to default arguments
|
|
Ok("root=UUID=auto ro".to_string())
|
|
}
|
|
|
|
async fn extract_kernel_args_from_config(&self, content: &str) -> Result<Option<String>, Error> {
|
|
for line in content.lines() {
|
|
if line.starts_with("options ") {
|
|
let args = line[8..].trim();
|
|
return Ok(Some(args.to_string()));
|
|
}
|
|
}
|
|
Ok(None)
|
|
}
|
|
}
|
|
```
|
|
|
|
#### **Kernel Arguments Parser**
|
|
|
|
```rust
|
|
// src/boot/kernel_args_parser.rs
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub struct KernelArgs {
|
|
args: Vec<KernelArg>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub struct KernelArg {
|
|
key: String,
|
|
value: Option<String>,
|
|
}
|
|
|
|
impl KernelArgs {
|
|
pub fn new() -> Self {
|
|
Self { args: Vec::new() }
|
|
}
|
|
|
|
pub fn from_string(input: &str) -> Result<Self, Error> {
|
|
let mut args = Vec::new();
|
|
|
|
for arg_str in input.split_whitespace() {
|
|
let arg = KernelArg::from_string(arg_str)?;
|
|
args.push(arg);
|
|
}
|
|
|
|
Ok(Self { args })
|
|
}
|
|
|
|
pub fn to_string(&self) -> String {
|
|
self.args
|
|
.iter()
|
|
.map(|arg| arg.to_string())
|
|
.collect::<Vec<_>>()
|
|
.join(" ")
|
|
}
|
|
|
|
pub fn append(&mut self, arg: KernelArg) {
|
|
self.args.push(arg);
|
|
}
|
|
|
|
pub fn replace(&mut self, new_arg: KernelArg) -> Result<Option<KernelArg>, Error> {
|
|
for existing_arg in &mut self.args {
|
|
if existing_arg.key == new_arg.key {
|
|
let old_arg = existing_arg.clone();
|
|
*existing_arg = new_arg;
|
|
return Ok(Some(old_arg));
|
|
}
|
|
}
|
|
Ok(None)
|
|
}
|
|
|
|
pub fn delete(&mut self, target_arg: &KernelArg) -> Result<Option<KernelArg>, Error> {
|
|
let mut i = 0;
|
|
while i < self.args.len() {
|
|
if self.args[i].key == target_arg.key {
|
|
if let Some(value) = &target_arg.value {
|
|
if self.args[i].value.as_deref() == Some(value) {
|
|
return Ok(Some(self.args.remove(i)));
|
|
}
|
|
} else {
|
|
return Ok(Some(self.args.remove(i)));
|
|
}
|
|
}
|
|
i += 1;
|
|
}
|
|
Ok(None)
|
|
}
|
|
|
|
pub fn contains(&self, target_arg: &KernelArg) -> bool {
|
|
self.args.iter().any(|arg| {
|
|
if arg.key == target_arg.key {
|
|
if let Some(value) = &target_arg.value {
|
|
arg.value.as_deref() == Some(value)
|
|
} else {
|
|
true
|
|
}
|
|
} else {
|
|
false
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
impl KernelArg {
|
|
pub fn new(key: String, value: Option<String>) -> Self {
|
|
Self { key, value }
|
|
}
|
|
|
|
pub fn from_string(input: &str) -> Result<Self, Error> {
|
|
if let Some(equal_pos) = input.find('=') {
|
|
let key = input[..equal_pos].to_string();
|
|
let value = input[equal_pos + 1..].to_string();
|
|
Ok(Self::new(key, Some(value)))
|
|
} else {
|
|
Ok(Self::new(input.to_string(), None))
|
|
}
|
|
}
|
|
|
|
pub fn to_string(&self) -> String {
|
|
if let Some(value) = &self.value {
|
|
format!("{}={}", self.key, value)
|
|
} else {
|
|
self.key.clone()
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### **4. Boot Configuration Management**
|
|
|
|
#### **Boot Configuration System**
|
|
|
|
```rust
|
|
// src/boot/boot_config_manager.rs
|
|
pub struct BootConfigManager {
|
|
ostree_manager: Arc<OstreeManager>,
|
|
bootloader_manager: Arc<BootloaderManager>,
|
|
}
|
|
|
|
impl BootConfigManager {
|
|
pub async fn update_boot_configuration(
|
|
&self,
|
|
deployment_hash: &str,
|
|
kernel_args: &KernelArgs,
|
|
initramfs_config: &InitramfsConfig,
|
|
) -> Result<(), Error> {
|
|
// Extract deployment to temporary directory
|
|
let temp_dir = tempfile::tempdir()?;
|
|
let deployment_path = temp_dir.path();
|
|
|
|
self.ostree_manager
|
|
.checkout_deployment(deployment_hash, deployment_path)
|
|
.await?;
|
|
|
|
// Update kernel arguments
|
|
self.update_kernel_args_in_deployment(deployment_path, kernel_args).await?;
|
|
|
|
// Update initramfs configuration
|
|
self.update_initramfs_config_in_deployment(deployment_path, initramfs_config).await?;
|
|
|
|
// Update bootloader configuration
|
|
self.bootloader_manager
|
|
.update_bootloader_config(deployment_path, deployment_hash)
|
|
.await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
async fn update_kernel_args_in_deployment(
|
|
&self,
|
|
deployment_path: &Path,
|
|
kernel_args: &KernelArgs,
|
|
) -> Result<(), Error> {
|
|
// Update systemd-boot configuration
|
|
let loader_path = deployment_path.join("boot/loader");
|
|
tokio::fs::create_dir_all(&loader_path).await?;
|
|
|
|
let entries_path = loader_path.join("entries");
|
|
tokio::fs::create_dir_all(&entries_path).await?;
|
|
|
|
// Create boot entry
|
|
let entry_path = entries_path.join("ostree.conf");
|
|
let entry_content = self.generate_boot_entry(kernel_args).await?;
|
|
|
|
tokio::fs::write(&entry_path, entry_content).await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
async fn generate_boot_entry(&self, kernel_args: &KernelArgs) -> Result<String, Error> {
|
|
let mut content = String::new();
|
|
|
|
content.push_str("title Debian Silverblue\n");
|
|
content.push_str("version latest\n");
|
|
content.push_str("linux /vmlinuz\n");
|
|
content.push_str("initrd /initramfs.img\n");
|
|
content.push_str(&format!("options {}\n", kernel_args.to_string()));
|
|
|
|
Ok(content)
|
|
}
|
|
|
|
async fn update_initramfs_config_in_deployment(
|
|
&self,
|
|
deployment_path: &Path,
|
|
initramfs_config: &InitramfsConfig,
|
|
) -> Result<(), Error> {
|
|
// Create initramfs configuration directory
|
|
let config_dir = deployment_path.join("etc/dracut.conf.d");
|
|
tokio::fs::create_dir_all(&config_dir).await?;
|
|
|
|
// Write dracut configuration
|
|
let config_path = config_dir.join("ostree.conf");
|
|
let config_content = self.generate_dracut_config(initramfs_config).await?;
|
|
|
|
tokio::fs::write(&config_path, config_content).await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
async fn generate_dracut_config(&self, config: &InitramfsConfig) -> Result<String, Error> {
|
|
let mut content = String::new();
|
|
|
|
if config.regeneration_enabled {
|
|
content.push_str("regenerate_initramfs=yes\n");
|
|
} else {
|
|
content.push_str("regenerate_initramfs=no\n");
|
|
}
|
|
|
|
if !config.additional_args.is_empty() {
|
|
content.push_str(&format!("dracut_args=\"{}\"\n", config.additional_args.join(" ")));
|
|
}
|
|
|
|
Ok(content)
|
|
}
|
|
}
|
|
```
|
|
|
|
## 🔐 **Security and Privileges**
|
|
|
|
### **1. Privilege Requirements**
|
|
|
|
```rust
|
|
// Security checks for boot management
|
|
impl InitramfsManager {
|
|
pub async fn check_initramfs_privileges(&self) -> Result<(), SecurityError> {
|
|
// Check if user has permission to modify initramfs
|
|
if !self.security_manager.can_modify_initramfs().await? {
|
|
return Err(SecurityError::InsufficientPrivileges(
|
|
"Initramfs modification requires elevated privileges".to_string(),
|
|
));
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl KernelArgsManager {
|
|
pub async fn check_kernel_args_privileges(&self) -> Result<(), SecurityError> {
|
|
// Check if user has permission to modify kernel arguments
|
|
if !self.security_manager.can_modify_kernel_args().await? {
|
|
return Err(SecurityError::InsufficientPrivileges(
|
|
"Kernel argument modification requires elevated privileges".to_string(),
|
|
));
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
```
|
|
|
|
### **2. Boot Configuration Validation**
|
|
|
|
```rust
|
|
// Validate boot configuration changes
|
|
impl BootConfigManager {
|
|
pub async fn validate_boot_configuration(
|
|
&self,
|
|
kernel_args: &KernelArgs,
|
|
initramfs_config: &InitramfsConfig,
|
|
) -> Result<(), Error> {
|
|
// Validate kernel arguments
|
|
for arg in &kernel_args.args {
|
|
self.validate_kernel_arg(arg).await?;
|
|
}
|
|
|
|
// Validate initramfs configuration
|
|
self.validate_initramfs_config(initramfs_config).await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
async fn validate_kernel_arg(&self, arg: &KernelArg) -> Result<(), Error> {
|
|
// Check for potentially dangerous arguments
|
|
let dangerous_args = [
|
|
"init=", "rdinit=", "panic=", "quiet", "debug",
|
|
"console=", "root=", "ro", "rw"
|
|
];
|
|
|
|
for dangerous in &dangerous_args {
|
|
if arg.key.starts_with(dangerous) {
|
|
// Log warning but allow
|
|
tracing::warn!("Potentially dangerous kernel argument: {}", arg.to_string());
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
async fn validate_initramfs_config(&self, config: &InitramfsConfig) -> Result<(), Error> {
|
|
// Validate dracut arguments
|
|
for arg in &config.additional_args {
|
|
if arg.contains("--") && !arg.starts_with("--") {
|
|
return Err(Error::InvalidDracutArgument(arg.clone()));
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
```
|
|
|
|
## 📊 **Performance Optimization**
|
|
|
|
### **1. Initramfs Caching**
|
|
|
|
```rust
|
|
// Cache generated initramfs images
|
|
impl InitramfsManager {
|
|
pub async fn get_cached_initramfs(
|
|
&self,
|
|
deployment_hash: &str,
|
|
additional_args: &[String],
|
|
) -> Result<Option<PathBuf>, Error> {
|
|
let cache_key = self.generate_cache_key(deployment_hash, additional_args).await?;
|
|
|
|
if let Some(cached_path) = self.cache.get(&cache_key).await? {
|
|
if self.validate_cached_initramfs(&cached_path).await? {
|
|
return Ok(Some(cached_path));
|
|
}
|
|
}
|
|
|
|
Ok(None)
|
|
}
|
|
|
|
async fn generate_cache_key(&self, deployment_hash: &str, additional_args: &[String]) -> Result<String, Error> {
|
|
let mut hasher = sha2::Sha256::new();
|
|
hasher.update(deployment_hash.as_bytes());
|
|
|
|
for arg in additional_args {
|
|
hasher.update(arg.as_bytes());
|
|
}
|
|
|
|
let result = hasher.finalize();
|
|
Ok(format!("{:x}", result))
|
|
}
|
|
}
|
|
```
|
|
|
|
### **2. Parallel Kernel Argument Processing**
|
|
|
|
```rust
|
|
// Parallel kernel argument validation
|
|
impl KernelArgsManager {
|
|
pub async fn validate_kernel_args_parallel(
|
|
&self,
|
|
args: &[KernelArg],
|
|
) -> Result<Vec<Result<(), Error>>, Error> {
|
|
let mut tasks = JoinSet::new();
|
|
|
|
// Spawn parallel validation tasks
|
|
for arg in args {
|
|
let arg = arg.clone();
|
|
let validator = self.clone();
|
|
|
|
tasks.spawn(async move {
|
|
validator.validate_single_kernel_arg(&arg).await
|
|
});
|
|
}
|
|
|
|
// Collect results
|
|
let mut results = Vec::new();
|
|
while let Some(result) = tasks.join_next().await {
|
|
results.push(result??);
|
|
}
|
|
|
|
Ok(results)
|
|
}
|
|
}
|
|
```
|
|
|
|
## 🧪 **Testing Strategy**
|
|
|
|
### **1. Unit Tests**
|
|
|
|
```rust
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[tokio::test]
|
|
async fn test_kernel_args_parsing() {
|
|
let args = KernelArgs::from_string("root=UUID=auto ro console=ttyS0").unwrap();
|
|
assert_eq!(args.args.len(), 3);
|
|
assert!(args.contains(&KernelArg::new("root".to_string(), Some("UUID=auto".to_string()))));
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_kernel_args_modification() {
|
|
let mut args = KernelArgs::from_string("root=UUID=auto ro").unwrap();
|
|
|
|
// Test append
|
|
args.append(KernelArg::new("console".to_string(), Some("ttyS0".to_string())));
|
|
assert_eq!(args.args.len(), 3);
|
|
|
|
// Test replace
|
|
let replaced = args.replace(KernelArg::new("root".to_string(), Some("UUID=new".to_string()))).unwrap();
|
|
assert!(replaced.is_some());
|
|
|
|
// Test delete
|
|
let deleted = args.delete(&KernelArg::new("console".to_string(), Some("ttyS0".to_string()))).unwrap();
|
|
assert!(deleted.is_some());
|
|
}
|
|
}
|
|
```
|
|
|
|
### **2. Integration Tests**
|
|
|
|
```rust
|
|
#[tokio::test]
|
|
async fn test_full_boot_management_workflow() {
|
|
// Set up test environment
|
|
let test_repo = create_test_repository().await?;
|
|
|
|
// Initialize managers
|
|
let initramfs_manager = InitramfsManager::new(&test_repo.path()).await?;
|
|
let kernel_args_manager = KernelArgsManager::new(&test_repo.path()).await?;
|
|
|
|
// Test initramfs management
|
|
initramfs_manager.enable_initramfs_regeneration(None, &[], false).await?;
|
|
let status = initramfs_manager.get_initramfs_status(None).await?;
|
|
assert!(status.enabled);
|
|
|
|
// Test kernel arguments
|
|
let modifications = kernel_args_manager
|
|
.modify_kernel_arguments(
|
|
None, None,
|
|
&["console=ttyS0".to_string()],
|
|
&[], &[], &[], &[],
|
|
false,
|
|
).await?;
|
|
assert!(!modifications.added_args.is_empty());
|
|
|
|
// Test boot configuration
|
|
let args = kernel_args_manager.get_kernel_arguments(None, None).await?;
|
|
assert!(args.contains(&KernelArg::new("console".to_string(), Some("ttyS0".to_string()))));
|
|
}
|
|
```
|
|
|
|
## 🚀 **Future Enhancements**
|
|
|
|
### **1. Advanced Boot Features**
|
|
- **Secure boot** integration and management
|
|
- **Bootloader selection** and configuration
|
|
- **Boot time** optimization and analysis
|
|
- **Boot failure** recovery and diagnostics
|
|
|
|
### **2. Performance Improvements**
|
|
- **Incremental initramfs** updates
|
|
- **Parallel dracut** execution
|
|
- **Boot configuration** caching
|
|
- **Background validation** and testing
|
|
|
|
### **3. Integration Features**
|
|
- **UEFI/BIOS** integration and management
|
|
- **Boot environment** variables and configuration
|
|
- **Boot time** services and dependencies
|
|
- **Boot monitoring** and analytics
|
|
|
|
This architecture provides a solid foundation for implementing production-ready boot management in apt-ostree, maintaining compatibility with the rpm-ostree ecosystem while leveraging the strengths of the Debian/Ubuntu boot system.
|