Refactor deployment process and update documentation
EK-DOS-WEB bauen und ausrollen / backend-pruefen (push) Failing after 2s
EK-DOS-WEB bauen und ausrollen / bauen-und-ausrollen (push) Skipped

- 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.
This commit is contained in:
Kyle Müller
2026-09-06 22:41:04 +02:00
parent 7d82e807fc
commit 940b2d4767
7 changed files with 589 additions and 259 deletions
-97
View File
@@ -1,97 +0,0 @@
# EK-DOS-WEB
#
# nginx macht beides: es liefert das statisch gebaute React-Bündel aus und
# reicht ausschliesslich /api/ an php-fpm weiter. Auf dem Server läuft kein
# Node und kein Anwendungsserver -- nur nginx und php8.5-fpm.
#
# Ablage: /etc/nginx/sites-available/ekdos.conf -> sites-enabled/
# Prüfen: nginx -t && systemctl reload nginx
upstream ekdos_php {
# Unix-Socket statt TCP: kein Netzwerkstack, keine offene Portnummer.
server unix:/run/php/ekdos.sock;
}
server {
listen 80;
listen [::]:80;
server_name schulung.elektro-krueger.local;
# Hinter einem TLS-Terminator (Cloudflare, vorgelagertes nginx) diesen Block
# entfernen und stattdessen den 443-Block unten verwenden.
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name schulung.elektro-krueger.local;
ssl_certificate /etc/ssl/ekdos/fullchain.pem;
ssl_certificate_key /etc/ssl/ekdos/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
# Das gebaute Bündel aus dem Gitea-Lauf.
root /var/www/ekdos/current;
index index.html;
# Die Anwendung ist intern; sie gehört in keinen Suchindex.
add_header X-Robots-Tag "noindex, nofollow" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "same-origin" always;
# Rechnungen und Serviceberichte sind mehrere Megabyte gross.
client_max_body_size 32m;
# ── Statische Oberfläche ──────────────────────────────────────────────
# Vite vergibt Inhaltshashes, deshalb dürfen die Bündel dauerhaft im Cache
# liegen. Nur index.html darf das nicht, sonst sieht niemand ein Update.
location /assets/ {
expires 1y;
add_header Cache-Control "public, immutable";
try_files $uri =404;
}
location = /index.html {
add_header Cache-Control "no-store";
}
# Einseitenanwendung: jede unbekannte Adresse bekommt index.html.
location / {
try_files $uri $uri/ /index.html;
}
# ── Backend ───────────────────────────────────────────────────────────
location /api/ {
include fastcgi_params;
fastcgi_pass ekdos_php;
# Einziger Einstiegspunkt. Es gibt keine weiteren .php-Dateien im Web.
fastcgi_param SCRIPT_FILENAME /var/www/ekdos/backend/public/index.php;
fastcgi_param SCRIPT_NAME /index.php;
fastcgi_param DOCUMENT_ROOT /var/www/ekdos/backend/public;
# Der Rechnungsabgleich und grosse PDFs brauchen Luft.
fastcgi_read_timeout 60s;
fastcgi_buffering off;
# Damit die Anmeldebremse die echte Adresse sieht.
fastcgi_param HTTP_X_FORWARDED_FOR $proxy_add_x_forwarded_for;
fastcgi_param HTTPS on;
}
# PHP wird nirgendwo sonst ausgeführt.
location ~ \.php$ {
return 404;
}
# Punktdateien bleiben unerreichbar (.env, .git und Ähnliches).
location ~ /\. {
deny all;
}
access_log /var/log/nginx/ekdos.access.log;
error_log /var/log/nginx/ekdos.error.log warn;
}
+126
View File
@@ -0,0 +1,126 @@
#!/usr/bin/env bash
#
# Eine hochgeladene Veröffentlichung aktivieren.
#
# Läuft auf dem Webserver als Benutzer `deploy`. Die Gitea-Strecke ruft das Skript
# nach dem Hochladen auf; von Hand geht es genauso:
#
# /var/www/ekdos/deploy/release.sh 2f9c1ab
#
# Erwartete Verzeichnisse:
#
# $ROOT/releases/<name>/public das Vite-Bündel
# $ROOT/releases/<name>/backend das PHP-Backend (noch ohne vendor/)
# $ROOT/shared/.env die Konfiguration, überlebt jedes Ausrollen
# $ROOT/current -> releases/<name> der Symlink, den nginx liest
#
# Beide Hälften werden mit einem einzigen Symlink-Tausch gemeinsam sichtbar.
# Schlägt vorher etwas fehl, bricht das Skript ab und die laufende Fassung
# bleibt unberührt.
set -euo pipefail
ROOT="${EKDOS_ROOT:-/var/www/ekdos}"
KEEP="${EKDOS_KEEP:-5}"
FPM_SERVICE="${EKDOS_FPM:-php8.5-fpm}"
HEALTH_URL="${EKDOS_HEALTH_URL:-http://127.0.0.1/api/health}"
RELEASE="${1:-}"
[ -n "$RELEASE" ] || { echo "Aufruf: release.sh <veroeffentlichung>" >&2; exit 2; }
RELEASE_DIR="$ROOT/releases/$RELEASE"
BACKEND="$RELEASE_DIR/backend"
step() { printf '\n\033[1m==> %s\033[0m\n' "$1"; }
fail() { printf '\033[31mFEHLER: %s\033[0m\n' "$1" >&2; exit 1; }
# ── 1. Prüfen, was da hochgeladen wurde ──────────────────────────────────────
step "Veröffentlichung $RELEASE prüfen"
[ -d "$RELEASE_DIR" ] || fail "$RELEASE_DIR gibt es nicht."
[ -f "$RELEASE_DIR/public/index.html" ] || fail "Im Bündel fehlt public/index.html."
[ -f "$BACKEND/public/index.php" ] || fail "Im Backend fehlt public/index.php."
[ -f "$ROOT/shared/.env" ] || fail "$ROOT/shared/.env fehlt. Einmalig aus backend/.env.example anlegen."
# ── 2. Gemeinsame Dateien einhängen ──────────────────────────────────────────
# Die .env gehört dem Server, nicht dem Repository, und wird nie mit ausgerollt.
step "Konfiguration verknüpfen"
ln -sfn "$ROOT/shared/.env" "$BACKEND/.env"
# var/cache bleibt bewusst je Veröffentlichung eigen: dort liegt der von PHP-DI
# kompilierte Container. Geteilt über mehrere Veröffentlichungen hinweg würde er
# auf Klassen der alten Fassung zeigen.
mkdir -p "$BACKEND/var/cache"
chmod 2775 "$BACKEND/var" "$BACKEND/var/cache"
# ── 3. Abhängigkeiten auf der Zielmaschine auflösen ──────────────────────────
# Bewusst hier und nicht im Gitea-Runner: nur hier stimmen PHP-Version und
# Erweiterungen (pdo_pgsql, redis), sodass die Plattformprüfung von Composer
# etwas Echtes prüft statt übersprungen zu werden.
step "Composer-Abhängigkeiten installieren"
( cd "$BACKEND" && composer install --no-dev --no-interaction --no-progress --prefer-dist --optimize-autoloader --classmap-authoritative )
# ── 4. Schema fortschreiben, bevor die neue Fassung sichtbar wird ────────────
step "Datenbankschema fortschreiben"
php "$BACKEND/bin/ekdos" migrate
# ── 5. Umschalten ────────────────────────────────────────────────────────────
# ln -sfn legt bei einem vorhandenen Symlink sonst *im* Ziel an, deshalb erst
# einen neuen Namen anlegen und dann mit mv -T atomar darüberschieben.
step "Auf $RELEASE umschalten"
PREVIOUS="$(readlink "$ROOT/current" 2>/dev/null || echo '')"
ln -sfn "releases/$RELEASE" "$ROOT/current.new"
mv -Tf "$ROOT/current.new" "$ROOT/current"
[ -n "$PREVIOUS" ] && printf '%s\n' "$PREVIOUS" > "$ROOT/previous" || true
# ── 6. php-fpm neu laden ─────────────────────────────────────────────────────
# Der Pool fährt mit opcache.validate_timestamps=0. Ohne Neuladen liefe weiter
# der alte Quelltext aus dem Opcache.
step "$FPM_SERVICE neu laden"
sudo systemctl reload "$FPM_SERVICE"
# ── 7. Zwischenspeicher verwerfen ────────────────────────────────────────────
# Zwischengespeicherte n8n-Antworten passen womöglich nicht mehr zur neuen
# Feldabbildung. Sitzungen bleiben unberührt -- niemand wird abgemeldet.
step "n8n-Cache verwerfen"
php "$BACKEND/bin/ekdos" cache:flush
# ── 8. Nachsehen, ob es läuft ────────────────────────────────────────────────
step "Gesundheitsprüfung"
for attempt in 1 2 3 4 5; do
if curl -fsS --max-time 5 "$HEALTH_URL" > /dev/null; then
echo " $HEALTH_URL antwortet."
break
fi
[ "$attempt" -eq 5 ] && fail "$HEALTH_URL antwortet nicht. Zurück mit: $ROOT/deploy/rollback.sh"
echo " Versuch $attempt fehlgeschlagen, neuer Versuch …"
sleep 2
done
# ── 9. Aufräumen ─────────────────────────────────────────────────────────────
# Die aktuelle und die vorige Veröffentlichung bleiben in jedem Fall liegen.
step "Alte Veröffentlichungen aufräumen (die letzten $KEEP bleiben)"
KEEP_LIST="$(printf '%s\n%s\n' "$RELEASE" "$(basename "${PREVIOUS:-none}")")"
# find statt ls: kein unexpandiertes */-Glob, das unter `pipefail` den ganzen
# Lauf nach einem bereits erfolgreichen Ausrollen scheitern liesse.
OBSOLETE="$(cd "$ROOT/releases" && find . -mindepth 1 -maxdepth 1 -type d -printf '%T@ %f\n' \
| sort -rn | cut -d' ' -f2- | tail -n "+$((KEEP + 1))" || true)"
if [ -n "$OBSOLETE" ]; then
printf '%s\n' "$OBSOLETE" | while read -r old; do
[ -n "$old" ] || continue
# Die aktive und die vorige Veröffentlichung bleiben in jedem Fall liegen.
if printf '%s\n' "$KEEP_LIST" | grep -qxF "$old"; then
continue
fi
echo " entferne $old"
rm -rf -- "$ROOT/releases/$old"
done
else
echo " nichts aufzuräumen"
fi
step "$RELEASE ist aktiv."
+60
View File
@@ -0,0 +1,60 @@
#!/usr/bin/env bash
#
# Auf die vorige Veröffentlichung zurückschalten.
#
# /var/www/ekdos/deploy/rollback.sh # eine zurück
# /var/www/ekdos/deploy/rollback.sh 2f9c1ab # auf eine bestimmte
# /var/www/ekdos/deploy/rollback.sh --list # zeigt, was vorrätig ist
#
# Es wird nur der Symlink getauscht und php-fpm neu geladen. Die alten
# Veröffentlichungen liegen vollständig da, ein Rückschritt dauert Sekunden.
#
# ACHTUNG: Datenbankmigrationen werden NICHT zurückgenommen. Hat die neue
# Fassung das Schema geändert, muss die alte damit umgehen können. Bei den
# bisherigen Migrationen (nur Tabellen anlegen) ist das der Fall.
set -euo pipefail
ROOT="${EKDOS_ROOT:-/var/www/ekdos}"
FPM_SERVICE="${EKDOS_FPM:-php8.5-fpm}"
HEALTH_URL="${EKDOS_HEALTH_URL:-http://127.0.0.1/api/health}"
step() { printf '\n\033[1m==> %s\033[0m\n' "$1"; }
fail() { printf '\033[31mFEHLER: %s\033[0m\n' "$1" >&2; exit 1; }
CURRENT="$(basename "$(readlink "$ROOT/current" 2>/dev/null || echo none)")"
if [ "${1:-}" = "--list" ]; then
echo "Vorrätige Veröffentlichungen (neueste zuerst):"
( cd "$ROOT/releases" && ls -1td -- */ | sed 's#/$##' ) | while read -r name; do
[ "$name" = "$CURRENT" ] && echo " * $name (aktiv)" || echo " $name"
done
exit 0
fi
TARGET="${1:-}"
if [ -z "$TARGET" ]; then
# Ohne Argument: der Eintrag, den release.sh beim letzten Umschalten notiert hat.
[ -f "$ROOT/previous" ] || fail "Keine vorige Veröffentlichung vermerkt. Auswahl zeigen mit: rollback.sh --list"
TARGET="$(basename "$(cat "$ROOT/previous")")"
fi
[ -d "$ROOT/releases/$TARGET" ] || fail "Die Veröffentlichung '$TARGET' gibt es nicht. Auswahl zeigen mit: rollback.sh --list"
[ "$TARGET" != "$CURRENT" ] || fail "'$TARGET' ist bereits aktiv."
step "Zurück von $CURRENT auf $TARGET"
ln -sfn "releases/$TARGET" "$ROOT/current.new"
mv -Tf "$ROOT/current.new" "$ROOT/current"
printf '%s\n' "$CURRENT" > "$ROOT/previous"
step "$FPM_SERVICE neu laden"
sudo systemctl reload "$FPM_SERVICE"
step "n8n-Cache verwerfen"
php "$ROOT/current/backend/bin/ekdos" cache:flush
step "Gesundheitsprüfung"
curl -fsS --max-time 5 "$HEALTH_URL" > /dev/null || fail "$HEALTH_URL antwortet nicht."
step "$TARGET ist wieder aktiv."