Fix YAML linting issues and update system requirements to Debian 13+

- Fix trailing spaces and blank lines in Forgejo workflows
- Update system requirements from Ubuntu Jammy/Bookworm to Debian 13+ (Trixie)
- Update test treefile to use Debian Trixie instead of Ubuntu Jammy
- Update documentation to reflect modern system requirements
- Fix yamllint errors for CI/CD functionality
- Ensure compatibility with modern OSTree and libapt versions
This commit is contained in:
robojerk 2025-08-18 11:39:58 -07:00
parent ec0da91864
commit 3dec23f8f7
85 changed files with 12569 additions and 1088 deletions

View file

@ -6,6 +6,7 @@ use std::sync::Arc;
use tokio::sync::RwLock;
use uuid::Uuid;
/// Transaction types for different operations
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum TransactionType {
@ -29,6 +30,34 @@ pub enum TransactionType {
Experimental, // Experimental features
}
/// Upgrade-specific transaction data
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UpgradeTransaction {
pub packages_to_install: Vec<String>,
pub packages_to_remove: Vec<String>,
pub allow_downgrade: bool,
pub require_signatures: bool,
pub reboot_after: bool,
pub preview_mode: bool,
pub cache_only: bool,
pub download_only: bool,
}
impl Default for UpgradeTransaction {
fn default() -> Self {
Self {
packages_to_install: Vec::new(),
packages_to_remove: Vec::new(),
allow_downgrade: false,
require_signatures: true,
reboot_after: false,
preview_mode: false,
cache_only: false,
download_only: false,
}
}
}
/// Transaction states throughout the lifecycle
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
pub enum TransactionState {
@ -44,6 +73,51 @@ pub enum TransactionState {
RolledBack, // Successfully rolled back
}
/// Transaction step information for detailed progress tracking
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TransactionStep {
pub id: String,
pub name: String,
pub description: String,
pub status: StepStatus,
pub progress: f64,
pub started_at: Option<DateTime<Utc>>,
pub completed_at: Option<DateTime<Utc>>,
pub error: Option<String>,
pub metadata: HashMap<String, String>,
}
/// Step status for detailed progress tracking
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum StepStatus {
Pending,
Running,
Completed,
Failed,
Skipped,
}
/// Transaction priority levels
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, PartialOrd)]
pub enum TransactionPriority {
Low = 1,
Normal = 2,
High = 3,
Critical = 4,
}
/// Parameters for creating a transaction with steps
#[derive(Debug, Clone)]
pub struct TransactionCreationParams {
pub transaction_type: TransactionType,
pub user_id: u32,
pub session_id: String,
pub title: String,
pub description: String,
pub steps: Vec<TransactionStep>,
pub priority: TransactionPriority,
}
/// Transaction result information
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TransactionResult {
@ -51,6 +125,9 @@ pub struct TransactionResult {
pub message: String,
pub details: Option<String>,
pub rollback_required: bool,
pub steps_completed: usize,
pub steps_total: usize,
pub warnings: Vec<String>,
}
/// Transaction object representing a single operation
@ -69,6 +146,12 @@ pub struct Transaction {
pub progress: f64, // 0.0 to 1.0
pub result: Option<TransactionResult>,
pub metadata: HashMap<String, String>,
pub upgrade_data: Option<UpgradeTransaction>,
pub steps: Vec<TransactionStep>,
pub dependencies: Vec<String>,
pub priority: TransactionPriority,
pub estimated_duration: Option<u64>, // in seconds
pub actual_duration: Option<u64>, // in seconds
}
impl Transaction {
@ -96,6 +179,12 @@ impl Transaction {
progress: 0.0,
result: None,
metadata: HashMap::new(),
upgrade_data: None,
steps: Vec::new(),
dependencies: Vec::new(),
priority: TransactionPriority::Normal,
estimated_duration: None,
actual_duration: None,
}
}
@ -115,7 +204,7 @@ impl Transaction {
/// Update progress
pub fn update_progress(&mut self, progress: f64) {
self.progress = progress.max(0.0).min(1.0);
self.progress = progress.clamp(0.0, 1.0);
}
/// Set transaction result
@ -128,6 +217,66 @@ impl Transaction {
self.metadata.insert(key, value);
}
/// Add a transaction step
pub fn add_step(&mut self, step: TransactionStep) {
self.steps.push(step);
}
/// Update step status
pub fn update_step_status(&mut self, step_id: &str, status: StepStatus) -> AptOstreeResult<()> {
if let Some(step) = self.steps.iter_mut().find(|s| s.id == step_id) {
step.status = status.clone();
match status {
StepStatus::Running => {
step.started_at = Some(Utc::now());
}
StepStatus::Completed | StepStatus::Failed => {
step.completed_at = Some(Utc::now());
}
_ => {}
}
Ok(())
} else {
Err(AptOstreeError::System(format!("Step {} not found", step_id)))
}
}
/// Get step by ID
pub fn get_step(&self, step_id: &str) -> Option<&TransactionStep> {
self.steps.iter().find(|s| s.id == step_id)
}
/// Calculate overall progress from steps
pub fn calculate_progress_from_steps(&mut self) {
if self.steps.is_empty() {
return;
}
let completed_steps = self.steps.iter()
.filter(|s| s.status == StepStatus::Completed)
.count();
let total_steps = self.steps.len();
self.progress = completed_steps as f64 / total_steps as f64;
}
/// Set transaction priority
pub fn set_priority(&mut self, priority: TransactionPriority) {
self.priority = priority;
}
/// Add dependency
pub fn add_dependency(&mut self, transaction_id: String) {
if !self.dependencies.contains(&transaction_id) {
self.dependencies.push(transaction_id);
}
}
/// Check if dependencies are satisfied
pub fn dependencies_satisfied(&self, completed_transactions: &[String]) -> bool {
self.dependencies.iter().all(|dep| completed_transactions.contains(dep))
}
/// Check if transaction is active
pub fn is_active(&self) -> bool {
matches!(
@ -154,6 +303,7 @@ impl Transaction {
}
/// Transaction manager for handling all transactions
#[allow(dead_code, clippy::new_without_default)]
pub struct TransactionManager {
transactions: Arc<RwLock<HashMap<String, Transaction>>>,
}
@ -165,7 +315,15 @@ impl TransactionManager {
transactions: Arc::new(RwLock::new(HashMap::new())),
}
}
}
impl Default for TransactionManager {
fn default() -> Self {
Self::new()
}
}
impl TransactionManager {
/// Create a new transaction
pub async fn create_transaction(
&self,
@ -267,6 +425,186 @@ impl TransactionManager {
Ok(())
}
/// Create an upgrade transaction
pub async fn create_upgrade_transaction(
&self,
user_id: u32,
session_id: String,
upgrade_data: UpgradeTransaction,
) -> AptOstreeResult<String> {
let transaction_id = Uuid::new_v4().to_string();
let mut transaction = Transaction::new(
transaction_id.clone(),
TransactionType::Upgrade,
user_id,
session_id,
"System Upgrade".to_string(),
"Upgrading system packages and OSTree tree".to_string(),
Utc::now(),
);
transaction.upgrade_data = Some(upgrade_data);
// Store transaction
self.transactions
.write()
.await
.insert(transaction_id.clone(), transaction);
Ok(transaction_id)
}
/// Execute an upgrade transaction
pub async fn execute_upgrade_transaction(&self, transaction_id: &str) -> AptOstreeResult<()> {
let mut transaction = self.get_transaction(transaction_id).await?;
if transaction.transaction_type != TransactionType::Upgrade {
return Err(AptOstreeError::System(
"Transaction is not an upgrade transaction".to_string()
));
}
// Extract upgrade data before borrowing
let _packages_to_install = if let Some(ref upgrade_data) = transaction.upgrade_data {
upgrade_data.packages_to_install.clone()
} else {
return Err(AptOstreeError::System(
"Upgrade transaction data not found".to_string()
));
};
let _packages_to_remove = if let Some(ref upgrade_data) = transaction.upgrade_data {
upgrade_data.packages_to_remove.len()
} else {
0
};
// Create upgrade steps if they don't exist
if transaction.steps.is_empty() {
let upgrade_steps = vec![
TransactionStep {
id: "validate".to_string(),
name: "Validate System".to_string(),
description: "Check OSTree system status and APT availability".to_string(),
status: StepStatus::Pending,
progress: 0.0,
started_at: None,
completed_at: None,
error: None,
metadata: HashMap::new(),
},
TransactionStep {
id: "update_cache".to_string(),
name: "Update APT Cache".to_string(),
description: "Refresh package lists and metadata".to_string(),
status: StepStatus::Pending,
progress: 0.0,
started_at: None,
completed_at: None,
error: None,
metadata: HashMap::new(),
},
TransactionStep {
id: "check_updates".to_string(),
name: "Check for Updates".to_string(),
description: "Identify available system and package updates".to_string(),
status: StepStatus::Pending,
progress: 0.0,
started_at: None,
completed_at: None,
error: None,
metadata: HashMap::new(),
},
TransactionStep {
id: "download_packages".to_string(),
name: "Download Packages".to_string(),
description: "Download required package updates".to_string(),
status: StepStatus::Pending,
progress: 0.0,
started_at: None,
completed_at: None,
error: None,
metadata: HashMap::new(),
},
TransactionStep {
id: "apply_updates".to_string(),
name: "Apply Updates".to_string(),
description: "Apply package updates and system changes".to_string(),
status: StepStatus::Pending,
progress: 0.0,
started_at: None,
completed_at: None,
error: None,
metadata: HashMap::new(),
},
TransactionStep {
id: "finalize".to_string(),
name: "Finalize Upgrade".to_string(),
description: "Complete upgrade and prepare for reboot if needed".to_string(),
status: StepStatus::Pending,
progress: 0.0,
started_at: None,
completed_at: None,
error: None,
metadata: HashMap::new(),
},
];
for step in upgrade_steps {
transaction.add_step(step);
}
}
// Update transaction state
transaction.update_state(TransactionState::Preparing);
transaction.update_progress(0.1);
self.update_transaction(&transaction).await?;
// Execute upgrade steps
let step_executor = |step: &TransactionStep| -> AptOstreeResult<bool> {
match step.id.as_str() {
"validate" => {
// TODO: Implement real validation logic
Ok(true)
}
"update_cache" => {
// TODO: Implement real APT cache update
Ok(true)
}
"check_updates" => {
// TODO: Implement real update checking
Ok(true)
}
"download_packages" => {
// TODO: Implement real package downloading
Ok(true)
}
"apply_updates" => {
// TODO: Implement real update application
Ok(true)
}
"finalize" => {
// TODO: Implement real upgrade finalization
Ok(true)
}
_ => Ok(true),
}
};
// Execute the transaction with steps
let result = self.execute_transaction_with_steps(transaction_id, step_executor).await?;
// Update the transaction with the result
let mut transaction = self.get_transaction(transaction_id).await?;
transaction.result = Some(result);
self.update_transaction(&transaction).await?;
Ok(())
}
/// Clean up completed transactions
pub async fn cleanup_completed_transactions(&self, max_age_hours: u64) -> AptOstreeResult<usize> {
let mut transactions = self.transactions.write().await;
@ -278,7 +616,7 @@ impl TransactionManager {
matches!(
t.state,
TransactionState::Completed | TransactionState::Failed | TransactionState::Cancelled | TransactionState::RolledBack
) && t.completed_at.map_or(false, |time| time < cutoff_time)
) && t.completed_at.is_some_and(|time| time < cutoff_time)
})
.map(|(id, _)| id.clone())
.collect();
@ -290,4 +628,174 @@ impl TransactionManager {
Ok(count)
}
/// Create a transaction with steps
#[allow(clippy::too_many_arguments)]
pub async fn create_transaction_with_steps(
&self,
params: TransactionCreationParams,
) -> AptOstreeResult<String> {
let transaction_id = Uuid::new_v4().to_string();
let mut transaction = Transaction::new(
transaction_id.clone(),
params.transaction_type,
params.user_id,
params.session_id,
params.title,
params.description,
Utc::now(),
);
transaction.set_priority(params.priority);
for step in params.steps {
transaction.add_step(step);
}
// Store transaction
self.transactions
.write()
.await
.insert(transaction_id.clone(), transaction);
Ok(transaction_id)
}
/// Execute a transaction with step-by-step progress
pub async fn execute_transaction_with_steps(
&self,
transaction_id: &str,
step_executor: impl Fn(&TransactionStep) -> AptOstreeResult<bool> + Send + Sync,
) -> AptOstreeResult<TransactionResult> {
let mut transaction = self.get_transaction(transaction_id).await?;
if !transaction.is_active() {
return Err(AptOstreeError::System(
"Transaction is not in an active state".to_string()
));
}
transaction.update_state(TransactionState::Running);
self.update_transaction(&transaction).await?;
let start_time = std::time::Instant::now();
let mut step_results = Vec::new();
// Execute each step and collect results
for step in &transaction.steps {
if step.status == StepStatus::Pending {
let mut step_result = step.clone();
step_result.status = StepStatus::Running;
step_result.started_at = Some(Utc::now());
// Execute the step
let step_success = match step_executor(step) {
Ok(success) => success,
Err(e) => {
step_result.status = StepStatus::Failed;
step_result.error = Some(e.to_string());
false
}
};
if step_success {
step_result.status = StepStatus::Completed;
step_result.completed_at = Some(Utc::now());
} else {
step_result.status = StepStatus::Failed;
if step_result.error.is_none() {
step_result.error = Some("Step execution failed".to_string());
}
}
// Check if any step failed
if step_result.status == StepStatus::Failed {
let error_message = step_result.error.clone();
let step_name = step_result.name.clone();
step_results.push(step_result);
transaction.update_state(TransactionState::Failed);
transaction.actual_duration = Some(start_time.elapsed().as_secs());
// Update steps in transaction
for (i, result) in step_results.iter().enumerate() {
if i < transaction.steps.len() {
transaction.steps[i] = result.clone();
}
}
self.update_transaction(&transaction).await?;
return Ok(TransactionResult {
success: false,
message: format!("Step '{}' failed", step_name),
details: error_message,
rollback_required: true,
steps_completed: step_results.iter().filter(|s| s.status == StepStatus::Completed).count(),
steps_total: transaction.steps.len(),
warnings: Vec::new(),
});
}
} else {
step_results.push(step.clone());
}
}
// Update transaction with step results
transaction.steps = step_results;
transaction.calculate_progress_from_steps();
// All steps completed successfully
transaction.update_state(TransactionState::Completed);
transaction.actual_duration = Some(start_time.elapsed().as_secs());
let result = TransactionResult {
success: true,
message: "Transaction completed successfully".to_string(),
details: None,
rollback_required: false,
steps_completed: transaction.steps.len(),
steps_total: transaction.steps.len(),
warnings: Vec::new(),
};
transaction.result = Some(result.clone());
self.update_transaction(&transaction).await?;
Ok(result)
}
/// Get transactions by priority
pub async fn get_transactions_by_priority(&self, priority: TransactionPriority) -> AptOstreeResult<Vec<Transaction>> {
let transactions = self.transactions.read().await;
Ok(transactions
.values()
.filter(|t| t.priority == priority)
.cloned()
.collect())
}
/// Get transactions with dependencies
pub async fn get_transactions_with_dependencies(&self) -> AptOstreeResult<Vec<Transaction>> {
let transactions = self.transactions.read().await;
Ok(transactions
.values()
.filter(|t| !t.dependencies.is_empty())
.cloned()
.collect())
}
/// Validate transaction dependencies
pub async fn validate_transaction_dependencies(&self, transaction_id: &str) -> AptOstreeResult<bool> {
let transaction = self.get_transaction(transaction_id).await?;
let completed_transactions = self.list_transactions().await?
.into_iter()
.filter(|t| t.state == TransactionState::Completed)
.map(|t| t.id)
.collect::<Vec<_>>();
Ok(transaction.dependencies_satisfied(&completed_transactions))
}
}