525 lines
17 KiB
Markdown
525 lines
17 KiB
Markdown
# LeoCRM — Vollständige Installationsanleitung
|
|
|
|
**Stand:** 2026-08-01
|
|
**Commit:** be20a85
|
|
**Alembic-Head:** 0090
|
|
|
|
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.*
|