Initial commit

This commit is contained in:
zazawowow
2026-01-24 22:01:51 +00:00
commit 64cc3bc7fb
56 changed files with 4584 additions and 0 deletions

View File

@@ -0,0 +1,101 @@
// Parmanode to App Manifest converter
// Converts Parmanode module structure to Archipelago app manifest format
use archipelago_container::AppManifest;
use anyhow::{Context, Result};
use std::path::PathBuf;
use tokio::fs;
use tracing::info;
pub struct ParmanodeConverter;
impl ParmanodeConverter {
pub fn new() -> Self {
Self
}
/// Convert a Parmanode module directory to an App Manifest
pub async fn convert_to_manifest(&self, module_path: &PathBuf) -> Result<AppManifest> {
info!("Converting Parmanode module to manifest: {:?}", module_path);
// Read Parmanode module metadata if available
let module_name = module_path
.file_name()
.and_then(|n| n.to_str())
.unwrap_or("unknown")
.to_string();
// Try to detect what the module installs
let install_script = module_path.join("install.sh");
let script_content = if install_script.exists() {
fs::read_to_string(&install_script).await.ok()
} else {
None
};
// Infer app details from script content
let (app_id, image) = self.infer_from_script(&script_content)?;
// Create a basic manifest
let manifest_yaml = format!(
r#"
app:
id: {}
name: {}
version: 1.0.0
description: Converted from Parmanode module
container:
image: {}
pull_policy: if-not-present
resources:
cpu_limit: 1
memory_limit: 512Mi
disk_limit: 10Gi
security:
capabilities: []
readonly_root: true
network_policy: isolated
"#,
app_id, module_name, image
);
AppManifest::from_str(&manifest_yaml)
.context("Failed to create manifest from Parmanode module")
}
fn infer_from_script(&self, script_content: &Option<String>) -> Result<(String, String)> {
let content = script_content.as_deref().unwrap_or("");
// Try to detect Bitcoin Core
if content.contains("bitcoind") || content.contains("bitcoin-core") {
return Ok(("bitcoin-core".to_string(), "bitcoin/bitcoin:latest".to_string()));
}
// Try to detect LND
if content.contains("lnd") && !content.contains("lightning") {
return Ok(("lnd".to_string(), "lightninglabs/lnd:latest".to_string()));
}
// Try to detect Core Lightning
if content.contains("clightning") || content.contains("core-lightning") {
return Ok(("core-lightning".to_string(), "elementsproject/lightningd:latest".to_string()));
}
// Try to detect Electrs
if content.contains("electrs") {
return Ok(("electrs".to_string(), "romanz/electrs:latest".to_string()));
}
// Default fallback
Ok(("parmanode-module".to_string(), "alpine:latest".to_string()))
}
}
impl Default for ParmanodeConverter {
fn default() -> Self {
Self::new()
}
}

View File

@@ -0,0 +1,5 @@
pub mod script_runner;
pub mod converter;
pub use script_runner::ParmanodeScriptRunner;
pub use converter::ParmanodeConverter;

View File

@@ -0,0 +1,128 @@
// 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")))
}
}
}
}