- 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
395 lines
14 KiB
Rust
395 lines
14 KiB
Rust
use crate::lib::error::{AptOstreeError, AptOstreeResult};
|
|
use std::process::Command;
|
|
use std::path::Path;
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
/// Basic APT functionality
|
|
#[allow(dead_code, clippy::new_without_default)]
|
|
pub struct AptManager {
|
|
cache_dir: String,
|
|
apt_config_path: String,
|
|
}
|
|
|
|
impl AptManager {
|
|
/// Create a new APT manager instance
|
|
#[allow(clippy::new_without_default)]
|
|
pub fn new() -> Self {
|
|
Self {
|
|
cache_dir: "/var/cache/apt".to_string(),
|
|
apt_config_path: "/etc/apt".to_string(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl AptManager {
|
|
/// Check APT database health
|
|
pub fn check_database_health(&self) -> AptOstreeResult<bool> {
|
|
// Check if APT cache is accessible
|
|
if !Path::new(&self.cache_dir).exists() {
|
|
return Err(AptOstreeError::System("APT cache directory not found".to_string()));
|
|
}
|
|
|
|
// Check if apt-get is available
|
|
if Command::new("apt-get").arg("--version").output().is_err() {
|
|
return Err(AptOstreeError::System("apt-get not available on this system".to_string()));
|
|
}
|
|
|
|
// Check if apt-cache is available
|
|
if Command::new("apt-cache").arg("--version").output().is_err() {
|
|
return Err(AptOstreeError::System("apt-cache not available on this system".to_string()));
|
|
}
|
|
|
|
Ok(true)
|
|
}
|
|
|
|
/// Install a package with real APT functionality
|
|
pub fn install_package(&self, package: &str) -> AptOstreeResult<()> {
|
|
// First check if package exists
|
|
let package_info = self.get_package_info(package)?;
|
|
if package_info.is_none() {
|
|
return Err(AptOstreeError::InvalidArgument(
|
|
format!("Package '{}' not found in APT repositories", package)
|
|
));
|
|
}
|
|
|
|
// Check dependencies
|
|
let dependencies = self.resolve_dependencies(package)?;
|
|
println!("Installing package: {} with {} dependencies", package, dependencies.len());
|
|
|
|
// Use apt-get to install the package
|
|
let output = Command::new("apt-get")
|
|
.arg("install")
|
|
.arg("--yes")
|
|
.arg("--no-install-recommends")
|
|
.arg(package)
|
|
.output()
|
|
.map_err(|e| AptOstreeError::System(format!("Failed to install package: {}", e)))?;
|
|
|
|
if !output.status.success() {
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
return Err(AptOstreeError::System(format!("Package installation failed: {}", stderr)));
|
|
}
|
|
|
|
println!("Successfully installed package: {}", package);
|
|
Ok(())
|
|
}
|
|
|
|
/// Remove a package with real APT functionality
|
|
pub fn remove_package(&self, package: &str) -> AptOstreeResult<()> {
|
|
// Check if package is installed
|
|
if !self.is_package_installed(package)? {
|
|
return Err(AptOstreeError::InvalidArgument(
|
|
format!("Package '{}' is not installed", package)
|
|
));
|
|
}
|
|
|
|
// Use apt-get to remove the package
|
|
let output = Command::new("apt-get")
|
|
.arg("remove")
|
|
.arg("--yes")
|
|
.arg(package)
|
|
.output()
|
|
.map_err(|e| AptOstreeError::System(format!("Failed to remove package: {}", e)))?;
|
|
|
|
if !output.status.success() {
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
return Err(AptOstreeError::System(format!("Package removal failed: {}", stderr)));
|
|
}
|
|
|
|
println!("Successfully removed package: {}", package);
|
|
Ok(())
|
|
}
|
|
|
|
/// Update package cache with real APT functionality
|
|
pub fn update_cache(&self) -> AptOstreeResult<()> {
|
|
println!("Updating APT package cache...");
|
|
|
|
let output = Command::new("apt-get")
|
|
.arg("update")
|
|
.output()
|
|
.map_err(|e| AptOstreeError::System(format!("Failed to update cache: {}", e)))?;
|
|
|
|
if !output.status.success() {
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
return Err(AptOstreeError::System(format!("Cache update failed: {}", stderr)));
|
|
}
|
|
|
|
println!("APT package cache updated successfully");
|
|
Ok(())
|
|
}
|
|
|
|
/// Check if authorization is required for an action
|
|
pub fn requires_authorization(&self, action: &str) -> bool {
|
|
// Package installation and removal require root privileges
|
|
matches!(action, "install" | "remove" | "upgrade" | "dist-upgrade")
|
|
}
|
|
|
|
/// Check if user is authorized for an action
|
|
pub fn check_authorization(&self, action: &str) -> AptOstreeResult<bool> {
|
|
if !self.requires_authorization(action) {
|
|
return Ok(true);
|
|
}
|
|
|
|
// Check if running as root
|
|
if unsafe { libc::geteuid() } == 0 {
|
|
Ok(true)
|
|
} else {
|
|
// TODO: Implement Polkit authorization check
|
|
Ok(false)
|
|
}
|
|
}
|
|
|
|
/// Search packages with real APT functionality
|
|
pub fn search_packages(&self, query: &str) -> AptOstreeResult<Vec<PackageInfo>> {
|
|
println!("Searching for packages matching: {}", query);
|
|
|
|
let output = Command::new("apt-cache")
|
|
.arg("search")
|
|
.arg(query)
|
|
.output()
|
|
.map_err(|e| AptOstreeError::System(format!("Failed to search packages: {}", e)))?;
|
|
|
|
if !output.status.success() {
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
return Err(AptOstreeError::System(format!("Package search failed: {}", stderr)));
|
|
}
|
|
|
|
let search_output = String::from_utf8_lossy(&output.stdout);
|
|
let mut packages = Vec::new();
|
|
|
|
for line in search_output.lines() {
|
|
if let Some(package) = self.parse_search_line(line) {
|
|
packages.push(package);
|
|
}
|
|
}
|
|
|
|
println!("Found {} packages matching '{}'", packages.len(), query);
|
|
Ok(packages)
|
|
}
|
|
|
|
/// Search packages with exact match
|
|
pub fn search_packages_exact(&self, query: &str) -> AptOstreeResult<Vec<PackageInfo>> {
|
|
// Use apt-cache policy for exact package information
|
|
let output = Command::new("apt-cache")
|
|
.arg("policy")
|
|
.arg(query)
|
|
.output()
|
|
.map_err(|e| AptOstreeError::System(format!("Failed to get package policy: {}", e)))?;
|
|
|
|
if !output.status.success() {
|
|
return Ok(Vec::new()); // Package not found
|
|
}
|
|
|
|
let policy_output = String::from_utf8_lossy(&output.stdout);
|
|
if let Some(package) = self.parse_policy_output(query, &policy_output) {
|
|
Ok(vec![package])
|
|
} else {
|
|
Ok(Vec::new())
|
|
}
|
|
}
|
|
|
|
/// Search packages with regex
|
|
pub fn search_packages_regex(&self, query: &str) -> AptOstreeResult<Vec<PackageInfo>> {
|
|
// For regex search, we'll use apt-cache search and then filter with regex
|
|
let all_packages = self.search_packages("")?;
|
|
|
|
// Simple regex-like matching (basic wildcard support)
|
|
let pattern = query.replace("*", ".*");
|
|
let regex = regex::Regex::new(&pattern)
|
|
.map_err(|e| AptOstreeError::System(format!("Invalid regex pattern: {}", e)))?;
|
|
|
|
let filtered_packages: Vec<PackageInfo> = all_packages
|
|
.into_iter()
|
|
.filter(|pkg| regex.is_match(&pkg.name))
|
|
.collect();
|
|
|
|
Ok(filtered_packages)
|
|
}
|
|
|
|
/// Check if a package is installed
|
|
pub fn is_package_installed(&self, package: &str) -> AptOstreeResult<bool> {
|
|
let output = Command::new("dpkg")
|
|
.arg("-s")
|
|
.arg(package)
|
|
.output();
|
|
|
|
match output {
|
|
Ok(output) => Ok(output.status.success()),
|
|
Err(_) => Ok(false),
|
|
}
|
|
}
|
|
|
|
/// Get detailed package information
|
|
pub fn get_package_info(&self, package: &str) -> AptOstreeResult<Option<PackageInfo>> {
|
|
let output = Command::new("apt-cache")
|
|
.arg("show")
|
|
.arg(package)
|
|
.output()
|
|
.map_err(|e| AptOstreeError::System(format!("Failed to get package info: {}", e)))?;
|
|
|
|
if !output.status.success() {
|
|
return Ok(None); // Package not found
|
|
}
|
|
|
|
let show_output = String::from_utf8_lossy(&output.stdout);
|
|
Ok(self.parse_show_output(package, &show_output))
|
|
}
|
|
|
|
/// Resolve package dependencies
|
|
pub fn resolve_dependencies(&self, package: &str) -> AptOstreeResult<Vec<String>> {
|
|
let output = Command::new("apt-cache")
|
|
.arg("depends")
|
|
.arg(package)
|
|
.output()
|
|
.map_err(|e| AptOstreeError::System(format!("Failed to get dependencies: {}", e)))?;
|
|
|
|
if !output.status.success() {
|
|
return Ok(Vec::new());
|
|
}
|
|
|
|
let depends_output = String::from_utf8_lossy(&output.stdout);
|
|
let mut dependencies = Vec::new();
|
|
|
|
for line in depends_output.lines() {
|
|
if line.trim().starts_with("Depends:") {
|
|
let deps = line.split("Depends:").nth(1).unwrap_or("").trim();
|
|
if !deps.is_empty() {
|
|
dependencies.extend(deps.split(',').map(|s| s.trim().to_string()));
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(dependencies)
|
|
}
|
|
|
|
/// Get cache status for daemon
|
|
pub fn get_cache_status(&self) -> AptOstreeResult<String> {
|
|
if !Path::new(&self.cache_dir).exists() {
|
|
return Ok("not-available".to_string());
|
|
}
|
|
|
|
// Check if cache is up to date
|
|
let output = Command::new("apt-get")
|
|
.arg("check")
|
|
.output();
|
|
|
|
match output {
|
|
Ok(output) => {
|
|
if output.status.success() {
|
|
Ok("healthy".to_string())
|
|
} else {
|
|
Ok("needs-update".to_string())
|
|
}
|
|
}
|
|
Err(_) => Ok("error".to_string()),
|
|
}
|
|
}
|
|
|
|
/// Parse search output line
|
|
fn parse_search_line(&self, line: &str) -> Option<PackageInfo> {
|
|
// Format: "package_name - package_description"
|
|
let parts: Vec<&str> = line.splitn(2, " - ").collect();
|
|
if parts.len() != 2 {
|
|
return None;
|
|
}
|
|
|
|
let name = parts[0].trim();
|
|
let description = parts[1].trim();
|
|
|
|
Some(PackageInfo {
|
|
name: name.to_string(),
|
|
version: "unknown".to_string(),
|
|
description: description.to_string(),
|
|
installed: false, // Will be checked separately
|
|
section: "unknown".to_string(),
|
|
priority: "unknown".to_string(),
|
|
depends: Vec::new(),
|
|
})
|
|
}
|
|
|
|
/// Parse policy output
|
|
fn parse_policy_output(&self, package_name: &str, policy_output: &str) -> Option<PackageInfo> {
|
|
let mut package = PackageInfo {
|
|
name: package_name.to_string(),
|
|
version: "unknown".to_string(),
|
|
description: "unknown".to_string(),
|
|
installed: false,
|
|
section: "unknown".to_string(),
|
|
priority: "unknown".to_string(),
|
|
depends: Vec::new(),
|
|
};
|
|
|
|
for line in policy_output.lines() {
|
|
if line.contains("Installed:") {
|
|
let version = line.split("Installed:").nth(1)?.trim();
|
|
if version != "(none)" {
|
|
package.version = version.to_string();
|
|
package.installed = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
Some(package)
|
|
}
|
|
|
|
/// Parse show output
|
|
fn parse_show_output(&self, package_name: &str, show_output: &str) -> Option<PackageInfo> {
|
|
let mut package = PackageInfo {
|
|
name: package_name.to_string(),
|
|
version: "unknown".to_string(),
|
|
description: "unknown".to_string(),
|
|
installed: false,
|
|
section: "unknown".to_string(),
|
|
priority: "unknown".to_string(),
|
|
depends: Vec::new(),
|
|
};
|
|
|
|
for line in show_output.lines() {
|
|
if line.starts_with("Version:") {
|
|
package.version = line.split("Version:").nth(1)?.trim().to_string();
|
|
} else if line.starts_with("Description:") {
|
|
package.description = line.split("Description:").nth(1)?.trim().to_string();
|
|
} else if line.starts_with("Section:") {
|
|
package.section = line.split("Section:").nth(1)?.trim().to_string();
|
|
} else if line.starts_with("Priority:") {
|
|
package.priority = line.split("Priority:").nth(1)?.trim().to_string();
|
|
} else if line.starts_with("Depends:") {
|
|
let deps = line.split("Depends:").nth(1)?.trim();
|
|
if !deps.is_empty() {
|
|
package.depends = deps.split(',').map(|s| s.trim().to_string()).collect();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check if installed
|
|
package.installed = self.is_package_installed(package_name).unwrap_or(false);
|
|
|
|
Some(package)
|
|
}
|
|
}
|
|
|
|
/// Package information
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct PackageInfo {
|
|
pub name: String,
|
|
pub version: String,
|
|
pub description: String,
|
|
pub installed: bool,
|
|
pub section: String,
|
|
pub priority: String,
|
|
pub depends: Vec<String>,
|
|
}
|
|
|
|
impl PackageInfo {
|
|
pub fn new(name: &str) -> Self {
|
|
Self {
|
|
name: name.to_string(),
|
|
version: "0.0.0".to_string(),
|
|
description: "No description available".to_string(),
|
|
installed: false,
|
|
section: "unknown".to_string(),
|
|
priority: "unknown".to_string(),
|
|
depends: Vec::new(),
|
|
}
|
|
}
|
|
}
|