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

525 lines
17 KiB
Markdown

# 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
```bash
git clone https://forgejo.media-on.de/Leopoldadmin/leocrm.git
cd leocrm
git checkout main
```
---
## 2. Docker-Image bauen
```bash
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 | <PASSWORT> |
| 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
```yaml
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
```env
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`.
```bash
# 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)
```bash
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:
```bash
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
```bash
curl https://crm.example.com/api/v1/health
# Erwartet: {"status":"healthy",...}
```
### 9.2 Login-Test
```bash
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
```bash
docker exec api-container python3 -m alembic current
# Erwartet: 0090 (head)
```
### 9.4 RLS prüfen
```sql
-- 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
```sql
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-Passwort> |
| 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
```bash
pg_dump -U crm_user -d crm_db -F c -f crm_backup.dump
```
### 11.2 Restore
```bash
# 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.*