- Removed nginx configuration file as it is no longer needed. - Updated README.md to reflect changes in deployment structure. - Enhanced DEPLOYMENT.md with detailed directory structure and user permissions. - Added release.sh and rollback.sh scripts for managing deployments. - Improved N8N.md to clarify file access and document handling. - Adjusted health check and cache flushing procedures in deployment scripts.
282 lines
12 KiB
Markdown
282 lines
12 KiB
Markdown
# EK-DOS-WEB betreiben
|
|
|
|
Zielsystem: Debian/Ubuntu mit nginx und php8.5-fpm. Postgres und Redis laufen
|
|
bereits für n8n und werden mitbenutzt (eigene Datenbank, eigene Redis-Nummer).
|
|
|
|
## Verzeichnisse auf dem Webserver
|
|
|
|
```
|
|
/var/www/ekdos/
|
|
├── current -> releases/<sha> was nginx liest
|
|
├── previous Notiz für rollback.sh
|
|
├── releases/
|
|
│ └── <sha>/
|
|
│ ├── public/ das Vite-Bündel -> nginx root
|
|
│ └── backend/ PHP + vendor/ -> fastcgi
|
|
│ ├── .env -> ../../../shared/.env
|
|
│ └── var/cache/ kompilierter DI-Container, je Veröffentlichung
|
|
├── shared/
|
|
│ └── .env überlebt jedes Ausrollen
|
|
└── deploy/
|
|
├── release.sh
|
|
└── rollback.sh
|
|
```
|
|
|
|
Oberfläche und Backend liegen **in derselben Veröffentlichung** und werden mit
|
|
einem einzigen Symlink-Tausch gemeinsam sichtbar. Eine neue Oberfläche kann
|
|
daher nie gegen ein altes Backend laufen.
|
|
|
|
## 1. Pakete
|
|
|
|
```bash
|
|
sudo apt update
|
|
sudo apt install -y nginx php8.5-fpm php8.5-cli php8.5-pgsql php8.5-redis \
|
|
php8.5-mbstring php8.5-curl php8.5-intl php8.5-xml \
|
|
composer rsync curl
|
|
```
|
|
|
|
`composer` wird auf dem Webserver gebraucht: die PHP-Abhängigkeiten werden dort
|
|
aufgelöst, nicht im Gitea-Runner. Nur dort stimmen PHP-Version und Erweiterungen,
|
|
sodass die Plattformprüfung von Composer etwas Echtes prüft.
|
|
|
|
`php8.5-intl` ist optional, verbessert aber die Erkennung der Kundenrücksprache
|
|
in Ticketberichten (Unicode-Normalisierung). Ohne die Erweiterung greift ein
|
|
Rückfall ohne Normalisierung.
|
|
|
|
## 2. Datenbank und Redis
|
|
|
|
```bash
|
|
sudo -u postgres createuser ekdos --pwprompt
|
|
sudo -u postgres createdb ekdos --owner=ekdos
|
|
```
|
|
|
|
Redis braucht nichts weiter -- EK-DOS legt seine Schlüssel unter dem Präfix
|
|
`ekdos:` ab und nutzt eine eigene Datenbanknummer (`REDIS_DB`).
|
|
|
|
> Redis hält Sitzungen **und** den n8n-Cache. Ist Redis weg, ist niemand mehr
|
|
> angemeldet und jede Ansicht fragt wieder direkt bei n8n an. Die Anwendung
|
|
> läuft weiter, aber langsamer und mit erneuter Anmeldung.
|
|
|
|
## 3. Benutzer und Rechte
|
|
|
|
Zwei Konten: `ekdos` führt PHP aus, `deploy` nimmt die Dateien entgegen. Über
|
|
die gemeinsame Gruppe kommt php-fpm an das, was `deploy` hochlädt.
|
|
|
|
```bash
|
|
sudo useradd --system --home /var/www/ekdos --shell /usr/sbin/nologin ekdos
|
|
sudo useradd --create-home --shell /bin/bash deploy
|
|
sudo usermod -aG ekdos deploy
|
|
|
|
sudo mkdir -p /var/www/ekdos/{releases,shared,deploy}
|
|
sudo chown -R deploy:ekdos /var/www/ekdos
|
|
sudo chmod -R g+rX /var/www/ekdos
|
|
|
|
# setgid: alles neu Angelegte erbt die Gruppe ekdos, sonst käme php-fpm
|
|
# nach dem nächsten Ausrollen nicht mehr an die Dateien.
|
|
sudo find /var/www/ekdos -type d -exec chmod g+s {} +
|
|
|
|
sudo mkdir -p /var/log/php && sudo chown ekdos:ekdos /var/log/php
|
|
```
|
|
|
|
`release.sh` lädt php-fpm neu. Dafür genau dieses eine Kommando ohne Passwort:
|
|
|
|
```bash
|
|
echo 'deploy ALL=(root) NOPASSWD: /bin/systemctl reload php8.5-fpm' \
|
|
| sudo tee /etc/sudoers.d/ekdos-deploy
|
|
sudo chmod 440 /etc/sudoers.d/ekdos-deploy
|
|
sudo visudo -c
|
|
```
|
|
|
|
## 4. Konfiguration anlegen
|
|
|
|
```bash
|
|
sudo -u deploy cp backend/.env.example /var/www/ekdos/shared/.env
|
|
sudo -u deploy editor /var/www/ekdos/shared/.env
|
|
sudo chown deploy:ekdos /var/www/ekdos/shared/.env
|
|
sudo chmod 640 /var/www/ekdos/shared/.env
|
|
```
|
|
|
|
`640` mit Gruppe `ekdos`: php-fpm liest sie, sonst niemand. Sie wird beim
|
|
Ausrollen ausdrücklich nie überschrieben.
|
|
|
|
### n8n-Adressen
|
|
|
|
| Einstellung | Wert | warum |
|
|
| ------------------- | -------------------------------- | ---------------------------------------------------------- |
|
|
| `N8N_PUBLIC_BASE` | `https://n8n.elektro-krueger.eu` | Der Dienst auf Port 5678 ist nur über den CNAME erreichbar |
|
|
| `N8N_INTERNAL_HOST` | `10.0.11.131` | Jeder weitere n8n-Dienst auf einem anderen Port |
|
|
|
|
## 5. php-fpm
|
|
|
|
```bash
|
|
sudo cp deploy/php-fpm/ekdos.pool.conf /etc/php/8.5/fpm/pool.d/ekdos.conf
|
|
sudo php-fpm8.5 -t
|
|
sudo systemctl restart php8.5-fpm
|
|
ls -l /run/php/ekdos.sock # muss www-data:www-data 0660 gehören
|
|
```
|
|
|
|
Der Pool setzt `opcache.validate_timestamps = 0`. Neuer Quelltext wird deshalb
|
|
**erst nach einem `systemctl reload php8.5-fpm`** wirksam -- `release.sh` macht
|
|
das selbst.
|
|
|
|
## 6. nginx
|
|
|
|
Die nginx-Konfiguration wird auf dem Server gepflegt und gehört bewusst nicht
|
|
ins Repository. Was sie leisten muss:
|
|
|
|
| Pfad | Ziel |
|
|
| ------------- | ------------------------------------------------------------------------ |
|
|
| `/api/` | `fastcgi_pass unix:/run/php/ekdos.sock`, `SCRIPT_FILENAME` auf `/var/www/ekdos/current/backend/public/index.php` |
|
|
| `/assets/` | `try_files $uri =404` -- kein SPA-Rückfall, sonst wird eine fehlende JS-Datei zu HTML mit Status 200 |
|
|
| alles andere | `root /var/www/ekdos/current/public`, `try_files $uri $uri/ /index.html` |
|
|
|
|
Drei Punkte, die erfahrungsgemäss Ärger machen:
|
|
|
|
- **`HTTP_X_FORWARDED_FOR` auf `$remote_addr` setzen**, nicht auf
|
|
`$proxy_add_x_forwarded_for`. Letzteres hängt die Adresse nur an einen vom
|
|
Client mitgeschickten Header an -- damit liesse sich die Anmeldebremse mit
|
|
einem selbst gesetzten `X-Forwarded-For` umgehen. Steht ein Proxy davor,
|
|
dessen Netze über `set_real_ip_from` eintragen.
|
|
- **`add_header` wird nicht vererbt**, sobald eine `location` eine eigene
|
|
Kopfzeile setzt. Ein Block mit eigenem `Cache-Control` verliert damit alle
|
|
Sicherheitskopfzeilen des Serverblocks.
|
|
- **`/api/health` von einer HTTPS-Umleitung ausnehmen.** `release.sh` prüft
|
|
lokal über HTTP; eine 301 wäre für `curl -f` ein Erfolg, ohne dass PHP je
|
|
erreicht würde. Alternativ `EKDOS_HEALTH_URL` auf die HTTPS-Adresse setzen.
|
|
|
|
Läuft der Zugriff nur intern über HTTP, muss `SESSION_COOKIE_SECURE=false`
|
|
gesetzt sein -- ein Secure-Cookie wird über reines HTTP nicht gesendet.
|
|
|
|
## 7. Erstes Ausrollen von Hand
|
|
|
|
Die Strecke übernimmt das später, aber einmal von Hand zeigt, ob alles steht:
|
|
|
|
```bash
|
|
# auf dem Arbeitsplatz
|
|
cd frontend && pnpm install && pnpm build && cd ..
|
|
|
|
SHA=erste
|
|
ssh deploy@10.0.11.131 "mkdir -p /var/www/ekdos/releases/$SHA"
|
|
rsync -az --delete frontend/dist/ deploy@10.0.11.131:/var/www/ekdos/releases/$SHA/public/
|
|
rsync -az --delete --exclude vendor/ --exclude .env --exclude var/ \
|
|
backend/ deploy@10.0.11.131:/var/www/ekdos/releases/$SHA/backend/
|
|
rsync -az --chmod=F755 deploy/release.sh deploy/rollback.sh \
|
|
deploy@10.0.11.131:/var/www/ekdos/deploy/
|
|
|
|
ssh deploy@10.0.11.131 /var/www/ekdos/deploy/release.sh $SHA
|
|
```
|
|
|
|
`release.sh` installiert die Composer-Pakete, schreibt das Schema fort, schaltet
|
|
den Symlink um, lädt php-fpm neu, verwirft den n8n-Cache und prüft
|
|
`/api/health`. Bricht etwas vorher ab, bleibt die laufende Fassung unberührt.
|
|
|
|
Danach den ersten Administrator anlegen:
|
|
|
|
```bash
|
|
ssh deploy@10.0.11.131 'php /var/www/ekdos/current/backend/bin/ekdos user:create'
|
|
ssh deploy@10.0.11.131 'php /var/www/ekdos/current/backend/bin/ekdos check'
|
|
```
|
|
|
|
## 8. Gitea-Strecke
|
|
|
|
`.gitea/workflows/build.yml` läuft bei jedem Push auf `main`:
|
|
|
|
| Auftrag | Container | tut |
|
|
| ---------------------- | ------------------ | ------------------------------------------------------------- |
|
|
| `backend-pruefen` | `php:8.5-cli` | `php -l` über alle Dateien, `composer validate` |
|
|
| `bauen-und-ausrollen` | `node:22-bookworm` | `pnpm typecheck` / `build`, dann rsync über SSH und `release.sh` |
|
|
|
|
Bei einem Pull Request wird nur geprüft und gebaut; ausgerollt wird
|
|
ausschliesslich aus `main`.
|
|
|
|
Bauen und Ausrollen liegen bewusst in **einem** Auftrag: das Bündel bleibt im
|
|
Arbeitsverzeichnis und geht von dort per rsync raus. `upload-artifact` /
|
|
`download-artifact` würden nur eine Fassungsabhängigkeit einführen (v3 oder v4,
|
|
je nach Gitea-Version) und damit einen verlässlichen Fehlschlag beim ersten Lauf.
|
|
|
|
**Es gibt keinen automatischen Rückschritt.** `release.sh` tauscht den Symlink
|
|
erst nach Composer und Migration. Scheitert es davor, läuft die alte Fassung
|
|
unverändert weiter -- ein automatischer Rückschritt würde dann auf die
|
|
*vorletzte* schalten und aus einem folgenlosen Fehlschlag einen Ausfall machen.
|
|
Der Lauf berichtet stattdessen, was gerade aktiv ist und ob `/api/health`
|
|
antwortet; zurückgeschaltet wird bei Bedarf von Hand.
|
|
|
|
### Schlüssel einrichten
|
|
|
|
Auf dem Arbeitsplatz:
|
|
|
|
```bash
|
|
ssh-keygen -t ed25519 -f gitea-deploy -C 'gitea-runner -> ekdos' -N ''
|
|
ssh-copy-id -i gitea-deploy.pub deploy@10.0.11.131
|
|
ssh-keyscan -t ed25519 10.0.11.131
|
|
```
|
|
|
|
In Gitea unter *Settings -> Secrets* zwei Einträge:
|
|
|
|
| Secret | Inhalt |
|
|
| -------------------- | ----------------------------------------------------------- |
|
|
| `DEPLOY_SSH_KEY` | der Inhalt von `gitea-deploy` (der **private** Schlüssel) |
|
|
| `DEPLOY_KNOWN_HOSTS` | die Ausgabe von `ssh-keyscan -t ed25519 10.0.11.131` |
|
|
|
|
`DEPLOY_KNOWN_HOSTS` ist optional -- fehlt es, übernimmt der Lauf den
|
|
Fingerabdruck ungeprüft und schreibt einen Hinweis ins Protokoll. Für den
|
|
Dauerbetrieb setzen.
|
|
|
|
Den privaten Schlüssel danach vom Arbeitsplatz löschen; er liegt in Gitea.
|
|
|
|
### Lockfiles
|
|
|
|
Beide Aufträge bestehen auf Sperrdateien, sonst löst jeder Lauf neu auf und
|
|
bekommt womöglich andere Fassungen als geprüft:
|
|
|
|
```bash
|
|
cd backend && composer update # erzeugt composer.lock
|
|
cd frontend && pnpm install # erzeugt pnpm-lock.yaml
|
|
```
|
|
|
|
Beide einchecken.
|
|
|
|
## Zurückschalten
|
|
|
|
```bash
|
|
ssh deploy@10.0.11.131 /var/www/ekdos/deploy/rollback.sh --list # was liegt da
|
|
ssh deploy@10.0.11.131 /var/www/ekdos/deploy/rollback.sh # eine zurück
|
|
ssh deploy@10.0.11.131 /var/www/ekdos/deploy/rollback.sh 2f9c1ab # auf eine bestimmte
|
|
```
|
|
|
|
Es wird nur der Symlink getauscht und php-fpm neu geladen -- Sekunden. Die
|
|
letzten fünf Veröffentlichungen bleiben liegen.
|
|
|
|
> **Migrationen werden nicht zurückgenommen.** Hat eine Fassung das Schema
|
|
> geändert, muss die alte damit umgehen können. Bei den bisherigen Migrationen
|
|
> (nur Tabellen anlegen) ist das der Fall.
|
|
|
|
## Prüfen, wenn etwas klemmt
|
|
|
|
```bash
|
|
php /var/www/ekdos/current/backend/bin/ekdos check # Postgres, Redis, n8n
|
|
curl -fsS http://127.0.0.1/api/health # ohne Anmeldung erreichbar
|
|
tail -f /var/log/php/ekdos-error.log # Ausnahmen aus dem Backend
|
|
tail -f /var/log/nginx/ekdos.error.log
|
|
```
|
|
|
|
| Symptom | meistens |
|
|
| ----------------------------------------------- | ------------------------------------------------------------------ |
|
|
| 502 auf `/api/` | Socket fehlt oder falsche Rechte; `systemctl status php8.5-fpm` |
|
|
| 403 auf `/api/`, im Fehlerlog „Permission denied" | setgid fehlt, php-fpm kommt nicht an die neue Veröffentlichung |
|
|
| Anmeldung wirkt, danach sofort abgemeldet | Redis nicht erreichbar, oder `SESSION_COOKIE_SECURE=true` ohne TLS |
|
|
| Listen leer, „n8n nicht erreichbar" | `N8N_PUBLIC_BASE` falsch, oder der Webhook ist in n8n nicht aktiv |
|
|
| Angezeigte Daten sind veraltet | Rückfall-Cache greift, weil n8n gerade nicht antwortet |
|
|
| 503 „noch nicht eingerichtet" | `N8N_INVOICE_SYNC_SECRET` bzw. `N8N_CUSTOMER_KEY` fehlt in `.env` |
|
|
| Quelltextänderung wirkt nicht | Opcache; `sudo systemctl reload php8.5-fpm` |
|
|
| Ausrollen bricht bei „composer install" ab | Erweiterung fehlt; `composer check-platform-reqs` auf dem Server |
|
|
|
|
## Alle Benutzer aussperren
|
|
|
|
```bash
|
|
redis-cli --scan --pattern 'ekdos:sess:*' | xargs -r redis-cli del
|
|
```
|
|
|
|
Danach muss sich jeder neu anmelden. Einzelne Konten trifft es gezielt über die
|
|
Benutzerverwaltung (Deaktivieren beendet die offenen Sitzungen sofort).
|