mid coding commit

This commit is contained in:
zazawowow
2026-01-24 22:59:20 +00:00
parent 64cc3bc7fb
commit 731cd67cfb
2228 changed files with 135554 additions and 18 deletions
@@ -0,0 +1,119 @@
use anyhow::{Context, Result};
use std::path::{Path, PathBuf};
use tokio::fs;
pub struct DevDataManager {
dev_data_dir: PathBuf,
}
impl DevDataManager {
pub fn new(dev_data_dir: PathBuf) -> Self {
Self { dev_data_dir }
}
/// Get the dev data directory for an app
pub fn get_app_data_dir(&self, app_id: &str) -> PathBuf {
self.dev_data_dir.join(app_id)
}
/// Create data directory for an app
pub async fn create_app_data_dir(&self, app_id: &str) -> Result<PathBuf> {
let app_dir = self.get_app_data_dir(app_id);
fs::create_dir_all(&app_dir)
.await
.with_context(|| format!("Failed to create app data directory: {:?}", app_dir))?;
Ok(app_dir)
}
/// Map a volume source path to dev path
pub fn map_volume_path(&self, app_id: &str, volume_source: &str) -> PathBuf {
// If volume source is already in dev_data_dir, use it as-is
if volume_source.starts_with(self.dev_data_dir.to_str().unwrap_or("")) {
PathBuf::from(volume_source)
} else {
// Map production path to dev path
// e.g., /var/lib/archipelago/bitcoin -> /tmp/archipelago-dev/bitcoin
let app_dir = self.get_app_data_dir(app_id);
// Extract the relative path from the production path
if let Some(relative) = volume_source.strip_prefix("/var/lib/archipelago/") {
app_dir.join(relative)
} else if let Some(relative) = volume_source.strip_prefix("/var/lib/archipelago") {
app_dir.join(relative)
} else {
// If it doesn't match expected pattern, use app_id as base
app_dir.join(volume_source.trim_start_matches('/'))
}
}
}
/// Clean up app data directory
pub async fn cleanup_app_data(&self, app_id: &str) -> Result<()> {
let app_dir = self.get_app_data_dir(app_id);
if app_dir.exists() {
fs::remove_dir_all(&app_dir)
.await
.with_context(|| format!("Failed to remove app data directory: {:?}", app_dir))?;
}
Ok(())
}
/// Preserve app data (no-op for cleanup, used when removing container)
pub async fn preserve_app_data(&self, _app_id: &str) -> Result<()> {
// In dev mode, we might want to preserve data between container removals
// This is a no-op by default, but can be extended
Ok(())
}
/// Get all app data directories
pub async fn list_app_data_dirs(&self) -> Result<Vec<String>> {
if !self.dev_data_dir.exists() {
return Ok(vec![]);
}
let mut entries = fs::read_dir(&self.dev_data_dir)
.await
.with_context(|| format!("Failed to read dev data directory: {:?}", self.dev_data_dir))?;
let mut app_ids = Vec::new();
while let Some(entry) = entries.next_entry().await? {
if entry.file_type().await?.is_dir() {
if let Some(name) = entry.file_name().to_str() {
app_ids.push(name.to_string());
}
}
}
Ok(app_ids)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::PathBuf;
#[tokio::test]
async fn test_map_volume_path() {
let temp_dir = std::env::temp_dir().join("test-archipelago");
let manager = DevDataManager::new(temp_dir.clone());
let dev_path = manager.map_volume_path("bitcoin-core", "/var/lib/archipelago/bitcoin");
assert!(dev_path.to_string_lossy().contains("bitcoin-core"));
// Cleanup
let _ = tokio::fs::remove_dir_all(&temp_dir).await;
}
#[tokio::test]
async fn test_create_app_data_dir() {
let temp_dir = std::env::temp_dir().join("test-archipelago-2");
let manager = DevDataManager::new(temp_dir.clone());
let app_dir = manager.create_app_data_dir("test-app").await.unwrap();
assert!(app_dir.exists());
// Cleanup
let _ = tokio::fs::remove_dir_all(&temp_dir).await;
}
}
@@ -0,0 +1,262 @@
use archipelago_container::{
AppManifest, BitcoinSimulator, BitcoinSimulationMode, ContainerRuntime as ContainerRuntimeTrait,
ContainerStatus, PortManager,
};
use anyhow::{Context, Result};
use std::path::PathBuf;
use std::sync::Arc;
use tokio::sync::RwLock;
use crate::config::{Config, ContainerRuntime, BitcoinSimulation};
use crate::container::data_manager::DevDataManager;
pub struct DevContainerOrchestrator {
runtime: Arc<dyn ContainerRuntimeTrait>,
port_manager: Arc<PortManager>,
bitcoin_simulator: Arc<BitcoinSimulator>,
data_manager: Arc<DevDataManager>,
config: Config,
}
impl DevContainerOrchestrator {
pub async fn new(config: Config) -> Result<Self> {
let user = std::env::var("USER").unwrap_or_else(|_| "archipelago".to_string());
// Create runtime based on config
let runtime: Arc<dyn ContainerRuntimeTrait> = match &config.container_runtime {
ContainerRuntime::Podman => {
Arc::new(archipelago_container::PodmanRuntime::new(user.clone()))
}
ContainerRuntime::Docker => {
Arc::new(archipelago_container::DockerRuntime::new(user.clone()))
}
ContainerRuntime::Auto => {
Arc::new(
archipelago_container::AutoRuntime::new(user.clone())
.await
.context("Failed to create auto runtime")?,
)
}
};
let port_manager = Arc::new(PortManager::new(config.port_offset));
let bitcoin_simulator = Arc::new(BitcoinSimulator::new(
BitcoinSimulationMode::from(
match &config.bitcoin_simulation {
BitcoinSimulation::Mock => "mock",
BitcoinSimulation::Testnet => "testnet",
BitcoinSimulation::Mainnet => "mainnet",
BitcoinSimulation::None => "none",
}
),
));
let data_manager = Arc::new(DevDataManager::new(config.dev_data_dir.clone()));
Ok(Self {
runtime,
port_manager,
bitcoin_simulator,
data_manager,
config,
})
}
/// Install a container from a manifest
pub async fn install_container(
&self,
manifest: &AppManifest,
manifest_path: &str,
) -> Result<String> {
let app_id = &manifest.app.id;
let container_name = format!("archipelago-{}-dev", app_id);
// Check dependencies
if self.config.dev_mode {
// In dev mode, check if Bitcoin dependency can be satisfied
for dep in &manifest.app.dependencies {
if let archipelago_container::Dependency::App {
app_id: dep_id,
version: _,
} = dep
{
if dep_id == "bitcoin-core" {
if !self.bitcoin_simulator.is_bitcoin_available() {
return Err(anyhow::anyhow!(
"Bitcoin Core dependency not satisfied (simulation: {:?})",
self.bitcoin_simulator.mode()
));
}
}
}
}
}
// Allocate ports
let base_ports: Vec<u16> = manifest.app.ports.iter().map(|p| p.host).collect();
let _dev_ports = self
.port_manager
.allocate_ports(app_id, &base_ports)
.context("Failed to allocate ports")?;
// Create app data directory
self.data_manager
.create_app_data_dir(app_id)
.await
.context("Failed to create app data directory")?;
// Map volumes to dev paths
let mut dev_manifest = manifest.clone();
for volume in &mut dev_manifest.app.volumes {
let dev_path = self.data_manager.map_volume_path(app_id, &volume.source);
volume.source = dev_path.to_string_lossy().to_string();
}
// Pull image
self.runtime
.pull_image(
&manifest.app.container.image,
manifest.app.container.image_signature.as_deref(),
)
.await
.context("Failed to pull image")?;
// Create container with port offset
let port_offset = if self.config.dev_mode {
self.config.port_offset
} else {
0
};
self.runtime
.create_container(&dev_manifest, &container_name, port_offset)
.await
.context("Failed to create container")?;
Ok(container_name)
}
/// Start a container
pub async fn start_container(&self, app_id: &str) -> Result<()> {
let container_name = format!("archipelago-{}-dev", app_id);
self.runtime
.start_container(&container_name)
.await
.context("Failed to start container")?;
Ok(())
}
/// Stop a container
pub async fn stop_container(&self, app_id: &str) -> Result<()> {
let container_name = format!("archipelago-{}-dev", app_id);
self.runtime
.stop_container(&container_name)
.await
.context("Failed to stop container")?;
Ok(())
}
/// Remove a container
pub async fn remove_container(&self, app_id: &str, preserve_data: bool) -> Result<()> {
let container_name = format!("archipelago-{}-dev", app_id);
// Stop container first
let _ = self.runtime.stop_container(&container_name).await;
// Remove container
self.runtime
.remove_container(&container_name)
.await
.context("Failed to remove container")?;
// Release ports
let _ = self.port_manager.release_ports(app_id);
// Clean up or preserve data
if preserve_data {
self.data_manager.preserve_app_data(app_id).await?;
} else {
self.data_manager.cleanup_app_data(app_id).await?;
}
Ok(())
}
/// Get container status with dev port info
pub async fn get_container_status(&self, app_id: &str) -> Result<ContainerStatus> {
let container_name = format!("archipelago-{}-dev", app_id);
let mut status = self
.runtime
.get_container_status(&container_name)
.await
.context("Failed to get container status")?;
// Add dev port information
if let Some(ports) = self.port_manager.get_port_mapping(app_id) {
status.ports = ports.iter().map(|p| p.to_string()).collect();
}
Ok(status)
}
/// List all containers with dev info
pub async fn list_containers(&self) -> Result<Vec<ContainerStatus>> {
let containers = self
.runtime
.list_containers()
.await
.context("Failed to list containers")?;
// Filter to only archipelago containers and add port info
let mut result = Vec::new();
for container in containers {
if container.name.contains("archipelago-") {
// Extract app_id from container name
if let Some(app_id) = container.name.strip_prefix("archipelago-") {
if let Some(app_id) = app_id.strip_suffix("-dev") {
if let Some(ports) = self.port_manager.get_port_mapping(app_id) {
let mut container_with_ports = container.clone();
container_with_ports.ports = ports.iter().map(|p| p.to_string()).collect();
result.push(container_with_ports);
} else {
result.push(container);
}
}
}
}
}
Ok(result)
}
/// Get container logs
pub async fn get_container_logs(&self, app_id: &str, lines: u32) -> Result<Vec<String>> {
let container_name = format!("archipelago-{}-dev", app_id);
self.runtime
.get_container_logs(&container_name, lines)
.await
.context("Failed to get container logs")
}
/// Get health status
pub async fn get_health_status(&self, app_id: &str) -> Result<String> {
let status = self.get_container_status(app_id).await?;
match status.state {
archipelago_container::ContainerState::Running => Ok("healthy".to_string()),
archipelago_container::ContainerState::Stopped
| archipelago_container::ContainerState::Exited => Ok("unhealthy".to_string()),
archipelago_container::ContainerState::Created => Ok("starting".to_string()),
archipelago_container::ContainerState::Paused => Ok("paused".to_string()),
archipelago_container::ContainerState::Unknown(_) => Ok("unknown".to_string()),
}
}
/// Get port mapping for an app
pub fn get_port_mapping(&self, app_id: &str) -> Option<Vec<u16>> {
self.port_manager.get_port_mapping(app_id)
}
/// Get Bitcoin simulator
pub fn bitcoin_simulator(&self) -> &Arc<BitcoinSimulator> {
&self.bitcoin_simulator
}
}
+5
View File
@@ -0,0 +1,5 @@
pub mod data_manager;
pub mod dev_orchestrator;
pub use data_manager::DevDataManager;
pub use dev_orchestrator::DevContainerOrchestrator;