Files
SaschaandClaude Opus 5.5 4adf48f336 Anmeldung gilt 7 Tage statt 12 Stunden
JWT-Laufzeit (JWT_EXPIRES_IN) standardmaessig auf 7d gesetzt, damit
Benutzer nicht mehr taeglich neu anmelden muessen. Doku (Betrieb,
Benutzeranleitung, Glossar) und .env.example angepasst.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 22:10:46 +02:00

160 lines
6.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 07 Betrieb
## Deployment
Gebaut wird **lokal**, auf den Server gehen nur `dist/` plus die Backend-Quellen.
```powershell
$env:ICON_CRM_SSH_PASSWORD = '…'
powershell -File deploy/deploy.ps1
powershell -File deploy/deploy.ps1 -SkipBuild # ohne neuen Build
powershell -File deploy/deploy.ps1 -Deps # bei neuen Backend-Paketen
```
Das Skript (`deploy/deploy.ps1`) macht in dieser Reihenfolge:
```mermaid
flowchart LR
B["npm run build"] --> U1["pscp dist/"]
U1 --> U2["pscp server/src + package.json"]
U2 --> R["systemctl restart icon-crm-backend"]
R --> C["curl /api/health"]
```
Schlägt der Build fehl, wird **nichts** hochgeladen – sonst stünde auf dem
Server ein halber Stand.
> **Falle aus der Praxis:** Wer `index.html` von Hand ändert und dann mit
> `-SkipBuild` hochlädt, spielt das alte `dist/` aus. Nach jeder Änderung an
> `src/` oder `index.html` gilt: erst bauen, dann hochladen.
Alte Build-Dateien werden beim Hochladen **nicht** entfernt – `dist/assets/`
wächst mit jeder Auslieferung. Das ist Absicht: ein Browser mit offenem Tab holt
sonst ein Bruchstück, das es nicht mehr gibt. Gelegentlich aufräumen schadet
trotzdem nicht.
## Dienste auf dem Server
| Was | Befehl |
| --- | --- |
| Zustand | `systemctl status icon-crm-backend` |
| Neustart | `systemctl restart icon-crm-backend` |
| Log | `journalctl -u icon-crm-backend -n 100 --no-pager` |
| Gesundheit | `curl -s http://127.0.0.1:5007/api/health` → `{"ok":true,"db":"up"}` |
| Apache | `systemctl reload apache2`, Logs unter `/var/log/apache2/icon_crm_*.log` |
Die systemd-Unit begrenzt den Speicher auf 1 GB (`MemoryMax=1024M`). Das ist
kein Sparzwang, sondern ein Sicherheitsnetz: ein Leck im Backend soll nicht
Apache und MariaDB mitreißen.
## Umgebungsvariablen
`server/.env`, chmod 600, gehört **nicht** ins Repository. Vorlage:
`server/.env.example`.
| Variable | Bedeutung |
| --- | --- |
| `PORT`, `HOST` | `5007` auf `127.0.0.1` – von außen nur über Apache |
| `DB_HOST`, `DB_PORT`, `DB_USER`, `DB_PASSWORD`, `DB_NAME` | MariaDB, Datenbank `icon_CRM` |
| `JWT_SECRET`, `JWT_EXPIRES_IN` | Sitzungen, Vorgabe 7 Tage (`7d`) |
| `ADMIN_USERNAME`, `ADMIN_NAME`, `ADMIN_EMAIL`, `ADMIN_PASSWORD` | erster Administrator, **nur solange `icon_users` leer ist** |
| `CORS_ORIGINS` | erlaubte Herkunft für den lokalen Dev-Server |
| `UPLOAD_DIR` | Ablage der Lebensläufe |
| `OPENAI_API_KEY`, `OPENAI_MODEL` | KI; fehlt der Schlüssel, laufen die übrigen Teile weiter |
| `PERINCO_API_URL`, `PERINCO_API_TOKEN` | ICON liest perinco |
| `PERINCO_INBOUND_TOKEN` | perinco liest ICON |
Ohne `ADMIN_PASSWORD` erzeugt das Backend eines und schreibt es **einmalig** ins
Log. Danach ist das Passwort nur noch über die Nutzerverwaltung bzw. *Mein
Konto* änderbar.
## Erstaufsetzung (einmalig, bereits erledigt)
```bash
apt-get install -y apache2 mariadb-server certbot python3-certbot-apache nodejs
a2enmod proxy proxy_http rewrite headers expires
mariadb -e "CREATE DATABASE icon_CRM CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
mariadb -e "CREATE USER 'iconcrm'@'localhost' IDENTIFIED BY '…';
GRANT ALL PRIVILEGES ON icon_CRM.* TO 'iconcrm'@'localhost';"
cd /var/www/icon_CRM/server && npm install --omit=dev
cp deploy/icon-crm-backend.service /etc/systemd/system/
systemctl daemon-reload && systemctl enable --now icon-crm-backend
cp deploy/icrm.icon-experts.de.conf /etc/apache2/sites-available/
a2ensite icrm.icon-experts.de && systemctl reload apache2
certbot --apache -d icrm.icon-experts.de
```
Das Schema legt das Backend beim Start selbst an (`schema.sql` +
Migrationen in `db.js`). Die Firewall (`ufw`) lässt 22, 80 und 443 durch; das
Zertifikat erneuert certbot selbst.
## Sicherung
Der Bestand steht vollständig in MariaDB; hochgeladene Lebensläufe liegen unter
`/var/www/icon_CRM/uploads`. Beides gehört in die Sicherung:
```bash
mariadb-dump --single-transaction icon_CRM | gzip > icon_CRM-$(date +%F).sql.gz
tar czf uploads-$(date +%F).tar.gz -C /var/www/icon_CRM uploads
```
## Der alte Name crm.icon-experts.de
Das erste ICON CRM lief unter **crm.icon-experts.de** auf `212.227.135.41`
(`/var/www/ICON`, Supabase als Datenbank). Seit dem 14.09.2026 zeigt der Name
auf diesen Server und wird vom Vhost `icrm.icon-experts.de` als `ServerAlias`
mitbedient – dasselbe Zertifikat deckt beide Namen ab. Verschickte Links und
Lesezeichen laufen damit weiter, es gibt keinen zweiten Stand zu pflegen.
Der alte Aufbau ist zurückgebaut: Vhosts (`icon`, `icon-le-ssl`) abgeschaltet,
`/var/www/ICON` gelöscht, das dortige Zertifikat für `crm.icon-experts.de`
entfernt (sonst hätte die Erneuerung ab sofort täglich Fehler gemeldet).
Gesichert ist er als `icon-crm-alt-2026-09-14.zip` – Quellcode, Build, `.env`
und beide Vhost-Dateien, ohne `node_modules`. Die Datei liegt **auf beiden
Servern** unter `/root/backups/`, Prüfsumme
`252f7fa3226c2df7c4020a0234c3095c`.
> Die Supabase-Instanz `icondb.perinco.de` läuft weiter. Sie war die Datenbank
> des alten CRM und ist die Quelle der Migration (`server/src/migrate.js`) –
> abzuschalten wäre eine eigene Entscheidung, nicht Teil dieses Rückbaus.
## Störungssuche
| Symptom | Erster Griff |
| --- | --- |
| Weiße Seite nach dem Rollout | `index.html` darf nicht aus dem Cache kommen – der Vhost setzt dafür `no-cache`; im Zweifel Strg+F5 |
| 502 auf `/api` | Backend aus: `systemctl status icon-crm-backend`, dann `journalctl -u …` |
| `{"db":"down"}` | MariaDB aus oder Zugangsdaten falsch |
| Anmeldung schlägt fehl | `icon_users` prüfen: existiert der Benutzer, ist `is_active = 1`? |
| Zertifikatswarnung | `certbot certificates`; deckt das Zertifikat **alle** Namen ab, auch `www`? |
| Website zeigt alte Ausschreibungen | `curl -s https://icon-experts.de/api/public/tenders | head` – kommt das durch den Proxy? |
| perinco-Kontakte fehlen | `GET /api/perinco/status`; Token beidseitig gesetzt? |
| Diktat lädt nicht | siehe [04 Diktat](04-diktat.md) – das ist eine Sache des Browsers, nicht des Servers |
### Wenn Daten zu fehlen scheinen
Erst messen, dann handeln. Die drei Griffe, die in der Praxis geholfen haben:
```bash
mysql icon_CRM -e "SELECT COUNT(*) FROM icon_contacts" # steht es in der Datenbank?
curl -s .../api/contacts -H "Authorization: Bearer …" | wc -c # liefert die API es aus?
tail -n 50 /var/log/apache2/icon_crm_access.log # kam es beim Browser an?
```
Stimmen alle drei, liegt es an der Ansicht – meist an einem aktiven Filter.
### DNS-Umstellungen
Nach einem Wechsel des A-Records antworten fremde Resolver unterschiedlich
lange mit der alten Adresse. Zum Prüfen die **autoritativen** Nameserver und
mindestens einen öffentlichen Resolver befragen, nicht nur den eigenen:
```bash
dig +short icon-experts.de @1.1.1.1
dig +short icon-experts.de @8.8.8.8
```
Ein lokaler Resolver (PiHole, Router) kann den alten Wert deutlich länger
halten – das sieht dann aus wie ein Serverfehler und ist keiner.