archy/core/parmanode/src/script_runner.rs

129 lines
4.4 KiB
Rust
Raw Normal View History

2026-01-24 22:01:51 +00:00
// Parmanode script runner - executes Parmanode installation scripts in containers
// Provides compatibility layer for existing Parmanode modules
use archipelago_container::{PodmanClient, AppManifest};
use anyhow::{Context, Result};
use std::path::PathBuf;
use std::process::Command;
use tokio::fs;
use tracing::{info, warn};
pub struct ParmanodeScriptRunner {
podman: PodmanClient,
scripts_dir: PathBuf,
}
impl ParmanodeScriptRunner {
pub fn new(scripts_dir: PathBuf) -> Self {
Self {
podman: PodmanClient::new("archipelago".to_string()),
scripts_dir,
}
}
/// Detect if a path contains a Parmanode script
pub fn is_parmanode_script(&self, path: &PathBuf) -> bool {
// Check for common Parmanode script patterns
path.file_name()
.and_then(|name| name.to_str())
.map(|name| {
name.ends_with(".sh") && (
name.contains("parmanode") ||
name.contains("bitcoin") ||
name.contains("lightning") ||
name.contains("electrs")
)
})
.unwrap_or(false)
}
/// Run a Parmanode script in an isolated container
pub async fn run_script(&self, script_path: &PathBuf) -> Result<()> {
info!("Running Parmanode script: {:?}", script_path);
// Create a temporary container manifest for the script
let script_name = script_path
.file_stem()
.and_then(|s| s.to_str())
.unwrap_or("parmanode-script");
// Create a minimal container to run the script
let container_name = format!("parmanode-{}", script_name);
// Copy script to a location accessible by containers
let script_content = fs::read_to_string(script_path).await
.context("Failed to read Parmanode script")?;
// Create a wrapper script that runs in Alpine
let wrapper_script = format!(
r#"#!/bin/sh
set -e
{}
"#,
script_content
);
// Write wrapper to temp location
let temp_script = format!("/tmp/parmanode-{}.sh", script_name);
fs::write(&temp_script, wrapper_script).await
.context("Failed to write wrapper script")?;
// Make executable
Command::new("chmod")
.arg("+x")
.arg(&temp_script)
.output()
.context("Failed to make script executable")?;
// Run script in a temporary Alpine container
let output = Command::new("podman")
.arg("run")
.arg("--rm")
.arg("--volume")
.arg(format!("{}:/script.sh:ro", temp_script))
.arg("alpine:latest")
.arg("sh")
.arg("/script.sh")
.output()
.context("Failed to execute Parmanode script in container")?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(anyhow::anyhow!("Parmanode script failed: {}", stderr));
}
info!("Parmanode script completed successfully");
Ok(())
}
/// Install a Parmanode module (runs script and sets up container)
pub async fn install_module(&self, module_path: &PathBuf) -> Result<String> {
// Find the main installation script
let install_script = module_path.join("install.sh");
if !install_script.exists() {
return Err(anyhow::anyhow!("No install.sh found in Parmanode module"));
}
// Run the installation script
self.run_script(&install_script).await?;
// Try to convert to app manifest for future management
let converter = crate::converter::ParmanodeConverter::new();
match converter.convert_to_manifest(module_path).await {
Ok(manifest) => {
info!("Converted Parmanode module to app manifest");
// TODO: Save manifest for future use
Ok(manifest.app.id)
}
Err(e) => {
warn!("Failed to convert Parmanode module: {}", e);
// Return a generic ID
Ok(format!("parmanode-{}",
module_path.file_name()
.and_then(|n| n.to_str())
.unwrap_or("module")))
}
}
}
}