Files
k3s-gitops/apps/ollama-mcp/install.sh

522 lines
14 KiB
Bash

#!/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 ""