use serde::{Deserialize, Serialize};
use std::error::Error as StdError;
use std::fmt;
use tokio::time::Duration;
pub mod package_managers;
pub mod repair_strategies;
pub mod system_deps;
pub mod user_deps;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum RepairError {
SystemDependency(String),
UserDependency(String),
NetworkError(String),
PermissionError(String),
RollbackError(String),
ValidationError(String),
Unknown(String),
}
impl fmt::Display for RepairError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
RepairError::SystemDependency(msg) => write!(f, "System dependency error: {}", msg),
RepairError::UserDependency(msg) => write!(f, "User dependency error: {}", msg),
RepairError::NetworkError(msg) => write!(f, "Network error: {}", msg),
RepairError::PermissionError(msg) => write!(f, "Permission error: {}", msg),
RepairError::RollbackError(msg) => write!(f, "Rollback error: {}", msg),
RepairError::ValidationError(msg) => write!(f, "Validation error: {}", msg),
RepairError::Unknown(msg) => write!(f, "Unknown error: {}", msg),
}
}
}
impl StdError for RepairError {}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum RepairableError {
OutdatedSystemPackages,
MissingBuildTools,
OutdatedRustToolchain,
MissingSystemDependencies(Vec<String>),
MissingSolanaCli,
OutdatedSolanaCli,
MissingKeypair(String),
InvalidConfig,
MissingConfigDirectory,
ConnectivityIssues,
RpcEndpointFailure(String),
InsufficientPermissions(String),
SystemTuningRequired,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum RepairResult {
Success(String),
Partial(String, Vec<RepairableError>),
Failed(RepairError),
RequiresManualIntervention(String),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RepairConfig {
pub interactive: bool,
pub timeout: Duration,
pub allow_system_repairs: bool,
pub allow_user_repairs: bool,
}
impl Default for RepairConfig {
fn default() -> Self {
Self {
interactive: true,
timeout: Duration::from_secs(300), allow_system_repairs: true,
allow_user_repairs: true,
}
}
}
pub struct SelfRepairSystem {
config: RepairConfig,
diagnostics: crate::utils::diagnostics::DiagnosticCoordinator,
}
impl SelfRepairSystem {
pub fn new(config: RepairConfig) -> Self {
Self {
config,
diagnostics: crate::utils::diagnostics::DiagnosticCoordinator::new(),
}
}
pub fn with_default_config() -> Self {
Self::new(RepairConfig::default())
}
pub async fn analyze_error(&self, error: &str) -> Result<Vec<RepairableError>, RepairError> {
let mut repairable_errors = Vec::new();
if error.contains("net.core.rmem_max") && error.contains("too small") {
repairable_errors.push(RepairableError::SystemTuningRequired);
}
if error.contains("Error reading keypair file")
&& error.contains("No such file or directory")
{
if let Some(path) = extract_keypair_path(error) {
repairable_errors.push(RepairableError::MissingKeypair(path));
}
if !self.is_solana_cli_installed().await? {
repairable_errors.push(RepairableError::MissingSolanaCli);
}
if !self.config_directory_exists().await? {
repairable_errors.push(RepairableError::MissingConfigDirectory);
}
}
let system_health = self
.diagnostics
.check_system_health()
.await
.map_err(|e| RepairError::ValidationError(e.to_string()))?;
if system_health.issues.iter().any(|issue| {
issue.title.contains("System tuning") || issue.description.contains("kernel parameter")
}) {
repairable_errors.push(RepairableError::SystemTuningRequired);
}
if system_health.has_package_updates() {
repairable_errors.push(RepairableError::OutdatedSystemPackages);
}
if system_health.has_rust_updates() {
repairable_errors.push(RepairableError::OutdatedRustToolchain);
}
if let Some(missing_deps) = system_health.missing_build_tools() {
if !missing_deps.is_empty() {
repairable_errors.push(RepairableError::MissingSystemDependencies(missing_deps));
}
}
Ok(repairable_errors)
}
pub async fn repair_automatically(
&self,
errors: Vec<RepairableError>,
) -> Result<RepairResult, RepairError> {
if errors.is_empty() {
return Ok(RepairResult::Success("No issues detected".to_string()));
}
let snapshot_id: Option<String> = None;
let mut repair_transaction = repair_strategies::RepairTransaction::new()
.map_err(|e| RepairError::Unknown(e.to_string()))?;
for error in &errors {
self.add_repair_operation(&mut repair_transaction, error)
.await?;
}
match repair_transaction.execute().await {
Ok(_) => {
let validation_result = self.validate_repairs(&errors).await?;
match validation_result {
true => Ok(RepairResult::Success(
"All repairs completed successfully".to_string(),
)),
false => {
Ok(RepairResult::Failed(RepairError::ValidationError(
"Repairs failed validation (no rollback available - snapshots disabled)".to_string()
)))
}
}
}
Err(e) => {
Ok(RepairResult::Failed(e))
}
}
}
async fn is_solana_cli_installed(&self) -> Result<bool, RepairError> {
system_deps::check_solana_cli()
.await
.map_err(|e| RepairError::SystemDependency(e.to_string()))
}
async fn config_directory_exists(&self) -> Result<bool, RepairError> {
user_deps::check_config_directory()
.await
.map_err(|e| RepairError::UserDependency(e.to_string()))
}
async fn add_repair_operation(
&self,
transaction: &mut repair_strategies::RepairTransaction,
error: &RepairableError,
) -> Result<(), RepairError> {
match error {
RepairableError::MissingSolanaCli => {
transaction.add_operation(repair_strategies::RepairOperation::InstallSolanaCli);
}
RepairableError::MissingKeypair(path) => {
transaction.add_operation(repair_strategies::RepairOperation::GenerateKeypair(
path.clone(),
));
}
RepairableError::MissingConfigDirectory => {
transaction
.add_operation(repair_strategies::RepairOperation::CreateConfigDirectory);
}
RepairableError::SystemTuningRequired => {
if self.config.allow_system_repairs {
transaction
.add_operation(repair_strategies::RepairOperation::TuneSystemParameters);
}
}
RepairableError::OutdatedSystemPackages => {
if self.config.allow_system_repairs {
transaction
.add_operation(repair_strategies::RepairOperation::UpdateSystemPackages);
}
}
RepairableError::OutdatedRustToolchain => {
transaction.add_operation(repair_strategies::RepairOperation::UpdateRustToolchain);
}
RepairableError::MissingSystemDependencies(deps) => {
if self.config.allow_system_repairs {
transaction.add_operation(
repair_strategies::RepairOperation::InstallSystemDependencies(deps.clone()),
);
}
}
_ => {
return Err(RepairError::Unknown(format!(
"Unsupported repair type: {:?}",
error
)));
}
}
Ok(())
}
async fn validate_repairs(&self, _errors: &[RepairableError]) -> Result<bool, RepairError> {
let health_check = self
.diagnostics
.check_system_health()
.await
.map_err(|e| RepairError::ValidationError(e.to_string()))?;
Ok(health_check.is_healthy())
}
}
fn extract_keypair_path(error: &str) -> Option<String> {
if let Some(start) = error.find("Error reading keypair file ") {
let start = start + "Error reading keypair file ".len();
if let Some(end) = error[start..].find(':') {
return Some(error[start..start + end].to_string());
}
}
None
}
pub async fn read_keypair_with_repair(
keypair_path: &str,
) -> Result<solana_sdk::signature::Keypair, Box<dyn StdError>> {
match solana_sdk::signature::read_keypair_file(keypair_path) {
Ok(keypair) => Ok(keypair),
Err(err) => {
let repair_system = SelfRepairSystem::with_default_config();
let error_message = format!("Error reading keypair file {}: {}", keypair_path, err);
println!("๐ง OSVM detected a configuration issue. Attempting automatic repair...");
match repair_system.analyze_error(&error_message).await {
Ok(repairable_errors) => {
if !repairable_errors.is_empty() {
println!("๐ Issues detected:");
for error in &repairable_errors {
println!(" - {:?}", error);
}
let in_test = std::env::var("CARGO_TEST").is_ok()
|| std::env::var("OSVM_TEST_MODE").is_ok()
|| std::thread::current()
.name()
.map_or(false, |name| name.contains("test"));
let should_repair = if in_test {
println!("๐งช Test environment detected - skipping repair");
false
} else {
println!(
"\n๐ ๏ธ Would you like OSVM to fix these issues automatically? (Y/n):"
);
let mut input = String::new();
std::io::stdin().read_line(&mut input).unwrap();
input.trim().to_lowercase() != "n"
};
if should_repair {
match repair_system.repair_automatically(repairable_errors).await {
Ok(RepairResult::Success(msg)) => {
println!("โ
{}", msg);
println!("๐ Retrying keypair read...");
match solana_sdk::signature::read_keypair_file(keypair_path) {
Ok(keypair) => return Ok(keypair),
Err(retry_err) => {
return Err(format!("Repair succeeded but keypair still not readable: {}", retry_err).into());
}
}
}
Ok(result) => {
return Err(format!("Repair incomplete: {:?}", result).into());
}
Err(repair_err) => {
return Err(format!("Repair failed: {}", repair_err).into());
}
}
}
}
}
Err(analysis_err) => {
return Err(format!("Failed to analyze error: {}", analysis_err).into());
}
}
Err(err)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_extract_keypair_path() {
let error = "Error reading keypair file /home/user/.config/solana/id.json: No such file or directory";
let path = extract_keypair_path(error);
assert_eq!(path, Some("/home/user/.config/solana/id.json".to_string()));
}
#[test]
fn test_extract_keypair_path_no_match() {
let error = "Some other error message";
let path = extract_keypair_path(error);
assert_eq!(path, None);
}
#[tokio::test]
async fn test_repair_system_creation() {
let repair_system = SelfRepairSystem::with_default_config();
assert!(repair_system.config.interactive);
}
}