Files
Agent Zero b3dea611b4 docs: update 6 stale files + delete 17 obsolete audit/plan files
Updated:
- README.md: 23 → 25 Plugins (self_improvement, knowledge)
- PROGRESS.md: Phase A-K done (261/261), Alembic 0136, 2174 Tests
- PLATFORM_ROADMAP.md: Phase I, J, K marked as DONE
- docs/api-documentation.md: 303 → 554+ endpoints
- docs/test-strategy.md: ~500 → 2174 Tests, create_all description updated
- docs/INSTALL.md: Alembic-Head 0090 → 0136

Deleted (17 obsolete files):
- Root: ARCHITECTURE_PLAN.md, COMPLETE_SYSTEM_AUDIT.md, COMPLETE_VERNETZUNGS_AUDIT.md, ENTERPRISE_READINESS_PLAN.md, ROADMAP_VERIFICATION.md, SYSTEM_AUDIT.md, TEST_PLAN.md
- docs/: audit-consolidated-errors.md, audit-fix-plan.md, audit-tracker.md, full-audit-errors.md, architecture-cleanup-plan.md, schema-authority.md, api-audit.md, phase-gate-review-g.md, phase-gate-review-h.md, arch-f-review.md
2026-08-21 10:40:22 +02:00

17 KiB

LeoCRM — Vollständige Installationsanleitung

Stand: 2026-08-21 Commit: 72e3756 Alembic-Head: 0136

Diese Anleitung beschreibt die komplette Installation von LeoCRM von Grund auf. Keine manuellen Nacharbeiten erforderlich. Alle Schritte sind reproduzierbar.


Voraussetzungen

  • Coolify v4 (oder Docker + Docker Compose)
  • PostgreSQL 16 mit pgvector-Extension
  • Redis 7
  • Git-Zugang zum Forgejo-Repo
  • Domain mit DNS-Eintrag

1. Repository klonen

git clone https://forgejo.media-on.de/Leopoldadmin/leocrm.git
cd leocrm
git checkout main

2. Docker-Image bauen

docker build -t leocrm:latest .

Dockerfile-Hinweise:

  • Verwendet npm ci --legacy-peer-deps (vite 8 peer dependency conflict)
  • Frontend wird in Multi-Stage-Build gebaut
  • Runtime-Image enthält: Python 3.12, Node.js, prestart.sh, worker.sh, healthcheck.sh

3. Datenbankrollen

LeoCRM verwendet 5 separate Datenbankrollen mit unterschiedlichen Berechtigungen. Diese Rollen werden automatisch durch Migration 0085 erstellt.

Rollen-Übersicht

Rolle Superuser BYPASSRLS Login Verwendung
crm_user Ja Ja Ja PostgreSQL-Container-Admin (POSTGRES_USER)
crm_migration Nein Ja Ja Alembic-Migrationen, Plugin-Migrationen (DDL)
crm_auth Nein Nein Ja Login, Authentifizierung, Password-Reset
crm_api Nein Nein Ja Normale API-Abfragen (SELECT, INSERT, UPDATE, DELETE)
crm_worker Nein Nein Ja ARQ-Worker, Outbox-Processing, Cron-Jobs

Bootstrap-Reihenfolge

1. PostgreSQL-Container startet
   → crm_user wird erstellt (POSTGRES_USER, SUPERUSER)

2. prestart.sh läuft im API-Container
   → alembic upgrade head (als crm_user über MIGRATION_DATABASE_URL)
   → Migration 0085 erstellt crm_migration, crm_auth, crm_api, crm_worker
   → Migration 0085 vergibt Grants und erstellt RLS-Policies
   → prestart.sh setzt Passwörter für alle Rollen

3. API startet (uvicorn)
   → Verwendet DATABASE_URL (crm_api) für normale Abfragen
   → Verwendet AUTH_DATABASE_URL (crm_auth) für Login
   → Plugin-Migrationen über get_migration_engine() (crm_migration)

4. Worker startet
   → Verwendet WORKER_DATABASE_URL (crm_worker) für Jobs
   → Plugin-Migrationen über get_migration_engine() (crm_migration)
   → Event-Handler für aktive Plugins registriert

WICHTIG: MIGRATION_DATABASE_URL

Der erste Alembic-Lauf auf einer leeren Datenbank MUSS als crm_user ausgeführt werden, weil crm_migration erst durch Migration 0085 erstellt wird.

MIGRATION_DATABASE_URL=postgresql+asyncpg://crm_user:<PASSWORT>@postgres:5432/crm_db

Nach Migration 0085 kann MIGRATION_DATABASE_URL auf crm_migration umgestellt werden, aber für den Bootstrap-Prozess ist crm_user erforderlich.


4. Environment-Variablen

API-Container

Variable Wert Beschreibung
DATABASE_URL postgresql+asyncpg://crm_api:PW@postgres:5432/crm_db API-Abfragen (crm_api)
AUTH_DATABASE_URL postgresql+asyncpg://crm_auth:PW@postgres:5432/crm_db Login/Auth (crm_auth)
WORKER_DATABASE_URL postgresql+asyncpg://crm_worker:PW@postgres:5432/crm_db Worker-Jobs (crm_worker)
MIGRATION_DATABASE_URL postgresql+asyncpg://crm_user:PW@postgres:5432/crm_db Migrationen (crm_user für Bootstrap)
REDIS_URL redis://default:PW@redis:6379/0 Redis-Verbindung
SECRET_KEY <mindestens 32 Zeichen> Session-Verschlüsselung
ENVIRONMENT production Umgebung
STORAGE_PATH /data/storage Datei-Storage
FRONTEND_URL https://crm.example.com Frontend-URL
CORS_ORIGINS https://crm.example.com CORS-Konfiguration
SESSION_COOKIE_SECURE true HTTPS-Cookies
LOG_LEVEL INFO Logging-Level

Worker-Container

Variable Wert Beschreibung
DATABASE_URL postgresql+asyncpg://crm_worker:PW@postgres:5432/crm_db Worker-DB (crm_worker)
WORKER_DATABASE_URL postgresql+asyncpg://crm_worker:PW@postgres:5432/crm_db Worker-DB (crm_worker)
MIGRATION_DATABASE_URL postgresql+asyncpg://crm_user:PW@postgres:5432/crm_db Plugin-Migrationen (crm_user)
REDIS_URL redis://default:PW@redis:6379/0 Redis-Verbindung
SECRET_KEY <mindestens 32 Zeichen> Session-Verschlüsselung
ENVIRONMENT production Umgebung
STORAGE_PATH /data/storage Datei-Storage

DB-Container

Variable Wert
POSTGRES_USER crm_user
POSTGRES_PASSWORD
POSTGRES_DB crm_db

WICHTIG: Alle DB-Passwörter sind identisch

Migration 0085 erstellt die Rollen crm_api, crm_auth, crm_worker, crm_migration mit demselben Passwort das in MIGRATION_DATABASE_URL für crm_user konfiguriert ist. prestart.sh setzt anschließend die Passwörter für alle Rollen aus der MIGRATION_DATABASE_URL.

Daher müssen alle DATABASE_URL, AUTH_DATABASE_URL, WORKER_DATABASE_URL dasselbe Passwort verwenden wie MIGRATION_DATABASE_URL.


5. Docker Compose

Vollständige docker-compose.yml

version: '3.8'

services:
  postgres:
    image: pgvector/pgvector:pg16
    restart: unless-stopped
    environment:
      POSTGRES_USER: crm_user
      POSTGRES_PASSWORD: ${DB_PASSWORD}
      POSTGRES_DB: crm_db
    volumes:
      - db-data:/var/lib/postgresql/data
    healthcheck:
      test: ['CMD-SHELL', 'pg_isready -U crm_user -d crm_db']
      interval: 5s
      timeout: 10s
      retries: 20

  redis:
    image: redis:7-alpine
    restart: unless-stopped
    command: redis-server --requirepass ${REDIS_PASSWORD}
    volumes:
      - redis-data:/data

  api:
    image: leocrm:latest
    restart: unless-stopped
    expose:
      - '8000'
    environment:
      DATABASE_URL: postgresql+asyncpg://crm_api:${DB_PASSWORD}@postgres:5432/crm_db
      AUTH_DATABASE_URL: postgresql+asyncpg://crm_auth:${DB_PASSWORD}@postgres:5432/crm_db
      WORKER_DATABASE_URL: postgresql+asyncpg://crm_worker:${DB_PASSWORD}@postgres:5432/crm_db
      MIGRATION_DATABASE_URL: postgresql+asyncpg://crm_user:${DB_PASSWORD}@postgres:5432/crm_db
      REDIS_URL: redis://default:${REDIS_PASSWORD}@redis:6379/0
      SECRET_KEY: ${SECRET_KEY}
      ENVIRONMENT: production
      STORAGE_PATH: /data/storage
      FRONTEND_URL: https://crm.example.com
      CORS_ORIGINS: https://crm.example.com
      SESSION_COOKIE_SECURE: 'true'
      LOG_LEVEL: INFO
    volumes:
      - api-storage:/data/storage
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_started
    healthcheck:
      test: ['CMD', 'curl', '-f', 'http://localhost:8000/api/v1/health']
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 180s

  worker:
    image: leocrm:latest
    restart: unless-stopped
    entrypoint: /app/worker.sh
    environment:
      DATABASE_URL: postgresql+asyncpg://crm_worker:${DB_PASSWORD}@postgres:5432/crm_db
      WORKER_DATABASE_URL: postgresql+asyncpg://crm_worker:${DB_PASSWORD}@postgres:5432/crm_db
      MIGRATION_DATABASE_URL: postgresql+asyncpg://crm_user:${DB_PASSWORD}@postgres:5432/crm_db
      REDIS_URL: redis://default:${REDIS_PASSWORD}@redis:6379/0
      SECRET_KEY: ${SECRET_KEY}
      ENVIRONMENT: production
      STORAGE_PATH: /data/storage
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_started

volumes:
  db-data:
  redis-data:
  api-storage:

.env Datei

DB_PASSWORD=YourSecurePassword2026
REDIS_PASSWORD=YourRedisPassword2026
SECRET_KEY=your-secret-key-with-at-least-32-characters!!

6. Coolify-Setup

6.1 Neue Anwendung erstellen (automatisiert)

LeoCRM wird als einzelner docker-compose Stack in Coolify deployt. Alle 4 Container (postgres, redis, crm_app, crm_worker) laufen in einer Coolify Application mit build_pack=dockercompose.

# Umgebungsvariablen setzen
export COOLIFY_API_TOKEN="dein-token"
export APP_DOMAIN="https://crm.example.com"
export APP_NAME="leocrm"
export DB_PASSWORD="YourSecurePassword2026"
export REDIS_PASSWORD="YourRedisPassword2026"
export SECRET_KEY="your-secret-key-with-at-least-32-characters!!"
export COOLIFY_PROJECT_UUID="<project-uuid>"
export COOLIFY_SERVER_UUID="<server-uuid>"
export COOLIFY_PRIVATE_KEY_UUID="<private-key-uuid>"

# Initial deployment (2-Phase: ohne Domain, dann mit Domain + Redeploy)
python scripts/deploy.py --initial

Das Script macht automatisch:

  1. Coolify Application via private-deploy-key erstellen
  2. Build Pack auf dockercompose setzen (liest docker-compose.yaml aus Git)
  3. Environment-Variablen setzen (Secrets, Domain, Admin-Credentials)
  4. Erster Deploy (ohne Domain — Coolify muss docker-compose.yaml lesen)
  5. docker_compose_domains setzen + Redeploy (mit Traefik-Labels)
  6. Verifikation (HTTP, Login, Alembic, RLS)

6.2 Redeploy (bestehende Anwendung)

export COOLIFY_API_TOKEN="dein-token"
export APP_DOMAIN="https://crm.example.com"
python scripts/deploy.py

6.3 Wichtige Hinweise

  • docker-compose.yaml (nicht .yml) — Coolify sucht nach .yaml
  • Service-Namen mit Unterstrichen: crm_app, crm_worker (nicht mit Bindestrich)
  • Domain ohne :443 in docker_compose_domains:443 führt zu leeren Traefik-Labels
  • Kein connect_to_docker_network — Coolify kümmert sich selbst um das Netzwerk im Stack
  • DB-Image: MUSS pgvector/pgvector:pg16 sein (nicht postgres:16-alpine)

6.4 Environment-Variablen in Coolify

Das --initial Script setzt automatisch alle benötigten ENV-Variablen in Coolify. Die docker-compose.yaml verwendet ${VARIABLE} Syntax — Coolify substituiert aus diesen ENV-Variablen.

Siehe auch: DEPLOY.md und COOLIFY_SETUP.md für Details.


7. Startup-Ablauf (prestart.sh)

prestart.sh wird beim API-Container-Start ausgeführt:

1. Warten auf PostgreSQL (pg_isready)
2. alembic upgrade head (als crm_user über MIGRATION_DATABASE_URL)
   → Migrationen 0001-0090 werden ausgeführt
   → Migration 0085 erstellt DB-Rollen, RLS-Policies, Grants
3. Passwörter für alle Rollen setzen
   → Extrahiert Passwort aus MIGRATION_DATABASE_URL
   → SET PASSWORD für crm_api, crm_auth, crm_worker, crm_migration
4. Uvicorn starten

8. Admin-User anlegen

Nach erfolgreichem Start:

docker exec api-container python3 scripts/seed_admin.py

Erstellt:

  • Tenant: "Default Org" (slug: default)
  • Admin-Role mit permissions={":": True}
  • User: admin@media-on.de / Admin123!
  • UserTenant-Link mit Admin-Role

Passwort ändern: ADMIN_PASSWORD Environment-Variable setzen vor Ausführung.


9. Verifikation

9.1 Health-Check

curl https://crm.example.com/api/v1/health
# Erwartet: {"status":"healthy",...}

9.2 Login-Test

curl -X POST https://crm.example.com/api/v1/auth/login \
  -H "Content-Type: application/json" \
  -H "Origin: https://crm.example.com" \
  -d '{"email":"admin@media-on.de","password":"Admin123!"}'
# Erwartet: 200 OK mit user_id, csrf_token, tenant_id

9.3 Alembic-Head prüfen

docker exec api-container python3 -m alembic current
# Erwartet: 0090 (head)

9.4 RLS prüfen

-- Als crm_api ohne Tenant-Kontext: 0 rows
SET ROLE crm_api;
SELECT count(*) FROM contacts;  -- Erwartet: 0
RESET ROLE;

-- Als crm_api mit Tenant-Kontext: Tenant-Daten
SET ROLE crm_api;
SET app.current_tenant_id = '<tenant-uuid>';
SELECT count(*) FROM contacts;  -- Erwartet: > 0
RESET ROLE;

9.5 DDL durch crm_api blockiert

SET ROLE crm_api;
CREATE TABLE test_block (id int);  -- Erwartet: permission denied
RESET ROLE;

10. SMTP-Konfiguration (optional)

Für Passwort-Reset-Emails:

Variable Wert
SMTP_HOST mail.example.com
SMTP_PORT 465
SMTP_USER noreply@example.com
SMTP_PASSWORD
SMTP_FROM_EMAIL noreply@example.com
SMTP_USE_TLS true

Wichtig: Port 465 verwendet implicit TLS (nicht STARTTLS).


11. Backup und Restore

11.1 Backup erstellen

pg_dump -U crm_user -d crm_db -F c -f crm_backup.dump

11.2 Restore

# 1. Leere Datenbank erstellen
createdb -U crm_user crm_restore

# 2. Restore (ohne ACLs, ohne Owner)
pg_restore -U crm_user -d crm_restore --no-owner --no-acl < crm_backup.dump

# 3. Grants neu anwenden (pg_restore --no-acl überspringt Grants)
# Führe Migration 0085 Grants aus oder verwende das Grant-Skript

# 4. Alembic auf neuesten Stand bringen
alembic upgrade head

# 5. App gegen die wiederhergestellte DB starten und verifizieren

11.3 WICHTIG: Grants nach Restore

pg_restore --no-acl überspringt GRANT-Statements. Nach einem Restore müssen die Grants aus Migration 0085 neu angewendet werden. Alternativ: pg_restore ohne --no-acl verwenden (erfordert korrekte Rollen).


12. Häufige Probleme

Problem: "extension vector is not available"

Ursache: DB-Image ist postgres:16-alpine statt pgvector/pgvector:pg16 Lösung: DB-Image in docker-compose.yml ändern

Problem: "permission denied for schema public"

Ursache: crm_api versucht DDL auszuführen Lösung: Plugin-Migrationen müssen über get_migration_engine() laufen (bereits implementiert)

Problem: "MIGRATION_DATABASE_URL is not set"

Ursache: MIGRATION_DATABASE_URL fehlt in Environment-Variablen Lösung: MIGRATION_DATABASE_URL setzen (auf crm_user für Bootstrap)

Problem: Worker crasht beim Start

Ursache: PluginModel.is_active existiert nicht (alte Migration) Lösung: Sicherstellen dass alle Migrationen bis 0090 ausgeführt wurden

Problem: Login gibt 401 zurück

Ursache: crm_auth hat keine SELECT-Rechte auf users/tenants Lösung: Migration 0085 Grants prüfen, ggf. neu anwenden

Problem: RLS zeigt alle Daten ohne Tenant-Kontext

Ursache: FORCE RLS nicht aktiviert oder Rolle ist SUPERUSER Lösung: ALTER TABLE ... FORCE ROW LEVEL SECURITY und Rolle NOSUPERUSER setzen


13. Architektur-Übersicht

┌──────────────┐     ┌──────────────┐     ┌──────────────┐
│   API (crm_api)   │     │  Auth (crm_auth) │     │ Worker (crm_worker)│
│  SELECT/INSERT/   │     │  Login/Logout    │     │  ARQ-Jobs/Outbox   │
│  UPDATE/DELETE    │     │  Tenant-Auflösung │     │  Cron-Jobs         │
│  Audit-Log        │     │  Password-Reset  │     │  Event-Handler     │
└────────┬─────────┘     └────────┬─────────┘     └────────┬───────────┘
         │                        │                        │
         │   ┌──────────────┐     │                        │
         └───┤ PostgreSQL  ├──────┘────────────────────────┘
             │  (RLS aktiv) │
             │  108 Tabellen│
             │  Fail-closed │
             └──────┬───────┘
                    │
             ┌──────┴───────┐
             │  Migration    │
             │  (crm_migration)│
             │  BYPASSRLS    │
             │  DDL-Operationen│
             └──────────────┘

14. Datei-Struktur

leocrm/
├── app/
│   ├── core/
│   │   ├── db/__init__.py      # DB-Engines (api, auth, worker, migration)
│   │   ├── worker.py           # ARQ-Worker-Konfiguration
│   │   ├── outbox.py           # Transactional Outbox
│   │   ├── auth.py             # Authentifizierung
│   │   └── middleware.py       # CSRF, CORS, Tenant-Context
│   ├── plugins/                # Built-in Plugins
│   ├── routes/                 # API-Routes
│   ├── services/               # Business-Logic
│   └── models/                  # SQLAlchemy-Models
├── alembic/versions/            # Migrationen 0001-0090
├── frontend/                    # React 18 + TypeScript + Vite
├── scripts/
│   ├── seed_admin.py           # Admin-User erstellen
│   └── test_migrations.sh      # Migrations-Test
├── prestart.sh                 # Container-Entrypoint (API)
├── worker.sh                   # Container-Entrypoint (Worker)
├── healthcheck.sh              # Health-Check-Script
├── docker-compose.yml          # Compose-Referenz
├── .env.docker.example         # ENV-Template
├── Dockerfile                  # Multi-Stage-Build
└── requirements.txt            # Python-Abhängigkeiten

Diese Anleitung wird mit jedem Release aktualisiert. Stand: Commit be20a85, Alembic-Head 0090.