- Add credentials management system with automatic saving and updates - Add upload form URL to JSON output - Add Ollama model information to JSON output - Implement credential update system (update_credentials.sh) - Implement credential save system (save_credentials.sh) - Add comprehensive test suites (infrastructure, n8n, PostgREST, complete system) - Add workflow auto-reload system with systemd service - Add detailed documentation (CREDENTIALS_MANAGEMENT.md, TEST_REPORT.md, VERIFICATION_SUMMARY.md) - Improve n8n setup with robust API-based workflow import - Add .gitignore for credentials directory - All tests passing (40+ test cases) Key Features: - Credentials automatically saved to credentials/<hostname>.json - Update Ollama URL from IP to hostname without container restart - Comprehensive testing with 4 test suites - Full documentation and examples - Production-ready system
380 lines
10 KiB
Bash
380 lines
10 KiB
Bash
#!/bin/bash
|
|
#
|
|
# n8n Workflow Auto-Reload Script
|
|
# Wird beim LXC-Start ausgeführt, um den Workflow neu zu laden
|
|
#
|
|
|
|
set -euo pipefail
|
|
|
|
# Konfiguration
|
|
SCRIPT_DIR="/opt/customer-stack"
|
|
LOG_DIR="${SCRIPT_DIR}/logs"
|
|
LOG_FILE="${LOG_DIR}/workflow-reload.log"
|
|
ENV_FILE="${SCRIPT_DIR}/.env"
|
|
WORKFLOW_TEMPLATE="${SCRIPT_DIR}/workflow-template.json"
|
|
WORKFLOW_NAME="RAG KI-Bot (PGVector)"
|
|
|
|
# API-Konfiguration
|
|
API_URL="http://127.0.0.1:5678"
|
|
COOKIE_FILE="/tmp/n8n_reload_cookies.txt"
|
|
MAX_WAIT=60 # Maximale Wartezeit in Sekunden
|
|
# Erstelle Log-Verzeichnis sofort (vor den Logging-Funktionen)
|
|
mkdir -p "${LOG_DIR}"
|
|
|
|
|
|
# Logging-Funktion
|
|
log() {
|
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "${LOG_FILE}"
|
|
}
|
|
|
|
log_error() {
|
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: $*" | tee -a "${LOG_FILE}" >&2
|
|
}
|
|
|
|
# Funktion: Warten bis n8n bereit ist
|
|
wait_for_n8n() {
|
|
log "Warte auf n8n API..."
|
|
local count=0
|
|
|
|
while [ $count -lt $MAX_WAIT ]; do
|
|
if curl -sS -o /dev/null -w "%{http_code}" "${API_URL}/rest/settings" 2>/dev/null | grep -q "200"; then
|
|
log "n8n API ist bereit"
|
|
return 0
|
|
fi
|
|
sleep 1
|
|
count=$((count + 1))
|
|
done
|
|
|
|
log_error "n8n API nicht erreichbar nach ${MAX_WAIT} Sekunden"
|
|
return 1
|
|
}
|
|
|
|
# Funktion: .env-Datei laden
|
|
load_env() {
|
|
if [ ! -f "${ENV_FILE}" ]; then
|
|
log_error ".env-Datei nicht gefunden: ${ENV_FILE}"
|
|
return 1
|
|
fi
|
|
|
|
# Exportiere alle Variablen aus .env
|
|
set -a
|
|
source "${ENV_FILE}"
|
|
set +a
|
|
|
|
log "Konfiguration geladen aus ${ENV_FILE}"
|
|
return 0
|
|
}
|
|
|
|
# Funktion: Login bei n8n
|
|
n8n_login() {
|
|
log "Login bei n8n als ${N8N_OWNER_EMAIL}..."
|
|
|
|
# Escape special characters in password for JSON
|
|
local escaped_password
|
|
escaped_password=$(echo "${N8N_OWNER_PASS}" | sed 's/\\/\\\\/g; s/"/\\"/g')
|
|
|
|
local response
|
|
response=$(curl -sS -X POST "${API_URL}/rest/login" \
|
|
-H "Content-Type: application/json" \
|
|
-c "${COOKIE_FILE}" \
|
|
-d "{\"emailOrLdapLoginId\":\"${N8N_OWNER_EMAIL}\",\"password\":\"${escaped_password}\"}" 2>&1)
|
|
|
|
if echo "$response" | grep -q '"code":\|"status":"error"'; then
|
|
log_error "Login fehlgeschlagen: ${response}"
|
|
return 1
|
|
fi
|
|
|
|
log "Login erfolgreich"
|
|
return 0
|
|
}
|
|
|
|
# Funktion: Workflow nach Name suchen
|
|
find_workflow() {
|
|
local workflow_name="$1"
|
|
|
|
log "Suche nach Workflow '${workflow_name}'..."
|
|
|
|
local response
|
|
response=$(curl -sS -X GET "${API_URL}/rest/workflows" \
|
|
-H "Content-Type: application/json" \
|
|
-b "${COOKIE_FILE}" 2>&1)
|
|
|
|
# Extract workflow ID by name
|
|
local workflow_id
|
|
workflow_id=$(echo "$response" | grep -oP "\"name\":\s*\"${workflow_name}\".*?\"id\":\s*\"\K[^\"]+|\"id\":\s*\"\K[^\"]+(?=.*?\"name\":\s*\"${workflow_name}\")" | head -1 || echo "")
|
|
|
|
if [ -n "$workflow_id" ]; then
|
|
log "Workflow gefunden: ID=${workflow_id}"
|
|
echo "$workflow_id"
|
|
return 0
|
|
else
|
|
log "Workflow '${workflow_name}' nicht gefunden"
|
|
echo ""
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Funktion: Workflow löschen
|
|
delete_workflow() {
|
|
local workflow_id="$1"
|
|
|
|
log "Lösche Workflow ${workflow_id}..."
|
|
|
|
local response
|
|
response=$(curl -sS -X DELETE "${API_URL}/rest/workflows/${workflow_id}" \
|
|
-H "Content-Type: application/json" \
|
|
-b "${COOKIE_FILE}" 2>&1)
|
|
|
|
log "Workflow ${workflow_id} gelöscht"
|
|
return 0
|
|
}
|
|
|
|
# Funktion: Credential nach Name und Typ suchen
|
|
find_credential() {
|
|
local cred_name="$1"
|
|
local cred_type="$2"
|
|
|
|
log "Suche nach Credential '${cred_name}' (Typ: ${cred_type})..."
|
|
|
|
local response
|
|
response=$(curl -sS -X GET "${API_URL}/rest/credentials" \
|
|
-H "Content-Type: application/json" \
|
|
-b "${COOKIE_FILE}" 2>&1)
|
|
|
|
# Extract credential ID by name and type
|
|
local cred_id
|
|
cred_id=$(echo "$response" | grep -oP "\"name\":\s*\"${cred_name}\".*?\"type\":\s*\"${cred_type}\".*?\"id\":\s*\"\K[^\"]+|\"id\":\s*\"\K[^\"]+(?=.*?\"name\":\s*\"${cred_name}\".*?\"type\":\s*\"${cred_type}\")" | head -1 || echo "")
|
|
|
|
if [ -n "$cred_id" ]; then
|
|
log "Credential gefunden: ID=${cred_id}"
|
|
echo "$cred_id"
|
|
return 0
|
|
else
|
|
log_error "Credential '${cred_name}' nicht gefunden"
|
|
echo ""
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Funktion: Workflow-Template verarbeiten
|
|
process_workflow_template() {
|
|
local pg_cred_id="$1"
|
|
local ollama_cred_id="$2"
|
|
local output_file="/tmp/workflow_processed.json"
|
|
|
|
log "Verarbeite Workflow-Template..."
|
|
|
|
# Python-Script zum Verarbeiten des Workflows
|
|
python3 - "$pg_cred_id" "$ollama_cred_id" <<'PYTHON_SCRIPT'
|
|
import json
|
|
import sys
|
|
|
|
# Read the workflow template
|
|
with open('/opt/customer-stack/workflow-template.json', 'r') as f:
|
|
workflow = json.load(f)
|
|
|
|
# Get credential IDs from arguments
|
|
pg_cred_id = sys.argv[1]
|
|
ollama_cred_id = sys.argv[2]
|
|
|
|
# Remove fields that should not be in the import
|
|
fields_to_remove = ['id', 'versionId', 'meta', 'tags', 'active', 'pinData']
|
|
for field in fields_to_remove:
|
|
workflow.pop(field, None)
|
|
|
|
# Process all nodes and replace credential IDs
|
|
for node in workflow.get('nodes', []):
|
|
credentials = node.get('credentials', {})
|
|
|
|
# Replace PostgreSQL credential
|
|
if 'postgres' in credentials:
|
|
credentials['postgres'] = {
|
|
'id': pg_cred_id,
|
|
'name': 'PostgreSQL (local)'
|
|
}
|
|
|
|
# Replace Ollama credential
|
|
if 'ollamaApi' in credentials:
|
|
credentials['ollamaApi'] = {
|
|
'id': ollama_cred_id,
|
|
'name': 'Ollama (local)'
|
|
}
|
|
|
|
# Write the processed workflow
|
|
with open('/tmp/workflow_processed.json', 'w') as f:
|
|
json.dump(workflow, f)
|
|
|
|
print("Workflow processed successfully")
|
|
PYTHON_SCRIPT
|
|
|
|
if [ $? -eq 0 ]; then
|
|
log "Workflow-Template erfolgreich verarbeitet"
|
|
echo "$output_file"
|
|
return 0
|
|
else
|
|
log_error "Fehler beim Verarbeiten des Workflow-Templates"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Funktion: Workflow importieren
|
|
import_workflow() {
|
|
local workflow_file="$1"
|
|
|
|
log "Importiere Workflow aus ${workflow_file}..."
|
|
|
|
local response
|
|
response=$(curl -sS -X POST "${API_URL}/rest/workflows" \
|
|
-H "Content-Type: application/json" \
|
|
-b "${COOKIE_FILE}" \
|
|
-d @"${workflow_file}" 2>&1)
|
|
|
|
# Extract workflow ID and version ID
|
|
local workflow_id
|
|
local version_id
|
|
workflow_id=$(echo "$response" | grep -oP '"id"\s*:\s*"\K[^"]+' | head -1)
|
|
version_id=$(echo "$response" | grep -oP '"versionId"\s*:\s*"\K[^"]+' | head -1)
|
|
|
|
if [ -z "$workflow_id" ]; then
|
|
log_error "Workflow-Import fehlgeschlagen: ${response}"
|
|
return 1
|
|
fi
|
|
|
|
log "Workflow importiert: ID=${workflow_id}, Version=${version_id}"
|
|
echo "${workflow_id}:${version_id}"
|
|
return 0
|
|
}
|
|
|
|
# Funktion: Workflow aktivieren
|
|
activate_workflow() {
|
|
local workflow_id="$1"
|
|
local version_id="$2"
|
|
|
|
log "Aktiviere Workflow ${workflow_id}..."
|
|
|
|
local response
|
|
response=$(curl -sS -X POST "${API_URL}/rest/workflows/${workflow_id}/activate" \
|
|
-H "Content-Type: application/json" \
|
|
-b "${COOKIE_FILE}" \
|
|
-d "{\"versionId\":\"${version_id}\"}" 2>&1)
|
|
|
|
if echo "$response" | grep -q '"active":true\|"active": true'; then
|
|
log "Workflow ${workflow_id} erfolgreich aktiviert"
|
|
return 0
|
|
else
|
|
log_error "Workflow-Aktivierung fehlgeschlagen: ${response}"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Funktion: Cleanup
|
|
cleanup() {
|
|
rm -f "${COOKIE_FILE}" /tmp/workflow_processed.json 2>/dev/null || true
|
|
}
|
|
|
|
# Hauptfunktion
|
|
main() {
|
|
log "========================================="
|
|
log "n8n Workflow Auto-Reload gestartet"
|
|
log "========================================="
|
|
|
|
# Erstelle Log-Verzeichnis falls nicht vorhanden
|
|
|
|
# Lade Konfiguration
|
|
if ! load_env; then
|
|
log_error "Fehler beim Laden der Konfiguration"
|
|
exit 1
|
|
fi
|
|
|
|
# Prüfe ob Workflow-Template existiert
|
|
if [ ! -f "${WORKFLOW_TEMPLATE}" ]; then
|
|
log_error "Workflow-Template nicht gefunden: ${WORKFLOW_TEMPLATE}"
|
|
exit 1
|
|
fi
|
|
|
|
# Warte auf n8n
|
|
if ! wait_for_n8n; then
|
|
log_error "n8n nicht erreichbar"
|
|
exit 1
|
|
fi
|
|
|
|
# Login
|
|
if ! n8n_login; then
|
|
log_error "Login fehlgeschlagen"
|
|
cleanup
|
|
exit 1
|
|
fi
|
|
|
|
# Suche nach bestehendem Workflow
|
|
local existing_workflow_id
|
|
existing_workflow_id=$(find_workflow "${WORKFLOW_NAME}" || echo "")
|
|
|
|
if [ -n "$existing_workflow_id" ]; then
|
|
log "Bestehender Workflow gefunden, wird gelöscht..."
|
|
delete_workflow "$existing_workflow_id"
|
|
fi
|
|
|
|
# Suche nach Credentials
|
|
log "Suche nach bestehenden Credentials..."
|
|
local pg_cred_id
|
|
local ollama_cred_id
|
|
|
|
pg_cred_id=$(find_credential "PostgreSQL (local)" "postgres" || echo "")
|
|
ollama_cred_id=$(find_credential "Ollama (local)" "ollamaApi" || echo "")
|
|
|
|
if [ -z "$pg_cred_id" ] || [ -z "$ollama_cred_id" ]; then
|
|
log_error "Credentials nicht gefunden (PostgreSQL: ${pg_cred_id}, Ollama: ${ollama_cred_id})"
|
|
cleanup
|
|
exit 1
|
|
fi
|
|
|
|
# Verarbeite Workflow-Template
|
|
local processed_workflow
|
|
processed_workflow=$(process_workflow_template "$pg_cred_id" "$ollama_cred_id")
|
|
|
|
if [ -z "$processed_workflow" ]; then
|
|
log_error "Fehler beim Verarbeiten des Workflow-Templates"
|
|
cleanup
|
|
exit 1
|
|
fi
|
|
|
|
# Importiere Workflow
|
|
local import_result
|
|
import_result=$(import_workflow "$processed_workflow")
|
|
|
|
if [ -z "$import_result" ]; then
|
|
log_error "Workflow-Import fehlgeschlagen"
|
|
cleanup
|
|
exit 1
|
|
fi
|
|
|
|
# Extrahiere IDs
|
|
local new_workflow_id
|
|
local new_version_id
|
|
new_workflow_id=$(echo "$import_result" | cut -d: -f1)
|
|
new_version_id=$(echo "$import_result" | cut -d: -f2)
|
|
|
|
# Aktiviere Workflow
|
|
if ! activate_workflow "$new_workflow_id" "$new_version_id"; then
|
|
log_error "Workflow-Aktivierung fehlgeschlagen"
|
|
cleanup
|
|
exit 1
|
|
fi
|
|
|
|
# Cleanup
|
|
cleanup
|
|
|
|
log "========================================="
|
|
log "Workflow-Reload erfolgreich abgeschlossen"
|
|
log "Workflow-ID: ${new_workflow_id}"
|
|
log "========================================="
|
|
|
|
exit 0
|
|
}
|
|
|
|
# Trap für Cleanup bei Fehler
|
|
trap cleanup EXIT
|
|
|
|
# Hauptfunktion ausführen
|
|
main "$@"
|