diff --git a/apps/ollama-mcp/install.sh b/apps/ollama-mcp/install.sh new file mode 100644 index 0000000..5d97e16 --- /dev/null +++ b/apps/ollama-mcp/install.sh @@ -0,0 +1,521 @@ +#!/bin/bash + +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Banner +echo -e "${BLUE}" +echo "╔═══════════════════════════════════════════════════════╗" +echo "║ ║" +echo "║ Ollama MCP Integration Installer ║" +echo "║ Kubernetes + Gitea MCP Servers ║" +echo "║ ║" +echo "╚═══════════════════════════════════════════════════════╝" +echo -e "${NC}" + +# Functions +log_info() { + echo -e "${BLUE}[INFO]${NC} $1" +} + +log_success() { + echo -e "${GREEN}[SUCCESS]${NC} $1" +} + +log_warning() { + echo -e "${YELLOW}[WARNING]${NC} $1" +} + +log_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +check_command() { + if ! command -v $1 &> /dev/null; then + log_error "$1 is not installed" + return 1 + fi + log_success "$1 is installed" + return 0 +} + +# Check prerequisites +log_info "Checking prerequisites..." + +MISSING_DEPS=0 + +if ! check_command docker; then + MISSING_DEPS=1 +fi + +if ! check_command docker-compose; then + if ! docker compose version &> /dev/null; then + log_error "docker-compose or 'docker compose' is not available" + MISSING_DEPS=1 + else + log_success "docker compose plugin is installed" + DOCKER_COMPOSE_CMD="docker compose" + fi +else + DOCKER_COMPOSE_CMD="docker-compose" +fi + +if ! check_command git; then + MISSING_DEPS=1 +fi + +if ! check_command curl; then + MISSING_DEPS=1 +fi + +if [ $MISSING_DEPS -eq 1 ]; then + log_error "Missing required dependencies. Please install them first." + echo "" + echo "Ubuntu/Debian:" + echo " sudo apt-get update" + echo " sudo apt-get install -y docker.io docker-compose git curl" + echo "" + echo "CentOS/RHEL:" + echo " sudo yum install -y docker docker-compose git curl" + echo " sudo systemctl start docker" + exit 1 +fi + +# Installation directory +INSTALL_DIR="${INSTALL_DIR:-$HOME/ollama-mcp}" +log_info "Installation directory: $INSTALL_DIR" + +# Create directory structure +log_info "Creating directory structure..." +mkdir -p "$INSTALL_DIR"/{config,mcp-kubernetes,mcp-gitea} +cd "$INSTALL_DIR" + +# Clone or update repository +if [ -d "$INSTALL_DIR/.git" ]; then + log_info "Updating existing repository..." + git pull origin main || log_warning "Failed to pull updates" +else + log_info "Cloning repository..." + git clone https://git.thedevops.dev/admin/k3s-gitops.git temp_repo + cp -r temp_repo/apps/ollama-mcp/* . + rm -rf temp_repo +fi + +# Environment configuration +log_info "Configuring environment..." + +if [ ! -f .env ]; then + cat > .env << 'EOF' +# Gitea Configuration +GITEA_URL=https://git.thedevops.dev +GITEA_TOKEN= +GITEA_OWNER=admin + +# Kubernetes Configuration +K8S_CONTEXT= +K8S_NAMESPACE=default + +# MCP Server Ports +MCP_K8S_PORT=3001 +MCP_GITEA_PORT=3002 +EOF + log_success "Created .env file" +else + log_info ".env file already exists, skipping..." +fi + +# Kubeconfig setup +log_info "Setting up Kubernetes configuration..." + +if [ -f "$HOME/.kube/config" ]; then + cp "$HOME/.kube/config" config/kubeconfig + chmod 600 config/kubeconfig + log_success "Copied kubeconfig from ~/.kube/config" +else + log_warning "No kubeconfig found at ~/.kube/config" + read -p "Enter path to your kubeconfig file (or press Enter to skip): " KUBECONFIG_PATH + + if [ -n "$KUBECONFIG_PATH" ] && [ -f "$KUBECONFIG_PATH" ]; then + cp "$KUBECONFIG_PATH" config/kubeconfig + chmod 600 config/kubeconfig + log_success "Copied kubeconfig from $KUBECONFIG_PATH" + else + log_warning "Skipping kubeconfig setup. You'll need to configure it manually." + fi +fi + +# Gitea token setup +log_info "Configuring Gitea access..." +echo "" +echo "To get your Gitea token:" +echo "1. Go to https://git.thedevops.dev" +echo "2. Settings → Applications → Generate New Token" +echo "3. Select scopes: repo, admin:org, write:repository" +echo "" + +read -p "Enter your Gitea token (or press Enter to configure later): " GITEA_TOKEN + +if [ -n "$GITEA_TOKEN" ]; then + # Update .env file + sed -i "s|GITEA_TOKEN=.*|GITEA_TOKEN=$GITEA_TOKEN|" .env + log_success "Gitea token configured" +else + log_warning "Gitea token not set. Update .env file manually before starting." +fi + +# Create MCP Kubernetes server files +log_info "Creating MCP Kubernetes server..." + +cat > mcp-kubernetes/package.json << 'EOF' +{ + "name": "mcp-kubernetes-server", + "version": "1.0.0", + "description": "MCP Server for Kubernetes integration with Ollama", + "main": "index.js", + "scripts": { + "start": "node index.js" + }, + "dependencies": { + "@modelcontextprotocol/sdk": "^1.0.0", + "@kubernetes/client-node": "^0.21.0", + "express": "^4.18.2" + } +} +EOF + +cat > mcp-kubernetes/index.js << 'EOF' +const express = require('express'); +const k8s = require('@kubernetes/client-node'); + +const app = express(); +const port = process.env.PORT || 3000; + +// Kubernetes client setup +const kc = new k8s.KubeConfig(); +kc.loadFromDefault(); + +const k8sApi = kc.makeApiClient(k8s.CoreV1Api); +const k8sAppsApi = kc.makeApiClient(k8s.AppsV1Api); + +app.use(express.json()); + +// Health check +app.get('/health', (req, res) => { + res.json({ status: 'healthy', service: 'mcp-kubernetes' }); +}); + +// List pods +app.post('/api/pods/list', async (req, res) => { + try { + const namespace = req.body.namespace || 'default'; + const response = await k8sApi.listNamespacedPod(namespace); + res.json(response.body); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// Get pod logs +app.post('/api/pods/logs', async (req, res) => { + try { + const { name, namespace, container, tailLines } = req.body; + const response = await k8sApi.readNamespacedPodLog( + name, + namespace || 'default', + container, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + tailLines || 100 + ); + res.json({ logs: response.body }); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// List deployments +app.post('/api/deployments/list', async (req, res) => { + try { + const namespace = req.body.namespace || 'default'; + const response = await k8sAppsApi.listNamespacedDeployment(namespace); + res.json(response.body); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// Get deployment +app.post('/api/deployments/get', async (req, res) => { + try { + const { name, namespace } = req.body; + const response = await k8sAppsApi.readNamespacedDeployment( + name, + namespace || 'default' + ); + res.json(response.body); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// List services +app.post('/api/services/list', async (req, res) => { + try { + const namespace = req.body.namespace || 'default'; + const response = await k8sApi.listNamespacedService(namespace); + res.json(response.body); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// List namespaces +app.post('/api/namespaces/list', async (req, res) => { + try { + const response = await k8sApi.listNamespace(); + res.json(response.body); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +app.listen(port, '0.0.0.0', () => { + console.log(`MCP Kubernetes server listening on port ${port}`); +}); +EOF + +cat > mcp-kubernetes/Dockerfile << 'EOF' +FROM node:20-alpine + +WORKDIR /app + +COPY package.json ./ +RUN npm install --production + +COPY index.js ./ + +EXPOSE 3000 + +CMD ["npm", "start"] +EOF + +# Create MCP Gitea server files +log_info "Creating MCP Gitea server..." + +cat > mcp-gitea/package.json << 'EOF' +{ + "name": "mcp-gitea-server", + "version": "1.0.0", + "description": "MCP Server for Gitea integration with Ollama", + "main": "index.js", + "scripts": { + "start": "node index.js" + }, + "dependencies": { + "@modelcontextprotocol/sdk": "^1.0.0", + "axios": "^1.6.0", + "express": "^4.18.2" + } +} +EOF + +cat > mcp-gitea/index.js << 'EOF' +const express = require('express'); +const axios = require('axios'); + +const app = express(); +const port = process.env.PORT || 3000; + +const GITEA_URL = process.env.GITEA_URL || 'https://git.thedevops.dev'; +const GITEA_TOKEN = process.env.GITEA_TOKEN; +const GITEA_OWNER = process.env.GITEA_OWNER || 'admin'; + +const gitea = axios.create({ + baseURL: `${GITEA_URL}/api/v1`, + headers: { + 'Authorization': `token ${GITEA_TOKEN}`, + 'Content-Type': 'application/json' + } +}); + +app.use(express.json()); + +// Health check +app.get('/health', (req, res) => { + res.json({ status: 'healthy', service: 'mcp-gitea' }); +}); + +// List repositories +app.post('/api/repos/list', async (req, res) => { + try { + const owner = req.body.owner || GITEA_OWNER; + const response = await gitea.get(`/users/${owner}/repos`); + res.json(response.data); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// Get file content +app.post('/api/repos/file/get', async (req, res) => { + try { + const { owner, repo, path, branch } = req.body; + const response = await gitea.get( + `/repos/${owner || GITEA_OWNER}/${repo}/contents/${path}`, + { params: { ref: branch || 'main' } } + ); + res.json(response.data); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// List directory contents +app.post('/api/repos/tree/get', async (req, res) => { + try { + const { owner, repo, path, branch } = req.body; + const response = await gitea.get( + `/repos/${owner || GITEA_OWNER}/${repo}/contents/${path || ''}`, + { params: { ref: branch || 'main' } } + ); + res.json(response.data); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// Create file +app.post('/api/repos/file/create', async (req, res) => { + try { + const { owner, repo, path, content, message, branch } = req.body; + const response = await gitea.post( + `/repos/${owner || GITEA_OWNER}/${repo}/contents/${path}`, + { + content: Buffer.from(content).toString('base64'), + message: message || 'Create file via MCP', + branch: branch || 'main' + } + ); + res.json(response.data); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// Update file +app.post('/api/repos/file/update', async (req, res) => { + try { + const { owner, repo, path, content, message, sha, branch } = req.body; + const response = await gitea.put( + `/repos/${owner || GITEA_OWNER}/${repo}/contents/${path}`, + { + content: Buffer.from(content).toString('base64'), + message: message || 'Update file via MCP', + sha: sha, + branch: branch || 'main' + } + ); + res.json(response.data); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// List branches +app.post('/api/repos/branches/list', async (req, res) => { + try { + const { owner, repo } = req.body; + const response = await gitea.get( + `/repos/${owner || GITEA_OWNER}/${repo}/branches` + ); + res.json(response.data); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +app.listen(port, '0.0.0.0', () => { + console.log(`MCP Gitea server listening on port ${port}`); +}); +EOF + +cat > mcp-gitea/Dockerfile << 'EOF' +FROM node:20-alpine + +WORKDIR /app + +COPY package.json ./ +RUN npm install --production + +COPY index.js ./ + +EXPOSE 3000 + +CMD ["npm", "start"] +EOF + +# Build and start containers +log_info "Building Docker images..." +$DOCKER_COMPOSE_CMD build + +log_info "Starting MCP servers..." +$DOCKER_COMPOSE_CMD up -d + +# Wait for services to be healthy +log_info "Waiting for services to start..." +sleep 10 + +# Check status +log_info "Checking service status..." +$DOCKER_COMPOSE_CMD ps + +# Test endpoints +log_info "Testing MCP servers..." + +if curl -s http://localhost:3001/health > /dev/null 2>&1; then + log_success "MCP Kubernetes server is running" +else + log_warning "MCP Kubernetes server is not responding" +fi + +if curl -s http://localhost:3002/health > /dev/null 2>&1; then + log_success "MCP Gitea server is running" +else + log_warning "MCP Gitea server is not responding" +fi + +# Final instructions +echo "" +echo -e "${GREEN}╔═══════════════════════════════════════════════════════╗${NC}" +echo -e "${GREEN}║ ║${NC}" +echo -e "${GREEN}║ Installation Complete! ║${NC}" +echo -e "${GREEN}║ ║${NC}" +echo -e "${GREEN}╚═══════════════════════════════════════════════════════╝${NC}" +echo "" +log_info "MCP servers are running at:" +echo " - Kubernetes MCP: http://localhost:3001" +echo " - Gitea MCP: http://localhost:3002" +echo "" +log_info "Next steps:" +echo " 1. Configure Ollama to use these MCP endpoints" +echo " 2. Update .env file with your Gitea token if not done" +echo " 3. Verify kubeconfig in config/kubeconfig" +echo "" +log_info "Useful commands:" +echo " View logs: cd $INSTALL_DIR && $DOCKER_COMPOSE_CMD logs -f" +echo " Restart: cd $INSTALL_DIR && $DOCKER_COMPOSE_CMD restart" +echo " Stop: cd $INSTALL_DIR && $DOCKER_COMPOSE_CMD down" +echo " Status: cd $INSTALL_DIR && $DOCKER_COMPOSE_CMD ps" +echo "" +log_info "Documentation: $INSTALL_DIR/README.md" +echo ""