Files
nexa/nexa-core/DEPLOYMENT.md
T
Florian Krebs 18d2360ad9 initial commit
2026-05-04 20:49:45 +00:00

15 KiB

NEXA Deployment Guide

🚀 Architektur-Überblick

┌─────────────────────────────────────────────────â”
│         MEMOS (Port 5230)                       │
│    Interface, Voice-Input, Webhooks             │
└────────────────────┬────────────────────────────┘
                     │
         ┌───────────┼───────────â”
         ↓           ↓           ↓
    ┌────────┠ ┌────────┠ ┌──────────â”
    │  n8n   │  │ SAIA   │  │ Nextcloud│
    │Logic   │  │LiteLLM │  │Tasks/Mail│
    │7879    │  │4000    │  │80/443    │
    └────┬───┘  └────┬───┘  └─────┬────┘
         │           │            │
    ┌────┴───────────┼────────────┴────â”
    ↓                ↓                  ↓
┌─────────┠  ┌────────────┠  ┌──────────────â”
│ QDRANT  │   │ GraphDB    │   │ Obsidian     │
│Vector   │   │ (Ontotext) │   │(local sync)  │
│6333     │   │7200        │   │              │
└─────────┘   └────────────┘   └──────────────┘

📋 Voraussetzungen

Hardware

  • CPU: Min. 4 Cores (empfohlen 8)
  • RAM: Min. 16 GB (empfohlen 32 GB)
  • Storage: Min. 100 GB SSD (für Datenbanken & Backups)
  • Network: Stabile Verbindung, HTTPS-ready

Software

  • Docker & Docker Compose (Version 20.10+)
  • Git
  • Bash
  • curl/wget
  • .env Datei aus .env.example (mit Secrets gefüllt)

Accounts & Credentials

  • Nextcloud Instanz mit Admin-Zugang
  • OpenAI API-Key (für SAIA/LiteLLM)
  • Obsidian Vault Zugang lokalem Filesystem

🳠Schritt 1: Docker Compose Setup

1.1 Repository klonen

git clone https://github.com/noheton/nexa.git
cd nexa/nexa-core

1.2 .env konfigurieren

cp .env.example .env
# WICHTIG: Alle Secrets eintragen!
# - MEMOS_API_KEY
# - SAIA_API_KEY
# - NC_APP_PASSWORD
# - QDRANT_API_KEY
# - GRAPHDB_PASSWORD
nano .env

1.3 docker-compose.yml erstellen

cat > docker-compose.yml << 'EOF'
version: '3.8'

services:
  # ===== MEMOS (Interface) =====
  memos:
    image: neosmemo/memos:latest
    container_name: nexa-memos
    ports:
      - "5230:5230"
    volumes:
      - ./data/memos:/root/.memos
    environment:
      MEMOS_PORT: "5230"
      MEMOS_DRIVER: sqlite
      MEMOS_DSN: /root/.memos/memos.db
    networks:
      - nexa
    restart: unless-stopped

  # ===== n8n (Workflow Engine) =====
  n8n:
    image: n8nio/n8n:latest
    container_name: nexa-n8n
    ports:
      - "5678:5678"
    volumes:
      - ./data/n8n:/home/node/.n8n
      - ./n8n-workflows:/workflows
    environment:
      N8N_BASIC_AUTH_ACTIVE: "true"
      N8N_BASIC_AUTH_USER: "${N8N_USER:-admin}"
      N8N_BASIC_AUTH_PASSWORD: "${N8N_PASSWORD:-nexa_secure_pass}"
      N8N_HOST: "${N8N_DOMAIN:-localhost}"
      N8N_PORT: "5678"
      N8N_PROTOCOL: https
      N8N_WEBHOOK_URL: "https://${N8N_DOMAIN:-localhost}/webhook"
      GENERIC_TIMEZONE: Europe/Berlin
      DB_TYPE: sqlite
      DB_SQLITE_PATH: /home/node/.n8n/nexa.db
    networks:
      - nexa
    restart: unless-stopped
    depends_on:
      - memos

  # ===== QDRANT (Vector DB) =====
  qdrant:
    image: qdrant/qdrant:latest
    container_name: nexa-qdrant
    ports:
      - "6333:6333"
    volumes:
      - ./data/qdrant:/qdrant/storage
    environment:
      QDRANT_API_KEY: "${QDRANT_API_KEY}"
    networks:
      - nexa
    restart: unless-stopped

  # ===== GraphDB (Ontotext SPARQL) =====
  graphdb:
    image: ontotext/graphdb:10-ee
    container_name: nexa-graphdb
    ports:
      - "7200:7200"
    volumes:
      - ./data/graphdb:/opt/graphdb/data
      - ./config/graphdb-init.ttl:/graphdb-import/import.ttl
    environment:
      GDB_JAVA_OPTS: "-Xmx8g -Xms4g"
      GDB_PASSWORD: "${GRAPHDB_PASSWORD}"
    networks:
      - nexa
    restart: unless-stopped

  # ===== SAIA / LiteLLM Proxy (optional - wenn lokal) =====
  # (Falls SAIA extern gehostet: nicht nötig)

networks:
  nexa:
    driver: bridge

volumes:
  nexa_memos:
  nexa_n8n:
  nexa_qdrant:
  nexa_graphdb:
EOF

🔧 Schritt 2: Individuelle Service-Konfiguration

2.1 MEMOS Initialisierung

# Nach memos startup (ca. 30s)
curl -X POST http://localhost:5230/api/v1/auth/signup \
  -H "Content-Type: application/json" \
  -d '{
    "username": "nexa",
    "password": "secure_password"
  }'

# API-Token generieren
TOKEN=$(curl -X POST http://localhost:5230/api/v1/auth/signin \
  -H "Content-Type: application/json" \
  -d '{"username":"nexa","password":"secure_password"}' \
  | jq -r '.data.token')

echo "MEMOS_API_KEY=$TOKEN" >> .env

2.2 Qdrant Konfiguration

# Warte auf Qdrant startup
sleep 10

# Erstelle Collection für Nexa
curl -X PUT "http://localhost:6333/collections/nexa_knowledge" \
  -H "Content-Type: application/json" \
  -H "api-key: ${QDRANT_API_KEY}" \
  -d '{
    "vectors": {
      "size": 1536,
      "distance": "Cosine"
    },
    "optimizers_config": {
      "deleted_threshold": 0.2,
      "vacuum_threshold": 0.5,
      "default_segment_number": 5,
      "max_segment_number": 30,
      "memmap_threshold": 268435456,
      "indexing_threshold": 20000,
      "flush_interval_sec": 10,
      "max_optimization_threads": 4
    }
  }'

echo "✅ Qdrant Collection erstellt"

2.3 GraphDB Initialisierung

# Warte auf GraphDB startup
sleep 15

# Erstelle Repository
GRAPHDB_URL="http://localhost:7200"

curl -X POST "$GRAPHDB_URL/rest/repositories" \
  -H "Content-Type: application/json" \
  -u "admin:${GRAPHDB_PASSWORD}" \
  -d '{
    "id": "nexa_knowledge",
    "title": "Nexa Knowledge Graph",
    "type": "free",
    "params": {
      "baseURL": {
        "label": "Base URL",
        "value": "http://nexa.local/"
      },
      "defaultLanguage": {
        "label": "Default language",
        "value": "en"
      }
    }
  }'

echo "✅ GraphDB Repository erstellt"

2.4 n8n Workflows Import

# Warte auf n8n startup (ca. 60s)
sleep 60

# Importiere Discovery Workflow
curl -X POST http://localhost:5678/api/v1/workflows \
  -H "Content-Type: application/json" \
  -u "admin:${N8N_PASSWORD}" \
  -d @n8n-workflows/phase-1/1_1_discovery_workflow.json

# Importiere Memos Bridge
curl -X POST http://localhost:5678/api/v1/workflows \
  -H "Content-Type: application/json" \
  -u "admin:${N8N_PASSWORD}" \
  -d @n8n-workflows/phase-1/1_2_memos_bridge_v2.json

# Importiere Email Butler
curl -X POST http://localhost:5678/api/v1/workflows \
  -H "Content-Type: application/json" \
  -u "admin:${N8N_PASSWORD}" \
  -d @n8n-workflows/phase-2/2_1_email_butler.json

echo "✅ n8n Workflows importiert"

🚀 Schritt 3: Deployment starten

3.1 Docker Compose Up

docker-compose up -d

# Logs überwachen
docker-compose logs -f

3.2 Health Checks

# Health Check Script
check_service() {
  local name=$1
  local url=$2
  echo "Checking $name..."
  if curl -s "$url" > /dev/null; then
    echo "✅ $name: OK"
  else
    echo "⌠$name: FAILED"
  fi
}

sleep 30

check_service "Memos" "http://localhost:5230"
check_service "n8n" "http://localhost:5678"
check_service "Qdrant" "http://localhost:6333/health"
check_service "GraphDB" "http://localhost:7200/health"

echo ""
echo "🎯 Service URLs:"
echo "  - Memos: http://localhost:5230"
echo "  - n8n: http://localhost:5678"
echo "  - Qdrant: http://localhost:6333"
echo "  - GraphDB: http://localhost:7200"

âš™ï¸ Schritt 4: Konfiguration via Command System

4.1 Discovery-Workflow triggern

# In Memos Memo erstellen:
# #nexa:config

# Oder via API:
curl -X POST http://localhost:5678/api/v1/workflows/run \
  -H "Content-Type: application/json" \
  -u "admin:${N8N_PASSWORD}" \
  -d '{
    "workflowId": "discovery-workflow-id"
  }'

4.2 Automatische Konfiguration laden

Nach erfolgreichem Discovery werden Nextcloud-Listen-IDs:

# Es wird automatisch aktualisiert in:
# - Qdrant: als Metadata
# - n8n: als Umgebungsvariablen
# - Memos: als Confirmation-Memo

🔠Schritt 5: Reverse Proxy Setup (Production)

5.1 Nginx Configuration

cat > /etc/nginx/sites-available/nexa.conf << 'EOF'
upstream memos_backend {
    server localhost:5230;
}

upstream n8n_backend {
    server localhost:5678;
}

upstream qdrant_backend {
    server localhost:6333;
}

upstream graphdb_backend {
    server localhost:7200;
}

server {
    listen 443 ssl http2;
    server_name nexa.yourdomain.com;

    ssl_certificate /etc/letsencrypt/live/nexa.yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/nexa.yourdomain.com/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;

    # MEMOS
    location / {
        proxy_pass http://memos_backend;
        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;
        proxy_read_timeout 3600s;
        proxy_send_timeout 3600s;
    }

    # n8n
    location /n8n/ {
        proxy_pass http://n8n_backend/;
        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;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }

    # Qdrant (nur intern, wenn nötig)
    location /qdrant/ {
        allow 10.0.0.0/8;
        deny all;
        proxy_pass http://qdrant_backend/;
    }

    # GraphDB (nur intern)
    location /graphdb/ {
        allow 10.0.0.0/8;
        deny all;
        proxy_pass http://graphdb_backend/;
    }

    # Webhook endpoint für externe Services
    location /webhook/ {
        proxy_pass http://n8n_backend/webhook/;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

server {
    listen 80;
    server_name nexa.yourdomain.com;
    return 301 https://$server_name$request_uri;
}
EOF

# Aktivieren
sudo ln -s /etc/nginx/sites-available/nexa.conf /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

📊 Schritt 6: Monitoring & Backups

6.1 Backup Script

cat > scripts/backup_nexa.sh << 'EOF'
#!/bin/bash

BACKUP_DIR="./backups/$(date +%Y%m%d_%H%M%S)"
mkdir -p "$BACKUP_DIR"

echo "🔄 Backing up Nexa..."

# Backup Datenbanken
docker-compose exec -T qdrant tar czf - /qdrant/storage > "$BACKUP_DIR/qdrant.tar.gz"
docker-compose exec -T graphdb tar czf - /opt/graphdb/data > "$BACKUP_DIR/graphdb.tar.gz"
docker-compose exec -T memos tar czf - /root/.memos > "$BACKUP_DIR/memos.tar.gz"
docker-compose exec -T n8n tar czf - /home/node/.n8n > "$BACKUP_DIR/n8n.tar.gz"

# Backup Configs
cp .env "$BACKUP_DIR/.env.backup"
cp docker-compose.yml "$BACKUP_DIR/docker-compose.yml.backup"

echo "✅ Backup completed: $BACKUP_DIR"

# Optional: zu S3 hochladen
# aws s3 sync "$BACKUP_DIR" s3://nexa-backups/$(date +%Y%m%d)/
EOF

chmod +x scripts/backup_nexa.sh

# Tägliches Backup (cron)
echo "0 2 * * * /path/to/nexa/scripts/backup_nexa.sh" | crontab -

6.2 Health Check Monitoring

cat > scripts/monitor_nexa.sh << 'EOF'
#!/bin/bash

# Monitore Service Status
SERVICES=("memos" "n8n" "qdrant" "graphdb")

for service in "${SERVICES[@]}"; do
  if docker-compose ps $service | grep -q "Up"; then
    echo "✅ $service: Running"
  else
    echo "⌠$service: DOWN"
    # Restart bei Fehler
    docker-compose restart $service
  fi
done
EOF

chmod +x scripts/monitor_nexa.sh

# Alle 5 Minuten
echo "*/5 * * * * /path/to/nexa/scripts/monitor_nexa.sh" | crontab -

🧪 Schritt 7: Validation & Testing

7.1 End-to-End Test

cat > scripts/test_nexa.sh << 'EOF'
#!/bin/bash

echo "🧪 Testing Nexa Installation..."

# Test 1: Memos API
echo "Test 1: Memos API"
if curl -s http://localhost:5230/api/v1/auth/status | jq . > /dev/null 2>&1; then
  echo "✅ Memos: OK"
else
  echo "⌠Memos: FAILED"
fi

# Test 2: n8n API
echo "Test 2: n8n API"
if curl -s -u "admin:${N8N_PASSWORD}" http://localhost:5678/api/v1/workflows | jq . > /dev/null 2>&1; then
  echo "✅ n8n: OK"
else
  echo "⌠n8n: FAILED"
fi

# Test 3: Qdrant Collection
echo "Test 3: Qdrant Collection"
if curl -s -H "api-key: ${QDRANT_API_KEY}" http://localhost:6333/collections/nexa_knowledge | jq . > /dev/null 2>&1; then
  echo "✅ Qdrant: OK"
else
  echo "⌠Qdrant: FAILED"
fi

# Test 4: GraphDB Repository
echo "Test 4: GraphDB Repository"
if curl -s -u "admin:${GRAPHDB_PASSWORD}" http://localhost:7200/repositories | jq . > /dev/null 2>&1; then
  echo "✅ GraphDB: OK"
else
  echo "⌠GraphDB: FAILED"
fi

echo ""
echo "🎯 Create Test Memo:"
MEMO=$(curl -s -X POST http://localhost:5230/api/v1/memos \
  -H "Authorization: Bearer ${MEMOS_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{"content": "Test memo - #nexa:config"}' | jq -r '.id')

echo "✅ Test Memo created: $MEMO"
echo "Check Memos UI to verify webhook triggers Discovery"
EOF

chmod +x scripts/test_nexa.sh
./scripts/test_nexa.sh

🆘 Troubleshooting

Problem: Memos kann sich nicht mit n8n verbinden

# Überprüfe n8n Webhook URL in Memos config
curl http://localhost:5678/webhook/memos

# Falls 404: Workflow ist nicht aktiviert/deployed
docker-compose logs n8n | grep webhook

Problem: GraphDB startet nicht

# Memory-Limit erhöhen
docker-compose exec graphdb -e "GDB_JAVA_OPTS=-Xmx16g -Xms8g" restart

# Logs checken
docker-compose logs graphdb

Problem: Qdrant Collection leer

# Überprüfe ob Memos Bridge Workflow läuft
docker-compose logs n8n | grep "memos_bridge"

# Manuell test
curl -X POST http://localhost:5230/api/v1/memos \
  -H "Authorization: Bearer ${MEMOS_API_KEY}" \
  -d '{"content": "- [ ] Test task #nexa"}'

📈 Next Steps

  1. ✅ Phase 1 deployt: Memos Bridge & Discovery laufen
  2. â³ Phase 2 aktivieren: Email Butler (wenn Nextcloud Mail konfiguriert)
  3. â³ Phase 3 setup: Qdrant Embedding Pipeline
  4. â³ Phase 4 integration: GraphRAG Queries testen
  5. ��³ Phase 5: Home Assistant Voice Integration

📞 Support & Debugging

# Alle Logs in eine Datei sammeln
docker-compose logs > deployment_logs.txt

# Spezifischer Service
docker-compose logs n8n --tail 100

# Wiederherstellung aus Backup
./scripts/restore_nexa.sh backup/2024-01-15_120000/

Deployment abgeschlossen! Nexa ist nun betriebsbereit. 🚀