Dokumentation: docs/07-betrieb.md

This commit is contained in:
2026-09-14 15:34:16 +02:00
parent 19cc1ac9f6
commit e3a5140d83
+138
View File
@@ -0,0 +1,138 @@
# 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 12 h |
| `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
```
## 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.