#![allow(dead_code)]
use anyhow::{Context, Result, bail};
use std::process::Command;
use crate::permissions::Permissions;
pub fn apple_system_running() -> bool {
Command::new("container")
.args(["system", "status"])
.output()
.map(|o| o.status.success() && String::from_utf8_lossy(&o.stdout).contains("is running"))
.unwrap_or(false)
}
pub fn start_apple_system() -> Result<()> {
if apple_system_running() {
return Ok(());
}
eprintln!("Starting Apple container system...");
let output = Command::new("sh")
.args(["-c", "echo 'Y' | container system start"])
.output()
.context("Failed to start Apple container system")?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
if !stderr.contains("already") {
bail!("Failed to start Apple container system: {}", stderr);
}
}
std::thread::sleep(std::time::Duration::from_millis(500));
Ok(())
}
pub fn apple_containers_available() -> bool {
if !cfg!(target_os = "macos") {
return false;
}
Command::new("container")
.arg("--version")
.output()
.map(|o| o.status.success())
.unwrap_or(false)
}
pub fn macos_version_supported() -> bool {
if !cfg!(target_os = "macos") {
return false;
}
let output = Command::new("sw_vers").arg("-productVersion").output().ok();
if let Some(output) = output
&& let Ok(version) = String::from_utf8(output.stdout)
&& let Some(major) = version.trim().split('.').next()
&& let Ok(major_num) = major.parse::<u32>()
{
return major_num >= 26;
}
false
}
pub struct AppleContainerSandbox {
pub name: String,
container_id: Option<String>,
}
impl AppleContainerSandbox {
pub fn new(name: &str) -> Self {
Self {
name: name.to_string(),
container_id: None,
}
}
pub async fn start_with_permissions(&mut self, image: &str, perms: &Permissions) -> Result<()> {
let container_name = format!("agentkernel-{}", self.name);
let _ = Command::new("container")
.args(["delete", "-f", &container_name])
.output();
let mut args = vec![
"run".to_string(),
"-d".to_string(), "--name".to_string(),
container_name.clone(),
];
if let Some(cpu_percent) = perms.max_cpu_percent {
let cpus = std::cmp::max(1, cpu_percent / 100);
args.push("--cpus".to_string());
args.push(cpus.to_string());
}
if let Some(mem) = perms.max_memory_mb {
args.push("--memory".to_string());
args.push(format!("{}M", mem));
}
if !perms.network {
}
if perms.mount_cwd
&& let Ok(cwd) = std::env::current_dir()
{
args.push("-v".to_string());
args.push(format!("{}:/app", cwd.display()));
args.push("-w".to_string());
args.push("/app".to_string());
}
if perms.read_only_root {
args.push("--read-only".to_string());
}
if perms.pass_env {
for var in ["PATH", "HOME", "USER", "LANG", "LC_ALL", "TERM"] {
if let Ok(val) = std::env::var(var) {
args.push("-e".to_string());
args.push(format!("{}={}", var, val));
}
}
}
args.push(image.to_string());
args.push("sleep".to_string());
args.push("infinity".to_string());
let output = Command::new("container")
.args(&args)
.output()
.context("Failed to start Apple container")?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
bail!("Failed to start container: {}", stderr);
}
self.container_id = Some(container_name);
Ok(())
}
pub async fn execute(&self, cmd: &[String]) -> Result<String> {
let container_id = self
.container_id
.as_ref()
.ok_or_else(|| anyhow::anyhow!("Container not started"))?;
let mut args = vec!["exec".to_string(), container_id.clone()];
args.extend(cmd.iter().cloned());
let output = Command::new("container")
.args(&args)
.output()
.context("Failed to execute command in Apple container")?;
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
let stderr = String::from_utf8_lossy(&output.stderr).to_string();
if !output.status.success() && !stderr.is_empty() {
bail!("Command failed: {}", stderr);
}
if stderr.is_empty() {
Ok(stdout)
} else {
Ok(format!("{}{}", stdout, stderr))
}
}
pub async fn stop(&mut self) -> Result<()> {
if let Some(container_id) = &self.container_id {
let output = Command::new("container")
.args(["stop", "-t", "1", container_id])
.output()
.context("Failed to stop Apple container")?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
eprintln!("Warning: stop returned error: {}", stderr);
}
}
Ok(())
}
pub async fn remove(&mut self) -> Result<()> {
if let Some(container_id) = &self.container_id {
let _ = Command::new("container")
.args(["delete", "-f", container_id])
.output();
self.container_id = None;
}
Ok(())
}
#[allow(dead_code)]
pub fn is_running(&self) -> bool {
if let Some(container_id) = &self.container_id {
let output = Command::new("container")
.args(["ls", "--filter", &format!("name={}", container_id)])
.output()
.ok();
if let Some(output) = output {
let stdout = String::from_utf8_lossy(&output.stdout);
return stdout.contains(container_id);
}
}
false
}
#[allow(dead_code)]
pub async fn run_ephemeral(
&mut self,
image: &str,
cmd: &[String],
perms: &Permissions,
) -> Result<String> {
let container_name = format!("agentkernel-{}", self.name);
let _ = Command::new("container")
.args(["delete", "-f", &container_name])
.output();
let mut args = vec![
"run".to_string(),
"--rm".to_string(), "--name".to_string(),
container_name.clone(),
];
if let Some(cpu_percent) = perms.max_cpu_percent {
let cpus = std::cmp::max(1, cpu_percent / 100);
args.push("--cpus".to_string());
args.push(cpus.to_string());
}
if let Some(mem) = perms.max_memory_mb {
args.push("--memory".to_string());
args.push(format!("{}M", mem));
}
if perms.mount_cwd
&& let Ok(cwd) = std::env::current_dir()
{
args.push("-v".to_string());
args.push(format!("{}:/app", cwd.display()));
args.push("-w".to_string());
args.push("/app".to_string());
}
if perms.read_only_root {
args.push("--read-only".to_string());
}
if perms.pass_env {
for var in ["PATH", "HOME", "USER", "LANG", "LC_ALL", "TERM"] {
if let Ok(val) = std::env::var(var) {
args.push("-e".to_string());
args.push(format!("{}={}", var, val));
}
}
}
args.push(image.to_string());
args.extend(cmd.iter().cloned());
let output = Command::new("container")
.args(&args)
.output()
.context("Failed to run Apple container")?;
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
let stderr = String::from_utf8_lossy(&output.stderr).to_string();
if !output.status.success() && !stderr.is_empty() {
bail!("Container command failed: {}", stderr);
}
if stderr.is_empty() {
Ok(stdout)
} else {
Ok(format!("{}{}", stdout, stderr))
}
}
}
impl Drop for AppleContainerSandbox {
fn drop(&mut self) {
if let Some(container_id) = &self.container_id {
let _ = Command::new("container")
.args(["delete", "-f", container_id])
.output();
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_apple_containers_check() {
let _ = apple_containers_available();
let _ = macos_version_supported();
}
}