feat: add automated installation script for ollama-mcp
This commit is contained in:
521
apps/ollama-mcp/install.sh
Normal file
521
apps/ollama-mcp/install.sh
Normal file
@@ -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 ""
|
||||
Reference in New Issue
Block a user