UNPKG

unifi-pro-mcp

Version:

MCP Server for UniFi Network and Protect systems with multi-device support

1,525 lines (1,276 loc) 36 kB
# Production Deployment Guide Comprehensive guide for deploying UniFi PRO MCP in production environments with enterprise-grade reliability and security. ## 🎯 Table of Contents - [Deployment Overview](#deployment-overview) - [Infrastructure Requirements](#infrastructure-requirements) - [Container Deployment](#container-deployment) - [Cloud Deployment](#cloud-deployment) - [High Availability Setup](#high-availability-setup) - [Monitoring & Observability](#monitoring--observability) - [Backup & Recovery](#backup--recovery) - [Performance Optimization](#performance-optimization) - [CI/CD Pipeline](#cicd-pipeline) - [Maintenance Procedures](#maintenance-procedures) ## 🏗️ Deployment Overview ### Deployment Architectures ```mermaid graph TB subgraph "Production Environment" LB[Load Balancer] subgraph "Application Tier" MCP1[UniFi MCP Server 1] MCP2[UniFi MCP Server 2] MCP3[UniFi MCP Server 3] end subgraph "Data Tier" REDIS[(Redis Cache)] LOGS[(Log Storage)] end subgraph "UniFi Infrastructure" UC1[UniFi Controller 1] UC2[UniFi Controller 2] UP1[UniFi Protect 1] end end CLAUDE[Claude Desktop] --> LB LB --> MCP1 LB --> MCP2 LB --> MCP3 MCP1 --> REDIS MCP2 --> REDIS MCP3 --> REDIS MCP1 --> UC1 MCP2 --> UC2 MCP3 --> UP1 MCP1 --> LOGS MCP2 --> LOGS MCP3 --> LOGS ``` ### Deployment Options | Option | Use Case | Complexity | Cost | Scalability | |--------|----------|------------|------|-------------| | **Single Instance** | Development, Small Office | Low | Low | Limited | | **Container (Docker)** | Medium Business | Medium | Medium | Good | | **Kubernetes** | Enterprise | High | Medium | Excellent | | **Cloud Managed** | Global Enterprise | Medium | High | Excellent | ## 🖥️ Infrastructure Requirements ### Minimum Production Requirements **Single Instance:** ```yaml Compute: CPU: 2 cores (3.0 GHz+) RAM: 4 GB Storage: 20 GB SSD Network: 1 Gbps Operating System: - Ubuntu 22.04 LTS (recommended) - RHEL 8/9 - CentOS Stream 9 - macOS 12+ (development only) ``` **High Availability Setup:** ```yaml Load Balancer: CPU: 2 cores RAM: 2 GB Network: 2x 1 Gbps (redundant) Application Servers (3x): CPU: 4 cores each RAM: 8 GB each Storage: 50 GB SSD each Network: 1 Gbps each Shared Storage: Type: Network-attached (NFS/iSCSI) Capacity: 200 GB IOPS: 1000+ (logs and cache) ``` ### Network Requirements ```yaml Firewall Rules: Inbound: - TCP 443 (HTTPS API) - TCP 22 (SSH management) Outbound: - TCP 8443 (UniFi Controller) - TCP 7443 (UniFi Protect) - TCP 443 (HTTPS/GitHub) - TCP 53 (DNS) - TCP 123 (NTP) Network Connectivity: - Direct access to UniFi Controllers - Internet access for updates - DNS resolution capability - NTP synchronization ``` ## 🐳 Container Deployment ### Docker Production Setup **1. Create Production Dockerfile:** ```dockerfile # Production Dockerfile FROM node:18-alpine AS builder # Install build dependencies RUN apk add --no-cache python3 make g++ WORKDIR /app COPY package*.json ./ RUN npm ci --only=production && npm cache clean --force # Production stage FROM node:18-alpine AS production # Security: Create non-root user RUN addgroup -g 1001 -S unifi-mcp && \ adduser -S unifi-mcp -u 1001 # Install production dependencies RUN apk add --no-cache \ dumb-init \ curl \ && rm -rf /var/cache/apk/* WORKDIR /app # Copy application files COPY --from=builder /app/node_modules ./node_modules COPY --chown=unifi-mcp:unifi-mcp . . # Build application RUN npm run build && \ chown -R unifi-mcp:unifi-mcp /app # Switch to non-root user USER unifi-mcp # Health check HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \ CMD curl -f http://localhost:3000/health || exit 1 # Security: Read-only root filesystem VOLUME ["/tmp", "/app/logs"] # Use init system for proper signal handling ENTRYPOINT ["/usr/bin/dumb-init", "--"] CMD ["node", "dist/index.js"] # Metadata LABEL maintainer="your-email@company.com" LABEL version="1.0.0" LABEL description="UniFi PRO MCP Server" ``` **2. Docker Compose Production Configuration:** ```yaml version: '3.8' services: unifi-mcp: build: context: . dockerfile: Dockerfile.production image: unifi-mcp:latest container_name: unifi-mcp-prod restart: unless-stopped # Resource limits deploy: resources: limits: cpus: '2.0' memory: 4G reservations: cpus: '1.0' memory: 2G # Environment configuration environment: - NODE_ENV=production - LOG_LEVEL=info - REDIS_URL=redis://redis:6379 # Secrets management secrets: - unifi_credentials - ssl_certificates # Network configuration networks: - unifi_network # Port mapping ports: - "3000:3000" # Volume mounts volumes: - logs_volume:/app/logs - cache_volume:/app/cache - /etc/ssl/certs:/etc/ssl/certs:ro # Security context security_opt: - no-new-privileges:true cap_drop: - ALL cap_add: - NET_BIND_SERVICE # Health check healthcheck: test: ["CMD", "curl", "-f", "http://localhost:3000/health"] interval: 30s timeout: 10s retries: 3 start_period: 30s # Redis for caching redis: image: redis:7-alpine container_name: redis-cache restart: unless-stopped command: redis-server --appendonly yes volumes: - redis_data:/data networks: - unifi_network deploy: resources: limits: memory: 1G # Nginx reverse proxy nginx: image: nginx:alpine container_name: nginx-proxy restart: unless-stopped ports: - "80:80" - "443:443" volumes: - ./nginx.conf:/etc/nginx/nginx.conf:ro - ssl_certs:/etc/ssl/certs:ro networks: - unifi_network depends_on: - unifi-mcp # Log aggregation fluentd: image: fluent/fluentd:v1.16 container_name: log-collector volumes: - ./fluentd.conf:/fluentd/etc/fluent.conf:ro - logs_volume:/app/logs:ro networks: - unifi_network # Network configuration networks: unifi_network: driver: bridge ipam: driver: default config: - subnet: 172.20.0.0/16 # Volume configuration volumes: logs_volume: driver: local cache_volume: driver: local redis_data: driver: local ssl_certs: external: true # Secrets configuration secrets: unifi_credentials: file: ./secrets/credentials.env ssl_certificates: file: ./secrets/ssl_bundle.pem ``` **3. Production Nginx Configuration:** ```nginx # nginx.conf events { worker_connections 1024; } http { upstream unifi_mcp { server unifi-mcp:3000 max_fails=3 fail_timeout=30s; keepalive 32; } # Rate limiting limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s; # SSL configuration ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512; ssl_prefer_server_ciphers off; ssl_session_cache shared:SSL:10m; ssl_session_timeout 10m; server { listen 443 ssl http2; server_name unifi-mcp.company.com; # SSL certificates ssl_certificate /etc/ssl/certs/unifi-mcp.crt; ssl_certificate_key /etc/ssl/certs/unifi-mcp.key; # Security headers add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; add_header X-Frame-Options DENY always; add_header X-Content-Type-Options nosniff always; add_header X-XSS-Protection "1; mode=block" always; # Rate limiting limit_req zone=api burst=20 nodelay; # Proxy configuration location / { proxy_pass http://unifi_mcp; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # Timeouts proxy_connect_timeout 30s; proxy_send_timeout 30s; proxy_read_timeout 30s; proxy_buffering off; } # Health check endpoint location /health { proxy_pass http://unifi_mcp/health; access_log off; } } # HTTP redirect to HTTPS server { listen 80; server_name unifi-mcp.company.com; return 301 https://$server_name$request_uri; } } ``` ## ☁️ Cloud Deployment ### AWS Deployment **1. ECS with Fargate:** ```yaml # aws-ecs-task-definition.json { "family": "unifi-mcp", "networkMode": "awsvpc", "requiresCompatibilities": ["FARGATE"], "cpu": "1024", "memory": "2048", "executionRoleArn": "arn:aws:iam::ACCOUNT:role/ecsTaskExecutionRole", "taskRoleArn": "arn:aws:iam::ACCOUNT:role/unifi-mcp-task-role", "containerDefinitions": [ { "name": "unifi-mcp", "image": "your-account.dkr.ecr.region.amazonaws.com/unifi-mcp:latest", "essential": true, "portMappings": [ { "containerPort": 3000, "protocol": "tcp" } ], "environment": [ { "name": "NODE_ENV", "value": "production" }, { "name": "REDIS_URL", "value": "redis://unifi-cache.aws.com:6379" } ], "secrets": [ { "name": "UNIFI_PASSWORD", "valueFrom": "arn:aws:secretsmanager:region:account:secret:unifi-credentials" } ], "logConfiguration": { "logDriver": "awslogs", "options": { "awslogs-group": "/aws/ecs/unifi-mcp", "awslogs-region": "us-west-2", "awslogs-stream-prefix": "ecs" } }, "healthCheck": { "command": ["CMD-SHELL", "curl -f http://localhost:3000/health || exit 1"], "interval": 30, "timeout": 5, "retries": 3, "startPeriod": 60 } } ] } ``` **2. Infrastructure as Code (Terraform):** ```hcl # main.tf provider "aws" { region = var.aws_region } # VPC and networking module "vpc" { source = "terraform-aws-modules/vpc/aws" version = "~> 3.0" name = "unifi-mcp-vpc" cidr = "10.0.0.0/16" azs = ["${var.aws_region}a", "${var.aws_region}b"] private_subnets = ["10.0.1.0/24", "10.0.2.0/24"] public_subnets = ["10.0.101.0/24", "10.0.102.0/24"] enable_nat_gateway = true enable_vpn_gateway = true single_nat_gateway = false tags = { Environment = "production" Project = "unifi-mcp" } } # Application Load Balancer resource "aws_lb" "main" { name = "unifi-mcp-alb" internal = false load_balancer_type = "application" security_groups = [aws_security_group.alb.id] subnets = module.vpc.public_subnets enable_deletion_protection = true tags = { Environment = "production" } } # ECS Cluster resource "aws_ecs_cluster" "main" { name = "unifi-mcp-cluster" capacity_providers = ["FARGATE", "FARGATE_SPOT"] default_capacity_provider_strategy { capacity_provider = "FARGATE" weight = 1 } setting { name = "containerInsights" value = "enabled" } tags = { Environment = "production" } } # ECS Service resource "aws_ecs_service" "main" { name = "unifi-mcp-service" cluster = aws_ecs_cluster.main.id task_definition = aws_ecs_task_definition.main.arn desired_count = 3 launch_type = "FARGATE" network_configuration { security_groups = [aws_security_group.ecs_tasks.id] subnets = module.vpc.private_subnets } load_balancer { target_group_arn = aws_lb_target_group.main.arn container_name = "unifi-mcp" container_port = 3000 } depends_on = [aws_lb_listener.main] tags = { Environment = "production" } } # ElastiCache Redis resource "aws_elasticache_subnet_group" "main" { name = "unifi-mcp-cache-subnet" subnet_ids = module.vpc.private_subnets } resource "aws_elasticache_cluster" "redis" { cluster_id = "unifi-mcp-cache" engine = "redis" node_type = "cache.t3.micro" num_cache_nodes = 1 parameter_group_name = "default.redis7" port = 6379 subnet_group_name = aws_elasticache_subnet_group.main.name security_group_ids = [aws_security_group.redis.id] tags = { Environment = "production" } } ``` ### Google Cloud Platform **1. Cloud Run Deployment:** ```yaml # cloudrun-service.yaml apiVersion: serving.knative.dev/v1 kind: Service metadata: name: unifi-mcp namespace: default annotations: run.googleapis.com/ingress: all run.googleapis.com/execution-environment: gen2 spec: template: metadata: annotations: autoscaling.knative.dev/maxScale: "10" autoscaling.knative.dev/minScale: "1" run.googleapis.com/cpu-throttling: "false" run.googleapis.com/execution-environment: gen2 spec: containerConcurrency: 100 timeoutSeconds: 300 containers: - image: gcr.io/PROJECT_ID/unifi-mcp:latest ports: - containerPort: 3000 env: - name: NODE_ENV value: production - name: REDIS_URL valueFrom: secretKeyRef: name: redis-connection key: url resources: limits: cpu: "2" memory: "4Gi" livenessProbe: httpGet: path: /health port: 3000 initialDelaySeconds: 30 periodSeconds: 30 readinessProbe: httpGet: path: /ready port: 3000 initialDelaySeconds: 10 periodSeconds: 10 ``` **2. Kubernetes Deployment:** ```yaml # k8s-deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: unifi-mcp namespace: production spec: replicas: 3 strategy: type: RollingUpdate rollingUpdate: maxUnavailable: 1 maxSurge: 1 selector: matchLabels: app: unifi-mcp template: metadata: labels: app: unifi-mcp spec: securityContext: runAsNonRoot: true runAsUser: 1001 fsGroup: 1001 containers: - name: unifi-mcp image: unifi-mcp:1.0.0 ports: - containerPort: 3000 env: - name: NODE_ENV value: production - name: REDIS_URL valueFrom: configMapKeyRef: name: app-config key: redis-url - name: UNIFI_PASSWORD valueFrom: secretKeyRef: name: unifi-credentials key: password resources: requests: memory: "1Gi" cpu: "500m" limits: memory: "4Gi" cpu: "2" livenessProbe: httpGet: path: /health port: 3000 initialDelaySeconds: 30 periodSeconds: 30 readinessProbe: httpGet: path: /ready port: 3000 initialDelaySeconds: 10 periodSeconds: 10 securityContext: allowPrivilegeEscalation: false readOnlyRootFilesystem: true runAsNonRoot: true volumeMounts: - name: tmp-volume mountPath: /tmp - name: cache-volume mountPath: /app/cache volumes: - name: tmp-volume emptyDir: {} - name: cache-volume emptyDir: {} --- apiVersion: v1 kind: Service metadata: name: unifi-mcp-service namespace: production spec: selector: app: unifi-mcp ports: - port: 80 targetPort: 3000 type: ClusterIP --- apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: unifi-mcp-ingress namespace: production annotations: nginx.ingress.kubernetes.io/ssl-redirect: "true" cert-manager.io/cluster-issuer: "letsencrypt-prod" spec: tls: - hosts: - unifi-mcp.company.com secretName: unifi-mcp-tls rules: - host: unifi-mcp.company.com http: paths: - path: / pathType: Prefix backend: service: name: unifi-mcp-service port: number: 80 ``` ## 🔄 High Availability Setup ### Load Balancer Configuration **HAProxy Configuration:** ```bash # haproxy.cfg global daemon maxconn 4096 log stdout local0 defaults mode http timeout connect 5000ms timeout client 50000ms timeout server 50000ms option httplog option dontlognull option httpchk GET /health frontend unifi_mcp_frontend bind *:80 bind *:443 ssl crt /etc/ssl/certs/unifi-mcp.pem redirect scheme https if !{ ssl_fc } # Rate limiting stick-table type ip size 100k expire 30s store http_req_rate(10s) http-request track-sc0 src http-request reject if { sc_http_req_rate(0) gt 20 } default_backend unifi_mcp_backend backend unifi_mcp_backend balance roundrobin option httpchk GET /health server mcp1 10.0.1.10:3000 check inter 5s rise 2 fall 3 server mcp2 10.0.1.11:3000 check inter 5s rise 2 fall 3 server mcp3 10.0.1.12:3000 check inter 5s rise 2 fall 3 ``` ### Database High Availability **Redis Cluster Setup:** ```bash # redis-cluster-setup.sh #!/bin/bash # Create Redis cluster configuration cat > redis.conf << EOF port 7000 cluster-enabled yes cluster-config-file nodes.conf cluster-node-timeout 5000 appendonly yes appendfilename "appendonly.aof" cluster-require-full-coverage no # Security requirepass ${REDIS_PASSWORD} masterauth ${REDIS_PASSWORD} EOF # Start Redis nodes for port in 7000 7001 7002 7003 7004 7005; do mkdir -p cluster/${port} cp redis.conf cluster/${port}/ sed -i "s/7000/${port}/g" cluster/${port}/redis.conf redis-server cluster/${port}/redis.conf & done # Create cluster redis-cli --cluster create \ 127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 \ 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 \ --cluster-replicas 1 \ --cluster-yes ``` ## 📊 Monitoring & Observability ### Prometheus Configuration ```yaml # prometheus.yml global: scrape_interval: 15s evaluation_interval: 15s rule_files: - "unifi_mcp_rules.yml" alerting: alertmanagers: - static_configs: - targets: - alertmanager:9093 scrape_configs: - job_name: 'unifi-mcp' static_configs: - targets: ['unifi-mcp:3000'] metrics_path: '/metrics' scrape_interval: 30s - job_name: 'node-exporter' static_configs: - targets: ['node-exporter:9100'] - job_name: 'redis' static_configs: - targets: ['redis-exporter:9121'] ``` ### Grafana Dashboard ```json { "dashboard": { "title": "UniFi PRO MCP Monitoring", "panels": [ { "title": "Request Rate", "type": "graph", "targets": [ { "expr": "rate(http_requests_total[5m])", "legendFormat": "{{method}} {{status}}" } ] }, { "title": "Response Time", "type": "graph", "targets": [ { "expr": "histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m]))", "legendFormat": "95th percentile" } ] }, { "title": "UniFi Controller Health", "type": "singlestat", "targets": [ { "expr": "unifi_controller_available", "legendFormat": "Controller Status" } ] } ] } } ``` ### Application Metrics ```typescript // metrics.ts - Custom application metrics import { createPrometheusMetrics } from 'prom-client'; export class MetricsCollector { private requestCounter = new Counter({ name: 'unifi_mcp_requests_total', help: 'Total number of requests', labelNames: ['method', 'endpoint', 'status'] }); private responseTime = new Histogram({ name: 'unifi_mcp_response_duration_seconds', help: 'Response time in seconds', labelNames: ['method', 'endpoint'], buckets: [0.1, 0.5, 1, 2, 5] }); private unifiControllerStatus = new Gauge({ name: 'unifi_controller_available', help: 'UniFi Controller availability', labelNames: ['controller'] }); recordRequest(method: string, endpoint: string, status: number, duration: number) { this.requestCounter.inc({ method, endpoint, status: status.toString() }); this.responseTime.observe({ method, endpoint }, duration); } updateControllerStatus(controller: string, available: boolean) { this.unifiControllerStatus.set({ controller }, available ? 1 : 0); } } ``` ## 💾 Backup & Recovery ### Backup Strategy ```bash #!/bin/bash # backup-script.sh set -euo pipefail BACKUP_DIR="/backups/unifi-mcp" DATE=$(date +%Y%m%d_%H%M%S) RETENTION_DAYS=30 # Create backup directory mkdir -p "${BACKUP_DIR}/${DATE}" # Backup configuration echo "Backing up configuration..." cp -r /app/config "${BACKUP_DIR}/${DATE}/" # Backup Redis data echo "Backing up Redis data..." redis-cli --rdb "${BACKUP_DIR}/${DATE}/dump.rdb" # Backup logs echo "Backing up logs..." tar -czf "${BACKUP_DIR}/${DATE}/logs.tar.gz" /app/logs/ # Backup application state echo "Backing up application state..." kubectl get configmaps,secrets -o yaml > "${BACKUP_DIR}/${DATE}/k8s-resources.yaml" # Encrypt backup echo "Encrypting backup..." tar -czf - "${BACKUP_DIR}/${DATE}" | \ gpg --cipher-algo AES256 --compress-algo 1 --symmetric \ --output "${BACKUP_DIR}/${DATE}.tar.gz.gpg" # Upload to cloud storage echo "Uploading to S3..." aws s3 cp "${BACKUP_DIR}/${DATE}.tar.gz.gpg" \ "s3://unifi-mcp-backups/${DATE}.tar.gz.gpg" # Cleanup old backups echo "Cleaning up old backups..." find "${BACKUP_DIR}" -type d -mtime +${RETENTION_DAYS} -exec rm -rf {} + aws s3 ls "s3://unifi-mcp-backups/" | \ while read -r line; do createDate=$(echo "$line" | awk '{print $1" "$2}') createDate=$(date -d"$createDate" +%s) olderThan=$(date -d"-${RETENTION_DAYS} days" +%s) if [[ $createDate -lt $olderThan ]]; then fileName=$(echo "$line" | awk '{print $4}') if [[ $fileName != "" ]]; then aws s3 rm "s3://unifi-mcp-backups/$fileName" fi fi done echo "Backup completed successfully" ``` ### Disaster Recovery Procedures ```bash #!/bin/bash # disaster-recovery.sh set -euo pipefail BACKUP_DATE=${1:-latest} BACKUP_DIR="/recovery/unifi-mcp" echo "Starting disaster recovery for backup: ${BACKUP_DATE}" # Download backup from S3 if [ "${BACKUP_DATE}" = "latest" ]; then BACKUP_FILE=$(aws s3 ls s3://unifi-mcp-backups/ | sort | tail -n 1 | awk '{print $4}') else BACKUP_FILE="${BACKUP_DATE}.tar.gz.gpg" fi echo "Downloading backup: ${BACKUP_FILE}" aws s3 cp "s3://unifi-mcp-backups/${BACKUP_FILE}" "${BACKUP_DIR}/" # Decrypt and extract echo "Decrypting backup..." gpg --decrypt "${BACKUP_DIR}/${BACKUP_FILE}" | tar -xzf - -C "${BACKUP_DIR}/" # Restore configuration echo "Restoring configuration..." cp -r "${BACKUP_DIR}"/*/config/* /app/config/ # Restore Redis data echo "Restoring Redis data..." redis-cli --rdb "${BACKUP_DIR}"/*/dump.rdb # Restore Kubernetes resources echo "Restoring Kubernetes resources..." kubectl apply -f "${BACKUP_DIR}"/*/k8s-resources.yaml # Restart services echo "Restarting services..." kubectl rollout restart deployment/unifi-mcp # Verify recovery echo "Verifying recovery..." sleep 30 if curl -f http://localhost:3000/health; then echo "Disaster recovery completed successfully" else echo "Disaster recovery failed - services not responding" exit 1 fi ``` ## ⚡ Performance Optimization ### Node.js Optimization ```typescript // performance-config.ts export const performanceConfig = { // Node.js runtime options nodeOptions: [ '--max-old-space-size=4096', // 4GB heap '--gc-interval=100', // Frequent GC '--optimize-for-size', // Memory optimization '--max-semi-space-size=128' // Young generation size ], // Application-level optimizations cache: { enabled: true, ttl: 300000, // 5 minutes maxSize: 10000, // 10k entries compression: true }, // Connection pooling connections: { maxSockets: 50, keepAlive: true, keepAliveMsecs: 300000, timeout: 30000 }, // Request optimization requests: { compression: true, timeout: 30000, retries: 3, retryDelay: 1000 } }; ``` ### Caching Strategy ```typescript // cache-manager.ts import { Redis } from 'ioredis'; import { LRUCache } from 'lru-cache'; export class CacheManager { private redis: Redis; private memoryCache: LRUCache<string, any>; constructor() { this.redis = new Redis(process.env.REDIS_URL); this.memoryCache = new LRUCache({ max: 1000, ttl: 5 * 60 * 1000, // 5 minutes }); } async get<T>(key: string): Promise<T | null> { // Try memory cache first (fastest) const memoryResult = this.memoryCache.get(key); if (memoryResult) { return memoryResult; } // Try Redis (network cache) const redisResult = await this.redis.get(key); if (redisResult) { const parsed = JSON.parse(redisResult); this.memoryCache.set(key, parsed); return parsed; } return null; } async set<T>(key: string, value: T, ttl: number = 300): Promise<void> { const serialized = JSON.stringify(value); // Store in both caches this.memoryCache.set(key, value); await this.redis.setex(key, ttl, serialized); } async invalidate(pattern: string): Promise<void> { // Clear memory cache for (const key of this.memoryCache.keys()) { if (key.match(pattern)) { this.memoryCache.delete(key); } } // Clear Redis cache const keys = await this.redis.keys(pattern); if (keys.length > 0) { await this.redis.del(...keys); } } } ``` ## 🔄 CI/CD Pipeline ### GitHub Actions Workflow ```yaml # .github/workflows/production-deploy.yml name: Production Deploy on: push: branches: [main] release: types: [published] env: REGISTRY: ghcr.io IMAGE_NAME: ${{ github.repository }} jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Setup Node.js uses: actions/setup-node@v3 with: node-version: '18' cache: 'npm' - name: Install dependencies run: npm ci - name: Run tests run: npm run test:ci - name: Security audit run: npm audit --audit-level high - name: Lint code run: npm run lint build: needs: test runs-on: ubuntu-latest outputs: image: ${{ steps.image.outputs.image }} steps: - uses: actions/checkout@v3 - name: Setup Docker Buildx uses: docker/setup-buildx-action@v2 - name: Login to Container Registry uses: docker/login-action@v2 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Extract metadata id: meta uses: docker/metadata-action@v4 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} tags: | type=ref,event=branch type=ref,event=pr type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} - name: Build and push uses: docker/build-push-action@v4 with: context: . platforms: linux/amd64,linux/arm64 push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=max - name: Output image id: image run: echo "image=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}" >> $GITHUB_OUTPUT deploy-staging: needs: build runs-on: ubuntu-latest environment: staging steps: - name: Deploy to staging run: | echo "Deploying ${{ needs.build.outputs.image }} to staging" # Add deployment commands deploy-production: needs: [build, deploy-staging] runs-on: ubuntu-latest environment: production if: github.ref == 'refs/heads/main' steps: - uses: actions/checkout@v3 - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v2 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-region: us-west-2 - name: Update ECS service run: | # Update task definition with new image aws ecs describe-task-definition \ --task-definition unifi-mcp \ --query taskDefinition > task-def.json # Update image in task definition jq '.containerDefinitions[0].image = "${{ needs.build.outputs.image }}"' \ task-def.json > new-task-def.json # Register new task definition aws ecs register-task-definition \ --cli-input-json file://new-task-def.json # Update service aws ecs update-service \ --cluster unifi-mcp-cluster \ --service unifi-mcp-service \ --task-definition unifi-mcp - name: Wait for deployment run: | aws ecs wait services-stable \ --cluster unifi-mcp-cluster \ --services unifi-mcp-service ``` ### Quality Gates ```bash #!/bin/bash # quality-gates.sh set -euo pipefail echo "Running quality gates..." # Code coverage threshold COVERAGE_THRESHOLD=85 COVERAGE=$(npm run test:coverage --silent | grep -oP 'All files.*?(\d+\.\d+)' | grep -oP '\d+\.\d+' | tail -1) if (( $(echo "$COVERAGE < $COVERAGE_THRESHOLD" | bc -l) )); then echo "Code coverage $COVERAGE% is below threshold $COVERAGE_THRESHOLD%" exit 1 fi # Security vulnerabilities if ! npm audit --audit-level high --dry-run; then echo "High severity vulnerabilities found" exit 1 fi # Performance tests if ! npm run test:performance; then echo "Performance tests failed" exit 1 fi # Integration tests if ! npm run test:integration; then echo "Integration tests failed" exit 1 fi echo "All quality gates passed" ``` ## 🔧 Maintenance Procedures ### Scheduled Maintenance ```bash #!/bin/bash # scheduled-maintenance.sh set -euo pipefail MAINTENANCE_WINDOW="02:00-04:00" LOG_FILE="/var/log/unifi-mcp-maintenance.log" log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE" } check_maintenance_window() { local current_time=$(date '+%H:%M') local start_time="${MAINTENANCE_WINDOW%-*}" local end_time="${MAINTENANCE_WINDOW#*-}" if [[ "$current_time" > "$start_time" && "$current_time" < "$end_time" ]]; then return 0 else log "Current time $current_time is outside maintenance window $MAINTENANCE_WINDOW" return 1 fi } perform_maintenance() { log "Starting scheduled maintenance" # Update system packages log "Updating system packages" apt update && apt upgrade -y # Clean up Docker images log "Cleaning up Docker images" docker system prune -f # Rotate logs log "Rotating logs" logrotate -f /etc/logrotate.d/unifi-mcp # Update application log "Checking for application updates" if docker pull "$IMAGE_NAME:latest"; then log "New version available, updating..." docker-compose down docker-compose up -d # Health check sleep 30 if curl -f http://localhost:3000/health; then log "Update successful" else log "Update failed, rolling back" docker-compose down docker-compose up -d fi else log "No updates available" fi # Database maintenance log "Performing database maintenance" redis-cli BGREWRITEAOF # Certificate renewal log "Checking certificate expiration" if openssl x509 -checkend 2592000 -noout -in /etc/ssl/certs/unifi-mcp.crt; then log "Certificate is valid" else log "Certificate expires soon, renewing..." certbot renew --nginx fi log "Scheduled maintenance completed" } # Main execution if check_maintenance_window; then perform_maintenance else log "Skipping maintenance - outside window" fi ``` ### Health Monitoring ```typescript // health-monitor.ts export class HealthMonitor { private checks: Map<string, () => Promise<boolean>> = new Map(); constructor() { this.registerChecks(); this.startMonitoring(); } private registerChecks() { this.checks.set('database', this.checkDatabase); this.checks.set('unifi-controller', this.checkUniFiController); this.checks.set('memory', this.checkMemoryUsage); this.checks.set('disk', this.checkDiskSpace); this.checks.set('network', this.checkNetworkConnectivity); } private async checkDatabase(): Promise<boolean> { try { await this.redis.ping(); return true; } catch (error) { logger.error('Database health check failed', error); return false; } } private async checkUniFiController(): Promise<boolean> { try { const response = await axios.get(`${this.controllerUrl}/inform`, { timeout: 5000, validateStatus: () => true }); return response.status < 500; } catch (error) { logger.error('UniFi Controller health check failed', error); return false; } } private async checkMemoryUsage(): Promise<boolean> { const usage = process.memoryUsage(); const heapUsed = usage.heapUsed / 1024 / 1024; // MB const heapLimit = 4096; // 4GB limit return (heapUsed / heapLimit) < 0.9; // Alert at 90% } private async checkDiskSpace(): Promise<boolean> { const stats = await fs.promises.statfs('/'); const freePercent = (stats.free / stats.blocks) * 100; return freePercent > 10; // Alert when <10% free } private startMonitoring() { setInterval(async () => { const results = new Map<string, boolean>(); for (const [name, check] of this.checks) { try { results.set(name, await check()); } catch (error) { results.set(name, false); } } this.processHealthResults(results); }, 30000); // Check every 30 seconds } private processHealthResults(results: Map<string, boolean>) { const failedChecks = Array.from(results.entries()) .filter(([_, healthy]) => !healthy) .map(([name]) => name); if (failedChecks.length > 0) { this.alerting.sendAlert({ severity: 'warning', message: `Health checks failed: ${failedChecks.join(', ')}`, timestamp: new Date().toISOString() }); } // Update metrics for (const [name, healthy] of results) { this.metrics.healthCheck.set({ check: name }, healthy ? 1 : 0); } } } ``` --- This production deployment guide provides comprehensive coverage for enterprise-grade UniFi PRO MCP deployments. The configurations and procedures ensure reliability, security, and scalability in production environments.