use crate::commands::remote::{RemoteLogsArgs, RemoteUpArgs};
use crate::context::CliContext;
use crate::paths;
use crate::services::{DockerService, RemoteImageService};
use crate::types::project::load_project_config;
use anyhow::{Context, Result};
pub async fn handle_remote_up(ctx: &mut CliContext, args: &RemoteUpArgs) -> Result<()> {
let config_path = ctx.working_dir.join(paths::PROJECT_CONFIG);
let config = load_project_config(&config_path).await.context(format!(
"Failed to load {}. Are you in a mecha10 project directory?",
paths::PROJECT_CONFIG
))?;
let targets = config.targets.as_ref();
let has_remote_nodes = targets.is_some_and(|t| t.has_remote_nodes());
if !has_remote_nodes {
println!();
println!("No remote nodes configured in mecha10.json.");
println!();
println!("To configure remote nodes, add a targets section:");
println!();
println!(" \"targets\": {{");
println!(" \"remote\": [\"@mecha10/object-detector\", \"@mecha10/image-classifier\"]");
println!(" }}");
println!();
return Ok(());
}
let targets = targets.unwrap();
let remote_nodes = targets.remote_nodes();
println!();
println!("Starting remote nodes: {}", remote_nodes.join(", "));
println!();
let compose_path = ctx.working_dir.join(paths::docker::COMPOSE_REMOTE_FILE);
if !compose_path.exists() {
return Err(anyhow::anyhow!(
"Remote compose file not found: {}\n\
Run `mecha10 init` to create project files, or create {} manually.",
compose_path.display(),
paths::docker::COMPOSE_REMOTE_FILE
));
}
let compose_file_path = compose_path.to_string_lossy().to_string();
let docker = DockerService::with_compose_file(&compose_file_path);
docker.check_installation()?;
docker.check_daemon()?;
let platform = args.arch.docker_platform();
std::env::set_var("MECHA10_PLATFORM", platform);
println!("Platform: {} ({:?})", platform, args.arch);
let remote_image_service = RemoteImageService::new();
let detection = if args.build {
println!("Forcing local build (--build flag)");
None
} else {
let result = remote_image_service.detect_prebuilt(targets, &ctx.working_dir).await?;
if result.can_use_prebuilt {
Some(result)
} else {
if let Some(reason) = &result.reason {
println!("Building locally: {}", reason);
}
None
}
};
if let Some(prebuilt) = detection {
if let Some(image_tag) = prebuilt.image_tag {
println!("Using pre-built image: {}", image_tag);
std::env::set_var(RemoteImageService::image_env_var(), &image_tag);
}
docker.compose_up(true).await?;
} else {
build_and_start(&docker, true).await?;
}
println!();
println!("Remote nodes started.");
println!();
println!("Useful commands:");
println!(" mecha10 remote logs -f # View logs (follow)");
println!(" mecha10 remote ps # List running nodes");
println!(" mecha10 remote down # Stop container");
println!();
Ok(())
}
async fn build_and_start(docker: &DockerService, detach: bool) -> Result<()> {
docker.compose_build(None)?;
docker.compose_up(detach).await?;
Ok(())
}
pub async fn handle_remote_down(ctx: &mut CliContext) -> Result<()> {
let compose_path = ctx.working_dir.join(paths::docker::COMPOSE_REMOTE_FILE);
if !compose_path.exists() {
println!("No remote compose file found.");
return Ok(());
}
let compose_file_path = compose_path.to_string_lossy().to_string();
let docker = DockerService::with_compose_file(&compose_file_path);
docker.check_installation()?;
println!("Stopping remote nodes...");
docker.compose_down().await?;
println!();
println!("Remote nodes stopped.");
println!();
Ok(())
}
pub async fn handle_remote_logs(ctx: &mut CliContext, args: &RemoteLogsArgs) -> Result<()> {
let compose_path = ctx.working_dir.join(paths::docker::COMPOSE_REMOTE_FILE);
if !compose_path.exists() {
return Err(anyhow::anyhow!("No remote compose file found."));
}
let compose_file_path = compose_path.to_string_lossy().to_string();
let docker = DockerService::with_compose_file(&compose_file_path);
docker.check_installation()?;
if let Some(node) = &args.node {
println!("Showing logs for node: {}", node);
println!("(Note: All nodes run in single container, filtering by log content)");
println!();
}
let tail = if args.tail > 0 { Some(args.tail as usize) } else { None };
docker.compose_logs(Some("mecha10-remote"), args.follow, tail)?;
Ok(())
}
pub async fn handle_remote_ps(ctx: &mut CliContext) -> Result<()> {
let compose_path = ctx.working_dir.join(paths::docker::COMPOSE_REMOTE_FILE);
if !compose_path.exists() {
println!("No remote compose file found.");
return Ok(());
}
let compose_file_path = compose_path.to_string_lossy().to_string();
let docker = DockerService::with_compose_file(&compose_file_path);
docker.check_installation()?;
let status = std::process::Command::new("docker")
.arg("compose")
.arg("-f")
.arg(&compose_file_path)
.arg("ps")
.status()
.context("Failed to run docker compose ps")?;
if !status.success() {
return Err(anyhow::anyhow!("docker compose ps failed"));
}
Ok(())
}
pub async fn handle_remote_build(ctx: &mut CliContext) -> Result<()> {
let compose_path = ctx.working_dir.join(paths::docker::COMPOSE_REMOTE_FILE);
if !compose_path.exists() {
return Err(anyhow::anyhow!(
"Remote compose file not found: {}\n\
Run `mecha10 init` to create project files.",
compose_path.display()
));
}
let compose_file_path = compose_path.to_string_lossy().to_string();
let docker = DockerService::with_compose_file(&compose_file_path);
docker.check_installation()?;
docker.check_daemon()?;
println!("Building remote container...");
docker.compose_build(None)?;
println!();
println!("Remote container built successfully.");
println!();
println!("Run `mecha10 remote up` to start.");
println!();
Ok(())
}