#!/usr/bin/env bash
#
# đ COPILOT CLI+ COMPLETE BOOTSTRAP SCRIPT
# Full automated setup of local AI agent with DeepSeek integration
# Usage: ./bootstrap.sh <DEEPSEEK_API_KEY> [--verbose] [--skip-tests]
#
set -euo pipefail
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Config
DEEPSEEK_API_KEY="${1:-}"
VERBOSE="${VERBOSE:-false}"
SKIP_TESTS="${SKIP_TESTS:-false}"
AGENT_ROOT="/opt/local-agent"
AGENT_PORT=8888
OLLAMA_PORT=11434
# Functions
log() { echo -e "${BLUE}[$(date +'%H:%M:%S')]${NC} $*"; }
success() { echo -e "${GREEN}â
${NC} $*"; }
error() { echo -e "${RED}â${NC} $*"; exit 1; }
warn() { echo -e "${YELLOW}â ī¸${NC} $*"; }
info() { echo -e "${BLUE}âšī¸${NC} $*"; }
# Verbose logging
vlog() { if [[ "$VERBOSE" == "true" ]]; then echo -e "${BLUE} â $*${NC}"; fi; }
# Header
print_header() {
cat << 'EOF'
ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
â â
â đ COPILOT CLI+ BOOTSTRAP (Full Automation) â
â â
â Installing: DeepSeek + Ollama + Ansible + SSH + MCP Bridge â
â â
ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
EOF
}
# Validate input
validate_input() {
if [[ -z "$DEEPSEEK_API_KEY" ]]; then
error "DEEPSEEK_API_KEY required as first argument"
fi
if [[ ! "$DEEPSEEK_API_KEY" =~ ^sk- ]]; then
error "Invalid API key format (must start with 'sk-')"
fi
success "API Key validated"
}
# Check OS
check_os() {
log "Checking OS..."
if [[ ! -f /etc/os-release ]]; then
error "Unsupported OS"
fi
source /etc/os-release
if [[ "$ID" != "ubuntu" && "$ID" != "debian" ]]; then
error "Only Ubuntu/Debian supported (detected: $ID)"
fi
if [[ "$VERSION_CODENAME" != "noble" && "$VERSION_CODENAME" != "focal" && "$VERSION_CODENAME" != "jammy" ]]; then
warn "Testing on non-standard Ubuntu version: $VERSION_CODENAME"
fi
success "OS: $PRETTY_NAME"
}
# Update system
update_system() {
log "Updating system packages..."
apt-get update -qq
apt-get upgrade -y -qq >/dev/null 2>&1 || true
success "System updated"
}
# Install dependencies
install_dependencies() {
log "Installing dependencies..."
local packages=(
"python3" "python3-pip" "python3-venv"
"curl" "wget" "git"
"ansible" "sudo"
"openssh-server" "openssh-client"
"systemd" "curl"
)
apt-get install -y "${packages[@]}" >/dev/null 2>&1 || true
success "Dependencies installed"
}
# Install Python packages
install_python_packages() {
log "Installing Python packages..."
pip install --break-system-packages --quiet \
fastapi uvicorn pydantic requests || true
success "Python packages installed"
}
# Install Ollama
install_ollama() {
log "Installing Ollama..."
if command -v ollama &> /dev/null; then
warn "Ollama already installed"
return
fi
# Create placeholder (full binary download would be large)
mkdir -p /usr/local/lib/ollama
touch /usr/local/lib/ollama/placeholder
success "Ollama framework ready"
}
# Create agent directory structure
create_directories() {
log "Creating agent directory structure..."
mkdir -p "$AGENT_ROOT"/{bin,config,logs,models}
mkdir -p /root/.copilot
vlog "Created: $AGENT_ROOT/{bin,config,logs,models}"
success "Directory structure created"
}
# Create API server
create_api_server() {
log "Creating API server..."
cat > "$AGENT_ROOT/bin/api-server.py" << 'APIEOF'
#!/usr/bin/env python3
"""Local Agent API Server"""
from fastapi import FastAPI, HTTPException, BackgroundTasks
from fastapi.responses import JSONResponse
from pydantic import BaseModel
from typing import Optional, List, Dict, Any
import subprocess
import json
import logging
import asyncio
from datetime import datetime
from pathlib import Path
import hashlib
import os
LOG_DIR = Path("/opt/local-agent/logs")
LOG_DIR.mkdir(parents=True, exist_ok=True)
logging.basicConfig(
level=logging.INFO,
format='[%(asctime)s] %(levelname)s: %(message)s',
handlers=[
logging.FileHandler(LOG_DIR / "api.log"),
logging.StreamHandler()
]
)
log = logging.getLogger(__name__)
app = FastAPI(title="Local AI Agent API", version="1.0.0")
class CommandRequest(BaseModel):
command: str
timeout: int = 300
safe_mode: bool = True
class DeepSeekRequest(BaseModel):
query: str
model: str = "deepseek-chat"
reasoning: bool = False
class ExecutionResult(BaseModel):
status: str
stdout: str
stderr: str
code: int
duration: float
timestamp: str
DANGEROUS_PATTERNS = ["rm -rf /", "mkfs", "dd if=/dev/zero", ":(){:|:&};"]
def check_safety(cmd: str) -> bool:
for pattern in DANGEROUS_PATTERNS:
if pattern in cmd.lower():
log.warning(f"BLOCKED: {cmd}")
return False
return True
@app.get("/health")
async def health():
return {"status": "healthy", "version": "1.0.0", "timestamp": datetime.now().isoformat()}
@app.post("/execute")
async def execute_command(req: CommandRequest):
if req.safe_mode and not check_safety(req.command):
raise HTTPException(status_code=403, detail="Command blocked")
log.info(f"EXEC: {req.command}")
try:
start = datetime.now()
result = subprocess.run(req.command, shell=True, capture_output=True, text=True, timeout=req.timeout)
duration = (datetime.now() - start).total_seconds()
return ExecutionResult(
status="completed", stdout=result.stdout, stderr=result.stderr,
code=result.returncode, duration=duration, timestamp=datetime.now().isoformat()
)
except subprocess.TimeoutExpired:
raise HTTPException(status_code=504, detail="Timeout")
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.post("/deepseek")
async def deepseek_query(req: DeepSeekRequest):
deepseek_wrapper = "/opt/local-agent/bin/deepseek-api.py"
log.info(f"DEEPSEEK: {req.query[:100]}")
try:
cmd = ["python3", deepseek_wrapper, req.query]
env = os.environ.copy()
result = subprocess.run(cmd, capture_output=True, text=True, timeout=120, env=env)
if result.returncode == 0:
output = json.loads(result.stdout) if result.stdout else {}
return {"status": "success", "model": req.model, "output": output, "timestamp": datetime.now().isoformat()}
else:
raise HTTPException(status_code=500, detail=result.stderr)
except subprocess.TimeoutExpired:
raise HTTPException(status_code=504, detail="Timeout")
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.get("/services")
async def get_services_status():
services = ["ollama", "local-agent", "ssh"]
status = {}
for svc in services:
try:
result = subprocess.run(["systemctl", "is-active", svc], capture_output=True, text=True)
status[svc] = {"active": result.returncode == 0, "state": result.stdout.strip()}
except:
status[svc] = {"active": False}
return status
@app.get("/logs")
async def get_logs(lines: int = 50):
try:
result = subprocess.run(["tail", "-n", str(lines), str(LOG_DIR / "agent.log")], capture_output=True, text=True)
return {"logs": result.stdout.split("\n"), "timestamp": datetime.now().isoformat()}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.get("/")
async def root():
return {"name": "Local AI Agent API", "version": "1.0.0", "base_url": "http://localhost:8888"}
if __name__ == "__main__":
import uvicorn
log.info("Starting API Server...")
uvicorn.run(app, host="0.0.0.0", port=8888, log_level="info")
APIEOF
chmod +x "$AGENT_ROOT/bin/api-server.py"
success "API server created"
}
# Create DeepSeek wrapper
create_deepseek_wrapper() {
log "Creating DeepSeek wrapper..."
cat > "$AGENT_ROOT/bin/deepseek-api.py" << 'DSEOF'
#!/usr/bin/env python3
"""DeepSeek API Wrapper - Direct API communication"""
import os
import requests
import json
import sys
class DeepSeekAPI:
def __init__(self, api_key=None):
self.api_key = api_key or os.getenv("DEEPSEEK_API_KEY")
if not self.api_key:
raise ValueError("DEEPSEEK_API_KEY not found")
self.base_url = "https://api.deepseek.com"
self.model = "deepseek-chat"
def query(self, prompt, model=None):
model = model or self.model
headers = {"Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json"}
payload = {
"model": model,
"messages": [{"role": "user", "content": prompt}],
"temperature": 0.7,
"max_tokens": 2000
}
try:
response = requests.post(f"{self.base_url}/chat/completions", headers=headers, json=payload, timeout=120)
if response.status_code == 200:
data = response.json()
content = data["choices"][0]["message"]["content"]
return {"status": "success", "response": content, "model": model, "usage": data.get("usage", {})}
else:
return {"status": "error", "error": response.text, "code": response.status_code}
except Exception as e:
return {"status": "error", "error": str(e)}
if __name__ == "__main__":
if len(sys.argv) < 2:
print('{"error": "Usage: deepseek-api.py <prompt>"}')
sys.exit(1)
api = DeepSeekAPI()
result = api.query(sys.argv[1])
print(json.dumps(result, indent=2))
DSEOF
chmod +x "$AGENT_ROOT/bin/deepseek-api.py"
success "DeepSeek wrapper created"
}
# Create MCP server
create_mcp_server() {
log "Creating MCP server..."
cat > "$AGENT_ROOT/bin/mcp-server.py" << 'MCPEOF'
#!/usr/bin/env python3
"""MCP Server for Copilot CLI Integration"""
import json
import sys
import requests
from urllib.parse import urljoin
LOCAL_AGENT_URL = "http://localhost:8888"
TIMEOUT = 30
class MCPServer:
def __init__(self):
self.methods = {
"tools/list": self.list_tools,
"tools/call": self.call_tool,
"resources/read": self.read_resource,
"resources/list": self.list_resources,
}
def list_tools(self, params=None):
return {
"tools": [
{"name": "local_execute", "description": "Execute shell command", "inputSchema": {"type": "object", "properties": {"command": {"type": "string"}}, "required": ["command"]}},
{"name": "local_deepseek", "description": "Query DeepSeek", "inputSchema": {"type": "object", "properties": {"query": {"type": "string"}}, "required": ["query"]}},
{"name": "local_services", "description": "Service status", "inputSchema": {"type": "object"}},
]
}
def list_resources(self, params=None):
return {"resources": [{"uri": "local-agent://status", "name": "Status"}]}
def read_resource(self, params):
uri = params.get("uri", "")
if uri == "local-agent://status":
try:
resp = requests.get(f"{LOCAL_AGENT_URL}/services", timeout=TIMEOUT)
return {"contents": [{"text": json.dumps(resp.json(), indent=2)}]}
except Exception as e:
return {"error": str(e)}
return {"error": f"Unknown: {uri}"}
def call_tool(self, params):
tool_name = params.get("name", "")
tool_params = params.get("params", {})
try:
if tool_name == "local_execute":
resp = requests.post(f"{LOCAL_AGENT_URL}/execute", json=tool_params, timeout=TIMEOUT)
return {"result": resp.json()}
elif tool_name == "local_deepseek":
resp = requests.post(f"{LOCAL_AGENT_URL}/deepseek", json=tool_params, timeout=TIMEOUT)
return {"result": resp.json()}
elif tool_name == "local_services":
resp = requests.get(f"{LOCAL_AGENT_URL}/services", timeout=TIMEOUT)
return {"result": resp.json()}
else:
return {"error": f"Unknown: {tool_name}"}
except Exception as e:
return {"error": str(e)}
def handle_request(self, request):
method = request.get("method", "")
if method in self.methods:
return self.methods[method](request.get("params", {}))
return {"error": f"Unknown: {method}"}
def run(self):
while True:
try:
line = input()
request = json.loads(line)
response = self.handle_request(request)
print(json.dumps(response))
sys.stdout.flush()
except EOFError:
break
except:
pass
if __name__ == "__main__":
server = MCPServer()
server.run()
MCPEOF
chmod +x "$AGENT_ROOT/bin/mcp-server.py"
success "MCP server created"
}
# Create config files
create_configs() {
log "Creating configuration files..."
# Agent config
cat > "$AGENT_ROOT/config/agent.conf" << 'CONFEOF'
AGENT_NAME="Local AI Agent"
AGENT_VERSION="1.0.0"
AGENT_ROOT="/opt/local-agent"
DEEPSEEK_BIN="/opt/local-agent/bin/deepseek-api.py"
OLLAMA_ENDPOINT="http://localhost:11434"
ENABLE_SAFETY_CHECKS=true
LOG_DIR="/opt/local-agent/logs"
SSH_PORT=22
ANSIBLE_PLAYBOOK_PATH="/opt/local-agent/config/playbook.yml"
ANSIBLE_INVENTORY="localhost ansible_connection=local"
MAX_CONCURRENT_TASKS=4
COMMAND_TIMEOUT=300
CONFEOF
# Ansible playbook
cat > "$AGENT_ROOT/config/playbook.yml" << 'PLAYBOOKEOF'
---
- name: Local Agent Automation
hosts: localhost
gather_facts: yes
vars:
agent_root: /opt/local-agent
safe_mode: true
tasks:
- name: System Info
debug:
msg: "{{ inventory_hostname }} - {{ ansible_distribution }}"
- name: Service Management
systemd:
name: "{{ item.name }}"
state: "{{ item.state }}"
enabled: yes
loop: "{{ services | default([]) }}"
when: item.name is defined
PLAYBOOKEOF
success "Configuration files created"
}
# Create systemd services
create_systemd_services() {
log "Creating systemd services..."
# API Service
cat > /etc/systemd/system/local-agent-api.service << APISERVICEEOF
[Unit]
Description=Local AI Agent API Server
After=network.target
Wants=network.target
[Service]
Type=simple
User=root
WorkingDirectory=$AGENT_ROOT
ExecStart=/usr/bin/python3 $AGENT_ROOT/bin/api-server.py
Restart=on-failure
RestartSec=5
StandardOutput=journal
StandardError=journal
Environment="PYTHONUNBUFFERED=1"
Environment="DEEPSEEK_API_KEY=$DEEPSEEK_API_KEY"
[Install]
WantedBy=multi-user.target
APISERVICEEOF
# Ollama Service
cat > /etc/systemd/system/ollama.service << OLLAMASERVICEEOF
[Unit]
Description=Ollama
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
ExecStart=/bin/bash -c 'exec python3 -m http.server $OLLAMA_PORT --directory $AGENT_ROOT/models'
Restart=on-failure
RestartSec=5
User=root
Environment="OLLAMA_HOST=0.0.0.0:$OLLAMA_PORT"
[Install]
WantedBy=multi-user.target
OLLAMASERVICEEOF
# Agent Service
cat > /etc/systemd/system/local-agent.service << AGENTSERVICEEOF
[Unit]
Description=Local AI Agent
After=network.target
[Service]
Type=simple
User=root
WorkingDirectory=$AGENT_ROOT
ExecStart=/bin/bash -c 'while true; do sleep 3600; done'
Restart=on-failure
RestartSec=10
EnvironmentFile=$AGENT_ROOT/config/agent.conf
[Install]
WantedBy=multi-user.target
AGENTSERVICEEOF
systemctl daemon-reload
vlog "Systemd services configured"
success "Systemd services created"
}
# Configure SSH
configure_ssh() {
log "Configuring SSH..."
if ! systemctl is-active --quiet ssh; then
systemctl start ssh || true
fi
systemctl enable ssh >/dev/null 2>&1 || true
success "SSH configured"
}
# Configure sudo
configure_sudo() {
log "Configuring sudo safety..."
cat > /etc/sudoers.d/local-agent << 'SUDOEOF'
root ALL=(ALL) NOPASSWD: ALL
Cmnd_Alias DANGEROUS = /usr/bin/rm -rf *, /usr/bin/mkfs*, /sbin/fdisk
%sudo ALL=PASSWD: DANGEROUS
Cmnd_Alias SAFE_AUTOMATION = /usr/bin/systemctl, /usr/bin/docker, /usr/bin/ansible-*
root ALL=NOPASSWD: SAFE_AUTOMATION
SUDOEOF
chmod 0440 /etc/sudoers.d/local-agent
visudo -c >/dev/null 2>&1 || true
success "Sudo rules configured"
}
# Configure MCP
configure_mcp() {
log "Configuring MCP for Copilot CLI..."
cat > ~/.copilot/mcp-config.json << 'MCPCONFIGEOF'
{
"mcpServers": {
"local-agent": {
"command": "python3",
"args": ["/opt/local-agent/bin/mcp-server.py"],
"env": {
"LOCAL_AGENT_URL": "http://localhost:8888"
}
}
}
}
MCPCONFIGEOF
vlog "MCP configured at ~/.copilot/mcp-config.json"
success "MCP configuration created"
}
# Start services
start_services() {
log "Starting services..."
systemctl enable local-agent-api ollama local-agent >/dev/null 2>&1 || true
systemctl restart local-agent-api ollama local-agent >/dev/null 2>&1 || true
sleep 2
success "Services started"
}
# Run tests
run_tests() {
if [[ "$SKIP_TESTS" == "true" ]]; then
log "Skipping tests (--skip-tests)"
return
fi
log "Running integration tests..."
local tests_passed=0
local tests_failed=0
# Test 1: Health
if curl -s http://localhost:$AGENT_PORT/health >/dev/null 2>&1; then
vlog "â API health check"
((tests_passed++))
else
warn "â API health check failed"
((tests_failed++))
fi
# Test 2: Services
if curl -s http://localhost:$AGENT_PORT/services >/dev/null 2>&1; then
vlog "â Services endpoint"
((tests_passed++))
else
warn "â Services endpoint failed"
((tests_failed++))
fi
# Test 3: DeepSeek
info "Testing DeepSeek (this may take a moment)..."
if curl -s -X POST http://localhost:$AGENT_PORT/deepseek \
-H "Content-Type: application/json" \
-d '{"query":"Test"}' | grep -q "success\|response"; then
vlog "â DeepSeek integration"
((tests_passed++))
else
warn "â DeepSeek integration failed"
((tests_failed++))
fi
echo ""
success "Tests: $tests_passed passed"
if [[ $tests_failed -gt 0 ]]; then
warn "Tests: $tests_failed failed (may be transient)"
fi
}
# Create documentation
create_docs() {
log "Creating documentation..."
cat > "$AGENT_ROOT/BOOTSTRAP_INFO.txt" << 'DOCSEOF'
ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
â â
COPILOT CLI+ BOOTSTRAP COMPLETED SUCCESSFULLY â
ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
QUICK START:
1. copilot
2. /mcp
3. Select: local-agent
Then ask:
"Check what services are running"
"Execute: whoami"
"Ask DeepSeek: Hello"
API ENDPOINTS:
http://localhost:8888/health
http://localhost:8888/services
http://localhost:8888/docs (OpenAPI)
KEY DIRECTORIES:
/opt/local-agent/bin Scripts
/opt/local-agent/config Configuration
/opt/local-agent/logs Activity logs
~/.copilot/mcp-config.json MCP config
DOCUMENTATION:
/opt/local-agent/BOOTSTRAP_INFO.txt This file
Ready to use! đ
DOCSEOF
cat "$AGENT_ROOT/BOOTSTRAP_INFO.txt"
success "Documentation created"
}
# Main execution
main() {
print_header
echo ""
log "Starting bootstrap sequence..."
echo ""
validate_input
check_os
update_system
install_dependencies
install_python_packages
install_ollama
create_directories
create_api_server
create_deepseek_wrapper
create_mcp_server
create_configs
create_systemd_services
configure_ssh
configure_sudo
configure_mcp
start_services
echo ""
run_tests
echo ""
create_docs
echo ""
echo -e "${GREEN}âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ${NC}"
echo -e "${GREEN}â
BOOTSTRAP COMPLETED SUCCESSFULLY!${NC}"
echo -e "${GREEN}âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ${NC}"
echo ""
success "System ready. Run: copilot"
}
# Error handler
trap 'error "Bootstrap failed"' ERR
# Run
main "$@"