feat: add Apple Container support with comprehensive documentation and dev tools

Add native Apple Container support for better performance on macOS while
maintaining full Docker compatibility. Enhance documentation with memory system
details, development guidelines, and sandbox setup instructions. Improve dev
experience with container image pre-pulling and unified cleanup tools.

Key changes:
- Auto-detect and prefer Apple Container on macOS with Docker fallback
- Add APPLE_CONTAINER.md with complete usage and troubleshooting guide
- Document memory system architecture in CLAUDE.md
- Add make setup-sandbox for pre-pulling container images
- Create cleanup-containers.sh for cross-runtime container cleanup
- Update all related documentation (README, SETUP, config examples)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
hetao
2026-02-03 20:41:36 +08:00
parent b773bae407
commit 5959ef87b8
8 changed files with 556 additions and 30 deletions

View File

@@ -4,17 +4,18 @@
help:
@echo "DeerFlow Development Commands:"
@echo " make check - Check if all required tools are installed"
@echo " make install - Install all dependencies (frontend + backend)"
@echo " make dev - Start all services (frontend + backend + nginx on localhost:2026)"
@echo " make stop - Stop all running services"
@echo " make clean - Clean up processes and temporary files"
@echo " make check - Check if all required tools are installed"
@echo " make install - Install all dependencies (frontend + backend)"
@echo " make setup-sandbox - Pre-pull sandbox container image (recommended)"
@echo " make dev - Start all services (frontend + backend + nginx on localhost:2026)"
@echo " make stop - Stop all running services"
@echo " make clean - Clean up processes and temporary files"
@echo ""
@echo "Docker Development Commands:"
@echo " make docker-init - Initialize and install dependencies in Docker containers"
@echo " make docker-start - Start all services in Docker (localhost:2026)"
@echo " make docker-stop - Stop Docker development services"
@echo " make docker-logs - View Docker development logs"
@echo " make docker-logs - View Docker development logs"
@echo " make docker-logs-web - View Docker frontend logs"
@echo " make docker-logs-api - View Docker backend logs"
@@ -100,6 +101,43 @@ install:
@echo "Installing frontend dependencies..."
@cd frontend && pnpm install
@echo "✓ All dependencies installed"
@echo ""
@echo "=========================================="
@echo " Optional: Pre-pull Sandbox Image"
@echo "=========================================="
@echo ""
@echo "If you plan to use Docker/Container-based sandbox, you can pre-pull the image:"
@echo " make setup-sandbox"
@echo ""
# Pre-pull sandbox Docker image (optional but recommended)
setup-sandbox:
@echo "=========================================="
@echo " Pre-pulling Sandbox Container Image"
@echo "=========================================="
@echo ""
@IMAGE=$$(grep -A 20 "# sandbox:" config.yaml 2>/dev/null | grep "image:" | awk '{print $$2}' | head -1); \
if [ -z "$$IMAGE" ]; then \
IMAGE="enterprise-public-cn-beijing.cr.volces.com/vefaas-public/all-in-one-sandbox:latest"; \
echo "Using default image: $$IMAGE"; \
else \
echo "Using configured image: $$IMAGE"; \
fi; \
echo ""; \
if command -v container >/dev/null 2>&1 && [ "$$(uname)" = "Darwin" ]; then \
echo "Detected Apple Container on macOS, pulling image..."; \
container pull "$$IMAGE" || echo "⚠ Apple Container pull failed, will try Docker"; \
fi; \
if command -v docker >/dev/null 2>&1; then \
echo "Pulling image using Docker..."; \
docker pull "$$IMAGE"; \
echo ""; \
echo "✓ Sandbox image pulled successfully"; \
else \
echo "✗ Neither Docker nor Apple Container is available"; \
echo " Please install Docker: https://docs.docker.com/get-docker/"; \
exit 1; \
fi
# Start all services
dev:
@@ -110,7 +148,7 @@ dev:
@-nginx -c $(PWD)/docker/nginx/nginx.local.conf -p $(PWD) -s quit 2>/dev/null || true
@sleep 1
@-pkill -9 nginx 2>/dev/null || true
@-docker ps -q --filter "name=deer-flow-sandbox" | xargs -r docker stop 2>/dev/null || true
@-./scripts/cleanup-containers.sh deer-flow-sandbox 2>/dev/null || true
@sleep 1
@echo ""
@echo "=========================================="
@@ -132,7 +170,7 @@ dev:
sleep 1; \
pkill -9 nginx 2>/dev/null || true; \
echo "Cleaning up sandbox containers..."; \
docker ps -q --filter "name=deer-flow-sandbox" | xargs -r docker stop 2>/dev/null || true; \
./scripts/cleanup-containers.sh deer-flow-sandbox 2>/dev/null || true; \
echo "✓ All services stopped"; \
exit 0; \
}; \
@@ -183,7 +221,7 @@ stop:
@sleep 1
@-pkill -9 nginx 2>/dev/null || true
@echo "Cleaning up sandbox containers..."
@-docker ps -q --filter "name=deer-flow-sandbox" | xargs -r docker stop 2>/dev/null || true
@-./scripts/cleanup-containers.sh deer-flow-sandbox 2>/dev/null || true
@echo "✓ All services stopped"
# Clean up

View File

@@ -41,18 +41,25 @@ If you prefer running services locally:
make install
```
3. **Start services**:
3. **(Optional) Pre-pull sandbox image**:
```bash
# Recommended if using Docker/Container-based sandbox
make setup-sandbox
```
4. **Start services**:
```bash
make dev
```
4. **Access**: http://localhost:2026
5. **Access**: http://localhost:2026
See [CONTRIBUTING.md](CONTRIBUTING.md) for detailed local development guide.
## Features
- 🤖 **LangGraph-based Agents** - Multi-agent orchestration with sophisticated workflows
- 🧠 **Persistent Memory** - LLM-powered context retention across conversations with automatic fact extraction
- 🔧 **Model Context Protocol (MCP)** - Extensible tool integration
- 🎯 **Skills System** - Reusable agent capabilities
- 🛡️ **Sandbox Execution** - Safe code execution environment

View File

@@ -40,6 +40,17 @@ deer-flow/
└── custom/ # Custom skills (gitignored)
```
## Important Development Guidelines
### Documentation Update Policy
**CRITICAL: Always update README.md and CLAUDE.md after every code change**
When making code changes, you MUST update the relevant documentation:
- Update `README.md` for user-facing changes (features, setup, usage instructions)
- Update `CLAUDE.md` for development changes (architecture, commands, workflows, internal systems)
- Keep documentation synchronized with the codebase at all times
- Ensure accuracy and timeliness of all documentation
## Commands
**Root directory** (for full application):
@@ -202,7 +213,49 @@ Configuration priority:
5. `TitleMiddleware` - Generates conversation titles
6. `TodoListMiddleware` - Tracks multi-step tasks (if plan_mode enabled)
7. `ViewImageMiddleware` - Injects image details for vision models
8. `ClarificationMiddleware` - Handles clarification requests (must be last)
8. `MemoryMiddleware` - Automatic context retention and personalization (if enabled)
9. `ClarificationMiddleware` - Handles clarification requests (must be last)
**Memory System** (`src/agents/memory/`)
- LLM-powered personalization layer that automatically extracts and stores user context across conversations
- Components:
- `updater.py` - LLM-based memory updates with fact extraction and file I/O
- `queue.py` - Debounced update queue for batching and performance optimization
- `prompt.py` - Prompt templates and formatting utilities for memory updates
- `MemoryMiddleware` (`src/agents/middlewares/memory_middleware.py`) - Queues conversations for memory updates
- Gateway API (`src/gateway/routers/memory.py`) - REST endpoints for memory management
- Storage: JSON file at `backend/.deer-flow/memory.json`
**Memory Data Structure**:
- **User Context** (current state):
- `workContext` - Work-related information (job, projects, technologies)
- `personalContext` - Preferences, communication style, background
- `topOfMind` - Current focus areas and immediate priorities
- **History** (temporal context):
- `recentMonths` - Recent activities and discussions
- `earlierContext` - Important historical context
- `longTermBackground` - Persistent background information
- **Facts** (structured knowledge):
- Discrete facts with categories: `preference`, `knowledge`, `context`, `behavior`, `goal`
- Each fact includes: `id`, `content`, `category`, `confidence` (0-1), `createdAt`, `source` (thread ID)
- Confidence threshold (default 0.7) filters low-quality facts
- Max facts limit (default 100) keeps highest-confidence facts
**Memory Workflow**:
1. **Post-Interaction**: `MemoryMiddleware` filters messages (user inputs + final AI responses only) and queues conversation
2. **Debounced Processing**: Queue waits 30s (configurable), batches multiple updates, resets timer on new updates
3. **LLM-Based Update**: Background thread loads memory, formats conversation, invokes LLM to extract:
- Updated context summaries (1-3 sentences each)
- New facts with confidence scores and categories
- Facts to remove (contradictions)
4. **Storage**: Applies updates atomically to `memory.json` with cache invalidation (mtime-based)
5. **Injection**: Next interaction loads memory, formats top 15 facts + context, injects into `<memory>` tags in system prompt
**Memory API Endpoints** (`/api/memory`):
- `GET /api/memory` - Retrieve current memory data
- `POST /api/memory/reload` - Force reload from file (invalidates cache)
- `GET /api/memory/config` - Get memory configuration
- `GET /api/memory/status` - Get both config and data
### Config Schema
@@ -215,6 +268,15 @@ Models, tools, sandbox providers, skills, and middleware settings are configured
- `skills.container_path`: Container mount path (default: `/mnt/skills`)
- `title`: Automatic thread title generation configuration
- `summarization`: Automatic conversation summarization configuration
- `memory`: Memory system configuration
- `enabled`: Master switch (boolean)
- `storage_path`: Path to memory.json file (relative to backend/)
- `debounce_seconds`: Wait time before processing updates (default: 30)
- `model_name`: LLM model for memory updates (null = use default model)
- `max_facts`: Maximum facts to store (default: 100)
- `fact_confidence_threshold`: Minimum confidence to store fact (default: 0.7)
- `injection_enabled`: Inject memory into system prompt (boolean)
- `max_injection_tokens`: Token limit for memory injection (default: 2000)
**Extensions Configuration Schema** (`extensions_config.json`):
- `mcpServers`: Map of MCP server name to configuration
@@ -307,6 +369,29 @@ For models with `supports_vision: true`:
- `view_image_tool` added to agent's toolset
- Images automatically converted and injected into state
### Memory System
Persistent context retention and personalization across conversations:
- **Automatic Extraction**: LLM analyzes conversations to extract user context, facts, and preferences
- **Structured Storage**: Maintains user context, history, and confidence-scored facts in JSON format
- **Smart Filtering**: Only processes meaningful messages (user inputs + final AI responses)
- **Debounced Updates**: Batches updates to minimize LLM calls (configurable wait time)
- **System Prompt Injection**: Automatically injects relevant memory context into agent prompts
- **Cache Optimization**: File modification time-based cache invalidation for external edits
- **Thread Safety**: Locks protect queue and cache for concurrent access
- **REST API**: Full CRUD operations via `/api/memory` endpoints
- **Frontend Integration**: Memory settings page for viewing and managing memory data
**Configuration**: Controlled via `memory` section in `config.yaml`
- Enable/disable memory system
- Configure storage path, debounce timing, fact limits
- Control system prompt injection and token limits
- Set confidence thresholds for fact storage
**Storage Location**: `backend/.deer-flow/memory.json`
See configuration section for detailed settings.
## Code Style
- Uses `ruff` for linting and formatting

View File

@@ -0,0 +1,238 @@
# Apple Container Support
DeerFlow now supports Apple Container as the preferred container runtime on macOS, with automatic fallback to Docker.
## Overview
Starting with this version, DeerFlow automatically detects and uses Apple Container on macOS when available, falling back to Docker when:
- Apple Container is not installed
- Running on non-macOS platforms
This provides better performance on Apple Silicon Macs while maintaining compatibility across all platforms.
## Benefits
### On Apple Silicon Macs with Apple Container:
- **Better Performance**: Native ARM64 execution without Rosetta 2 translation
- **Lower Resource Usage**: Lighter weight than Docker Desktop
- **Native Integration**: Uses macOS Virtualization.framework
### Fallback to Docker:
- Full backward compatibility
- Works on all platforms (macOS, Linux, Windows)
- No configuration changes needed
## Requirements
### For Apple Container (macOS only):
- macOS 15.0 or later
- Apple Silicon (M1/M2/M3/M4)
- Apple Container CLI installed
### Installation:
```bash
# Download from GitHub releases
# https://github.com/apple/container/releases
# Verify installation
container --version
# Start the service
container system start
```
### For Docker (all platforms):
- Docker Desktop or Docker Engine
## How It Works
### Automatic Detection
The `AioSandboxProvider` automatically detects the available container runtime:
1. On macOS: Try `container --version`
- Success → Use Apple Container
- Failure → Fall back to Docker
2. On other platforms: Use Docker directly
### Runtime Differences
Both runtimes use nearly identical command syntax:
**Container Startup:**
```bash
# Apple Container
container run --rm -d -p 8080:8080 -v /host:/container -e KEY=value image
# Docker
docker run --rm -d -p 8080:8080 -v /host:/container -e KEY=value image
```
**Container Cleanup:**
```bash
# Apple Container (with --rm flag)
container stop <id> # Auto-removes due to --rm
# Docker (with --rm flag)
docker stop <id> # Auto-removes due to --rm
```
### Implementation Details
The implementation is in `backend/src/community/aio_sandbox/aio_sandbox_provider.py`:
- `_detect_container_runtime()`: Detects available runtime at startup
- `_start_container()`: Uses detected runtime, skips Docker-specific options for Apple Container
- `_stop_container()`: Uses appropriate stop command for the runtime
## Configuration
No configuration changes are needed! The system works automatically.
However, you can verify the runtime in use by checking the logs:
```
INFO:src.community.aio_sandbox.aio_sandbox_provider:Detected Apple Container: container version 0.1.0
INFO:src.community.aio_sandbox.aio_sandbox_provider:Starting sandbox container using container: ...
```
Or for Docker:
```
INFO:src.community.aio_sandbox.aio_sandbox_provider:Apple Container not available, falling back to Docker
INFO:src.community.aio_sandbox.aio_sandbox_provider:Starting sandbox container using docker: ...
```
## Container Images
Both runtimes use OCI-compatible images. The default image works with both:
```yaml
sandbox:
use: src.community.aio_sandbox:AioSandboxProvider
image: enterprise-public-cn-beijing.cr.volces.com/vefaas-public/all-in-one-sandbox:latest # Default image
```
Make sure your images are available for the appropriate architecture:
- ARM64 for Apple Container on Apple Silicon
- AMD64 for Docker on Intel Macs
- Multi-arch images work on both
### Pre-pulling Images (Recommended)
**Important**: Container images are typically large (500MB+) and are pulled on first use, which can cause a long wait time without clear feedback.
**Best Practice**: Pre-pull the image during setup:
```bash
# From project root
make setup-sandbox
```
This command will:
1. Read the configured image from `config.yaml` (or use default)
2. Detect available runtime (Apple Container or Docker)
3. Pull the image with progress indication
4. Verify the image is ready for use
**Manual pre-pull**:
```bash
# Using Apple Container
container pull enterprise-public-cn-beijing.cr.volces.com/vefaas-public/all-in-one-sandbox:latest
# Using Docker
docker pull enterprise-public-cn-beijing.cr.volces.com/vefaas-public/all-in-one-sandbox:latest
```
If you skip pre-pulling, the image will be automatically pulled on first agent execution, which may take several minutes depending on your network speed.
## Cleanup Scripts
The project includes a unified cleanup script that handles both runtimes:
**Script:** `scripts/cleanup-containers.sh`
**Usage:**
```bash
# Clean up all DeerFlow sandbox containers
./scripts/cleanup-containers.sh deer-flow-sandbox
# Custom prefix
./scripts/cleanup-containers.sh my-prefix
```
**Makefile Integration:**
All cleanup commands in `Makefile` automatically handle both runtimes:
```bash
make stop # Stops all services and cleans up containers
make clean # Full cleanup including logs
```
## Testing
Test the container runtime detection:
```bash
cd backend
python test_container_runtime.py
```
This will:
1. Detect the available runtime
2. Optionally start a test container
3. Verify connectivity
4. Clean up
## Troubleshooting
### Apple Container not detected on macOS
1. Check if installed:
```bash
which container
container --version
```
2. Check if service is running:
```bash
container system start
```
3. Check logs for detection:
```bash
# Look for detection message in application logs
grep "container runtime" logs/*.log
```
### Containers not cleaning up
1. Manually check running containers:
```bash
# Apple Container
container list
# Docker
docker ps
```
2. Run cleanup script manually:
```bash
./scripts/cleanup-containers.sh deer-flow-sandbox
```
### Performance issues
- Apple Container should be faster on Apple Silicon
- If experiencing issues, you can force Docker by temporarily renaming the `container` command:
```bash
# Temporary workaround - not recommended for permanent use
sudo mv /opt/homebrew/bin/container /opt/homebrew/bin/container.bak
```
## References
- [Apple Container GitHub](https://github.com/apple/container)
- [Apple Container Documentation](https://github.com/apple/container/blob/main/docs/)
- [OCI Image Spec](https://github.com/opencontainers/image-spec)

View File

@@ -49,6 +49,22 @@ The backend searches for `config.yaml` in this order:
**Recommended**: Place `config.yaml` in project root (`deer-flow/config.yaml`).
## Sandbox Setup (Optional but Recommended)
If you plan to use Docker/Container-based sandbox (configured in `config.yaml` under `sandbox.use: src.community.aio_sandbox:AioSandboxProvider`), it's highly recommended to pre-pull the container image:
```bash
# From project root
make setup-sandbox
```
**Why pre-pull?**
- The sandbox image (~500MB+) is pulled on first use, causing a long wait
- Pre-pulling provides clear progress indication
- Avoids confusion when first using the agent
If you skip this step, the image will be automatically pulled on first agent execution, which may take several minutes depending on your network speed.
## Troubleshooting
### Config file not found

View File

@@ -32,14 +32,17 @@ IDLE_CHECK_INTERVAL = 60 # Check every 60 seconds
class AioSandboxProvider(SandboxProvider):
"""Sandbox provider that manages Docker containers running the AIO sandbox.
"""Sandbox provider that manages containers running the AIO sandbox.
On macOS, automatically prefers Apple Container if available, otherwise falls back to Docker.
On other platforms, uses Docker.
Configuration options in config.yaml under sandbox:
use: src.community.aio_sandbox:AioSandboxProvider
image: enterprise-public-cn-beijing.cr.volces.com/vefaas-public/all-in-one-sandbox:latest # Docker image to use
image: enterprise-public-cn-beijing.cr.volces.com/vefaas-public/all-in-one-sandbox:latest # Container image to use (works with both runtimes)
port: 8080 # Base port for sandbox containers
base_url: http://localhost:8080 # If set, uses existing sandbox instead of starting new container
auto_start: true # Whether to automatically start Docker container
auto_start: true # Whether to automatically start container
container_prefix: deer-flow-sandbox # Prefix for container names
idle_timeout: 600 # Idle timeout in seconds (default: 600 = 10 minutes). Set to 0 to disable.
mounts: # List of volume mounts
@@ -62,6 +65,7 @@ class AioSandboxProvider(SandboxProvider):
self._shutdown_called = False
self._idle_checker_stop = threading.Event()
self._idle_checker_thread: threading.Thread | None = None
self._container_runtime = self._detect_container_runtime()
# Register shutdown handler to clean up containers on exit
atexit.register(self.shutdown)
@@ -184,6 +188,35 @@ class AioSandboxProvider(SandboxProvider):
resolved[key] = str(value)
return resolved
def _detect_container_runtime(self) -> str:
"""Detect which container runtime to use.
On macOS, prefer Apple Container if available, otherwise fall back to Docker.
On other platforms, use Docker.
Returns:
"container" for Apple Container, "docker" for Docker.
"""
import platform
# Only try Apple Container on macOS
if platform.system() == "Darwin":
try:
result = subprocess.run(
["container", "--version"],
capture_output=True,
text=True,
check=True,
timeout=5,
)
logger.info(f"Detected Apple Container: {result.stdout.strip()}")
return "container"
except (FileNotFoundError, subprocess.CalledProcessError, subprocess.TimeoutExpired):
logger.info("Apple Container not available, falling back to Docker")
# Default to Docker
return "docker"
def _is_sandbox_ready(self, base_url: str, timeout: int = 30) -> bool:
"""Check if sandbox is ready to accept connections.
@@ -253,7 +286,10 @@ class AioSandboxProvider(SandboxProvider):
return None
def _start_container(self, sandbox_id: str, port: int, extra_mounts: list[tuple[str, str, bool]] | None = None) -> str:
"""Start a new Docker container for the sandbox.
"""Start a new container for the sandbox.
On macOS, prefers Apple Container if available, otherwise uses Docker.
On other platforms, uses Docker.
Args:
sandbox_id: Unique identifier for the sandbox.
@@ -267,17 +303,22 @@ class AioSandboxProvider(SandboxProvider):
container_name = f"{self._config['container_prefix']}-{sandbox_id}"
cmd = [
"docker",
self._container_runtime,
"run",
"--security-opt",
"seccomp=unconfined",
]
# Add Docker-specific security options
if self._container_runtime == "docker":
cmd.extend(["--security-opt", "seccomp=unconfined"])
cmd.extend([
"--rm",
"-d",
"-p",
f"{port}:8080",
"--name",
container_name,
]
])
# Add configured environment variables
for key, value in self._config["environment"].items():
@@ -303,26 +344,28 @@ class AioSandboxProvider(SandboxProvider):
cmd.append(image)
logger.info(f"Starting sandbox container: {' '.join(cmd)}")
logger.info(f"Starting sandbox container using {self._container_runtime}: {' '.join(cmd)}")
try:
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
container_id = result.stdout.strip()
logger.info(f"Started sandbox container {container_name} with ID {container_id}")
logger.info(f"Started sandbox container {container_name} with ID {container_id} using {self._container_runtime}")
return container_id
except subprocess.CalledProcessError as e:
logger.error(f"Failed to start sandbox container: {e.stderr}")
logger.error(f"Failed to start sandbox container using {self._container_runtime}: {e.stderr}")
raise RuntimeError(f"Failed to start sandbox container: {e.stderr}")
def _stop_container(self, container_id: str) -> None:
"""Stop and remove a Docker container.
"""Stop and remove a container.
Since we use --rm flag, the container is automatically removed after stopping.
Args:
container_id: The container ID to stop.
"""
try:
subprocess.run(["docker", "stop", container_id], capture_output=True, text=True, check=True)
logger.info(f"Stopped sandbox container {container_id}")
subprocess.run([self._container_runtime, "stop", container_id], capture_output=True, text=True, check=True)
logger.info(f"Stopped sandbox container {container_id} using {self._container_runtime} (--rm will auto-remove)")
except subprocess.CalledProcessError as e:
logger.warning(f"Failed to stop sandbox container {container_id}: {e.stderr}")

View File

@@ -144,17 +144,20 @@ tools:
sandbox:
use: src.sandbox.local:LocalSandboxProvider
# Option 2: Docker-based AIO Sandbox
# Executes commands in isolated Docker containers
# Option 2: Container-based AIO Sandbox
# Executes commands in isolated containers (Docker or Apple Container)
# On macOS: Automatically prefers Apple Container if available, falls back to Docker
# On other platforms: Uses Docker
# Uncomment to use:
# sandbox:
# use: src.community.aio_sandbox:AioSandboxProvider
#
# # Optional: Use existing sandbox at this URL (no Docker container will be started)
# # Optional: Use existing sandbox at this URL (no container will be started)
# # base_url: http://localhost:8080
#
# # Optional: Docker image to use
# # Optional: Container image to use (works with both Docker and Apple Container)
# # Default: enterprise-public-cn-beijing.cr.volces.com/vefaas-public/all-in-one-sandbox:latest
# # Recommended: enterprise-public-cn-beijing.cr.volces.com/vefaas-public/all-in-one-sandbox:latest (works on both x86_64 and arm64)
# # image: enterprise-public-cn-beijing.cr.volces.com/vefaas-public/all-in-one-sandbox:latest
#
# # Optional: Base port for sandbox containers (default: 8080)

96
scripts/cleanup-containers.sh Executable file
View File

@@ -0,0 +1,96 @@
#!/usr/bin/env bash
#
# cleanup-containers.sh - Clean up DeerFlow sandbox containers
#
# This script cleans up both Docker and Apple Container runtime containers
# to ensure compatibility across different container runtimes.
#
set -e
PREFIX="${1:-deer-flow-sandbox}"
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
echo "Cleaning up sandbox containers with prefix: ${PREFIX}"
# Function to clean up Docker containers
cleanup_docker() {
if command -v docker &> /dev/null; then
echo -n "Checking Docker containers... "
DOCKER_CONTAINERS=$(docker ps -q --filter "name=${PREFIX}" 2>/dev/null || echo "")
if [ -n "$DOCKER_CONTAINERS" ]; then
echo ""
echo "Found Docker containers to clean up:"
docker ps --filter "name=${PREFIX}" --format "table {{.ID}}\t{{.Names}}\t{{.Status}}"
echo "Stopping Docker containers..."
echo "$DOCKER_CONTAINERS" | xargs docker stop 2>/dev/null || true
echo -e "${GREEN}✓ Docker containers stopped${NC}"
else
echo -e "${GREEN}none found${NC}"
fi
else
echo "Docker not found, skipping..."
fi
}
# Function to clean up Apple Container containers
cleanup_apple_container() {
if command -v container &> /dev/null; then
echo -n "Checking Apple Container containers... "
# List all containers and filter by name
CONTAINER_LIST=$(container list --format json 2>/dev/null || echo "[]")
if [ "$CONTAINER_LIST" != "[]" ] && [ -n "$CONTAINER_LIST" ]; then
# Extract container IDs that match our prefix
CONTAINER_IDS=$(echo "$CONTAINER_LIST" | python3 -c "
import json
import sys
try:
containers = json.load(sys.stdin)
if isinstance(containers, list):
for c in containers:
if isinstance(c, dict):
name = c.get('name', '')
cid = c.get('id', '')
if '${PREFIX}' in name and cid:
print(cid)
except:
pass
" 2>/dev/null || echo "")
if [ -n "$CONTAINER_IDS" ]; then
echo ""
echo "Found Apple Container containers to clean up:"
echo "$CONTAINER_IDS" | while read -r cid; do
echo " - $cid"
done
echo "Stopping Apple Container containers..."
echo "$CONTAINER_IDS" | while read -r cid; do
container stop "$cid" 2>/dev/null || true
container delete "$cid" 2>/dev/null || true
done
echo -e "${GREEN}✓ Apple Container containers stopped${NC}"
else
echo -e "${GREEN}none found${NC}"
fi
else
echo -e "${GREEN}none found${NC}"
fi
else
echo "Apple Container not found, skipping..."
fi
}
# Clean up both runtimes
cleanup_docker
cleanup_apple_container
echo -e "${GREEN}✓ Container cleanup complete${NC}"