From 6c7b3441179dceab315f8cae82548dbb97f87751 Mon Sep 17 00:00:00 2001 From: Zac Gaetano Date: Sat, 4 Apr 2026 15:10:25 -0400 Subject: [PATCH] Remove unneeded file: deploy-mcp.py --- deploy-mcp.py | 419 -------------------------------------------------- 1 file changed, 419 deletions(-) delete mode 100644 deploy-mcp.py diff --git a/deploy-mcp.py b/deploy-mcp.py deleted file mode 100644 index 962db52..0000000 --- a/deploy-mcp.py +++ /dev/null @@ -1,419 +0,0 @@ -#!/usr/bin/env python3 - -""" -Claude Code Stack - TrueNAS MCP Deployment Script - -Deploys the Claude Code + Agents UI stack to TrueNAS SCALE using the MCP protocol. -Requires mcp-server to be running on TrueNAS and accessible via MCP. - -Usage: - python deploy-mcp.py --mcp-url https://mcp.wilddragon.net/mcp --pool tank -""" - -import asyncio -import json -import argparse -import os -from pathlib import Path -from typing import Optional -import sys - -try: - import httpx -except ImportError: - print("Error: httpx is required. Install with: pip install httpx") - sys.exit(1) - -# ANSI Color codes -class Colors: - GREEN = '\033[0;32m' - YELLOW = '\033[1;33m' - RED = '\033[0;31m' - BLUE = '\033[0;34m' - RESET = '\033[0m' - -def colored(text: str, color: str) -> str: - """Return colored text for terminal output""" - return f"{color}{text}{Colors.RESET}" - -def log_info(msg: str): - print(colored(f"[INFO] {msg}", Colors.GREEN)) - -def log_warn(msg: str): - print(colored(f"[WARN] {msg}", Colors.YELLOW)) - -def log_error(msg: str): - print(colored(f"[ERROR] {msg}", Colors.RED)) - -def log_step(msg: str): - print(colored(f"\n>>> {msg}", Colors.BLUE)) - -# Docker Compose YAML template -DOCKER_COMPOSE_TEMPLATE = """version: '3.8' - -services: - agents-ui: - image: node:20-alpine - container_name: claude-agents-ui - restart: unless-stopped - ports: - - "3000:3000" - environment: - ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY} - NODE_ENV: production - CLAUDE_DIR: /root/.claude - volumes: - - claude-config:/root/.claude - - workspace:/workspace - depends_on: - - claude-code-backend - networks: - - claude-stack - healthcheck: - test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:3000"] - interval: 30s - timeout: 10s - retries: 3 - - claude-code-backend: - image: ghcr.io/anthropics/claude-code:latest - container_name: claude-code-runtime - restart: unless-stopped - ports: - - "5000:5000" - environment: - ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY} - CLAUDE_MODEL: claude-opus-4-1 - WORKSPACE_DIR: /workspace - volumes: - - claude-config:/root/.claude - - workspace:/workspace - - ${HOME}/.ssh:/root/.ssh:ro - networks: - - claude-stack - healthcheck: - test: ["CMD", "test", "-d", "/workspace"] - interval: 30s - timeout: 10s - retries: 3 - - postgres: - image: postgres:16-alpine - container_name: claude-postgres - restart: unless-stopped - ports: - - "5432:5432" - environment: - POSTGRES_USER: claude - POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} - POSTGRES_DB: claude_agents - volumes: - - postgres-data:/var/lib/postgresql/data - networks: - - claude-stack - healthcheck: - test: ["CMD-SHELL", "pg_isready -U claude"] - interval: 10s - timeout: 5s - retries: 5 - - redis: - image: redis:7-alpine - container_name: claude-redis - restart: unless-stopped - ports: - - "6379:6379" - volumes: - - redis-data:/data - networks: - - claude-stack - command: redis-server --appendonly yes - healthcheck: - test: ["CMD", "redis-cli", "ping"] - interval: 10s - timeout: 5s - retries: 5 - -volumes: - claude-config: - driver: local - workspace: - driver: local - postgres-data: - driver: local - redis-data: - driver: local - -networks: - claude-stack: - driver: bridge -""" - -ENV_TEMPLATE = """# Claude Code Stack Environment Configuration -ANTHROPIC_API_KEY={api_key} -POSTGRES_PASSWORD={postgres_password} -SESSION_SECRET={session_secret} -NODE_ENV=production -CLAUDE_MODEL=claude-opus-4-1 -RUN_CLAUDE_CODE=true -LOG_LEVEL=info -""" - -class TrueNASMCPDeployer: - def __init__(self, mcp_url: str, pool_name: str, api_key: str): - self.mcp_url = mcp_url - self.pool_name = pool_name - self.api_key = api_key - self.project_dir = f"/mnt/{pool_name}/docker/claude-code-stack" - self.client = httpx.AsyncClient(timeout=30.0) - - async def call_mcp(self, method: str, params: dict) -> dict: - """Call MCP method via HTTP""" - payload = { - "jsonrpc": "2.0", - "method": method, - "params": params, - "id": 1 - } - - try: - response = await self.client.post( - self.mcp_url, - json=payload, - headers={"Content-Type": "application/json"} - ) - - if response.status_code != 200: - log_error(f"MCP call failed: {response.status_code}") - log_error(f"Response: {response.text}") - return None - - result = response.json() - - if "error" in result: - log_error(f"MCP error: {result['error']}") - return None - - return result.get("result") - - except Exception as e: - log_error(f"MCP connection error: {str(e)}") - return None - - async def create_directory(self) -> bool: - """Create project directory on TrueNAS""" - log_step(f"Creating project directory: {self.project_dir}") - - result = await self.call_mcp( - "truenas:start_process", - { - "command": f"mkdir -p {self.project_dir} && chmod 755 {self.project_dir}", - "shell": True - } - ) - - if result: - log_info(f"Directory created: {self.project_dir}") - return True - return False - - async def write_file(self, filename: str, content: str) -> bool: - """Write file to TrueNAS via MCP""" - filepath = f"{self.project_dir}/{filename}" - - try: - # Write file using exec command - escaped_content = content.replace('"', '\\"').replace('\n', '\\n') - - result = await self.call_mcp( - "truenas:start_process", - { - "command": f'cat > {filepath} << \'EOF\'\n{content}\nEOF', - "shell": True - } - ) - - if result: - log_info(f"Created: {filename}") - return True - - except Exception as e: - log_error(f"Failed to write {filename}: {str(e)}") - - return False - - async def generate_env(self) -> str: - """Generate .env file with random secrets""" - import secrets - import string - - postgres_password = secrets.token_urlsafe(16) - session_secret = secrets.token_urlsafe(32) - - return ENV_TEMPLATE.format( - api_key=self.api_key, - postgres_password=postgres_password, - session_secret=session_secret - ) - - async def deploy_docker_compose(self) -> bool: - """Deploy docker-compose.yml""" - log_step("Deploying docker-compose.yml") - return await self.write_file("docker-compose.yml", DOCKER_COMPOSE_TEMPLATE) - - async def deploy_env_file(self) -> bool: - """Deploy .env file""" - log_step("Generating and deploying .env file") - env_content = await self.generate_env() - return await self.write_file(".env", env_content) - - async def pull_images(self) -> bool: - """Pull Docker images""" - log_step("Pulling Docker images") - - result = await self.call_mcp( - "truenas:start_process", - { - "command": f"cd {self.project_dir} && docker-compose pull", - "shell": True - } - ) - - return result is not None - - async def start_services(self) -> bool: - """Start Docker services""" - log_step("Starting Docker services") - - result = await self.call_mcp( - "truenas:start_process", - { - "command": f"cd {self.project_dir} && docker-compose up -d", - "shell": True - } - ) - - return result is not None - - async def get_service_status(self) -> Optional[str]: - """Get service status""" - result = await self.call_mcp( - "truenas:start_process", - { - "command": f"cd {self.project_dir} && docker-compose ps", - "shell": True - } - ) - - return result - - async def deploy(self) -> bool: - """Execute full deployment""" - try: - print(colored("="*50, Colors.BLUE)) - print(colored("Claude Code Stack - TrueNAS MCP Deployment", Colors.BLUE)) - print(colored("="*50, Colors.BLUE)) - print() - - # Check API key - if not self.api_key: - log_error("ANTHROPIC_API_KEY is required") - return False - - log_info(f"MCP Server: {self.mcp_url}") - log_info(f"Pool: {self.pool_name}") - log_info(f"Project Directory: {self.project_dir}") - print() - - # Execute deployment steps - if not await self.create_directory(): - return False - - if not await self.deploy_docker_compose(): - return False - - if not await self.deploy_env_file(): - return False - - if not await self.pull_images(): - log_warn("Failed to pull images, continuing...") - - if not await self.start_services(): - return False - - # Wait for services - log_step("Waiting for services to stabilize...") - await asyncio.sleep(5) - - # Get status - log_step("Checking service status") - status = await self.get_service_status() - if status: - print(status) - - return True - - finally: - await self.client.aclose() - -async def main(): - parser = argparse.ArgumentParser( - description="Deploy Claude Code Stack to TrueNAS via MCP" - ) - - parser.add_argument( - "--mcp-url", - required=True, - help="MCP server URL (e.g., https://mcp.wilddragon.net/mcp)" - ) - - parser.add_argument( - "--pool", - default="tank", - help="TrueNAS pool name (default: tank)" - ) - - parser.add_argument( - "--api-key", - help="Anthropic API Key (or set ANTHROPIC_API_KEY env var)" - ) - - args = parser.parse_args() - - # Get API key - api_key = args.api_key or os.environ.get("ANTHROPIC_API_KEY") - - if not api_key: - log_error("ANTHROPIC_API_KEY not provided") - log_info("Set it via: export ANTHROPIC_API_KEY=your_key") - log_info("Or pass: --api-key your_key") - sys.exit(1) - - # Create deployer - deployer = TrueNASMCPDeployer( - mcp_url=args.mcp_url, - pool_name=args.pool, - api_key=api_key - ) - - # Execute deployment - success = await deployer.deploy() - - if success: - print() - print(colored("="*50, Colors.GREEN)) - print(colored("Deployment Successful!", Colors.GREEN)) - print(colored("="*50, Colors.GREEN)) - print() - print("Access Agents UI at: http://your-truenas-ip:3000") - print(f"Project directory: {deployer.project_dir}") - sys.exit(0) - else: - print() - print(colored("="*50, Colors.RED)) - print(colored("Deployment Failed!", Colors.RED)) - print(colored("="*50, Colors.RED)) - sys.exit(1) - -if __name__ == "__main__": - asyncio.run(main())