Refactor deployment process and update documentation
- 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:
+165
-97
@@ -3,24 +3,69 @@ name: EK-DOS-WEB bauen und ausrollen
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
workflow_dispatch:
|
||||
|
||||
# Zwei gleichzeitige Ausrollvorgänge kämen sich beim Symlink-Tausch in die Quere.
|
||||
# Ein neuer Lauf wartet, statt den laufenden abzubrechen.
|
||||
concurrency:
|
||||
group: ekdos-deploy
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
DEPLOY_HOST: 10.0.11.131
|
||||
DEPLOY_USER: deploy
|
||||
DEPLOY_ROOT: /var/www/ekdos
|
||||
DEPLOY_PATH: /var/www/ekdos
|
||||
|
||||
jobs:
|
||||
# ──────────────────────────────────────────────────────────────────────────
|
||||
# Die Oberfläche wird hier zu statischen Dateien kompiliert. Node existiert
|
||||
# ausschliesslich in diesem Container; auf dem Zielserver läuft keines.
|
||||
# Backend: nichts zu kompilieren, aber alles zu prüfen. Composer läuft hier
|
||||
# NICHT -- die Abhängigkeiten werden auf dem Webserver aufgelöst, wo PHP-
|
||||
# Version und Erweiterungen tatsächlich stimmen. Eine Plattformprüfung im
|
||||
# Runner würde nur sich selbst prüfen.
|
||||
# ──────────────────────────────────────────────────────────────────────────
|
||||
frontend:
|
||||
backend-pruefen:
|
||||
runs-on: ubuntu-latest
|
||||
container: node:22-bookworm-slim
|
||||
defaults:
|
||||
run:
|
||||
working-directory: frontend
|
||||
container: php:8.5-cli
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Syntax aller PHP-Dateien prüfen
|
||||
working-directory: backend
|
||||
run: |
|
||||
set -eu
|
||||
find src public bin -type f \( -name '*.php' -o -name 'ekdos' \) -print0 \
|
||||
| xargs -0 -n1 -P4 php -l
|
||||
|
||||
- name: composer.json und composer.lock prüfen
|
||||
working-directory: backend
|
||||
run: |
|
||||
set -eu
|
||||
curl -sS https://getcomposer.org/installer \
|
||||
| php -- --install-dir=/usr/local/bin --filename=composer
|
||||
composer validate --no-check-publish
|
||||
# Ohne Sperrdatei löst der Webserver bei jedem Ausrollen neu auf und
|
||||
# bekommt womöglich andere Fassungen als hier geprüft.
|
||||
test -f composer.lock || {
|
||||
echo "composer.lock fehlt. Einmal 'composer update' laufen lassen und einchecken."
|
||||
exit 1
|
||||
}
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────
|
||||
# Bauen und ausrollen in einem Auftrag.
|
||||
#
|
||||
# Bewusst ohne upload-/download-artifact: welche Fassung dieser Aktionen eine
|
||||
# Gitea-Instanz beherrscht (v3 oder v4), hängt an deren Version und ist eine
|
||||
# verlässliche Quelle für Fehlschläge beim ersten Lauf. Das Bündel bleibt
|
||||
# einfach im Arbeitsverzeichnis liegen und geht von dort per rsync raus.
|
||||
#
|
||||
# Node existiert nur in diesem Container -- auf dem Webserver läuft keines.
|
||||
# ──────────────────────────────────────────────────────────────────────────
|
||||
bauen-und-ausrollen:
|
||||
runs-on: ubuntu-latest
|
||||
container: node:22-bookworm
|
||||
needs: [backend-pruefen]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
@@ -28,121 +73,144 @@ jobs:
|
||||
run: corepack enable && corepack prepare pnpm@10.16.1 --activate
|
||||
|
||||
- name: Abhängigkeiten installieren
|
||||
working-directory: frontend
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
# Typprüfung getrennt, damit ein Typfehler nicht als Bündelfehler erscheint.
|
||||
# Getrennt von `build`, damit ein Typfehler als solcher im Protokoll steht
|
||||
# und nicht als Bündelfehler.
|
||||
- name: Typen prüfen
|
||||
working-directory: frontend
|
||||
run: pnpm typecheck
|
||||
|
||||
- name: Bündel bauen
|
||||
working-directory: frontend
|
||||
run: pnpm build
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: frontend-dist
|
||||
path: frontend/dist
|
||||
retention-days: 7
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────
|
||||
# Das Backend wird nicht kompiliert, aber geprüft: Syntax aller Dateien und
|
||||
# ein Abhängigkeitsbaum ohne Entwicklungspakete.
|
||||
# ──────────────────────────────────────────────────────────────────────────
|
||||
backend:
|
||||
runs-on: ubuntu-latest
|
||||
container: php:8.5-cli
|
||||
defaults:
|
||||
run:
|
||||
working-directory: backend
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Composer und Erweiterungen bereitstellen
|
||||
- name: Bündel prüfen
|
||||
working-directory: frontend
|
||||
run: |
|
||||
apt-get update && apt-get install -y --no-install-recommends git unzip libpq-dev
|
||||
docker-php-ext-install pdo_pgsql
|
||||
curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
|
||||
set -eu
|
||||
test -f dist/index.html || { echo "dist/index.html fehlt -- der Bau hat nichts erzeugt."; exit 1; }
|
||||
du -sh dist
|
||||
|
||||
- name: Abhängigkeiten installieren
|
||||
run: composer install --no-dev --no-interaction --prefer-dist --optimize-autoloader --ignore-platform-req=ext-redis
|
||||
# ── Ab hier nur noch aus main ──────────────────────────────────────────
|
||||
|
||||
- name: Syntax prüfen
|
||||
run: find src public bin -type f \( -name '*.php' -o -name 'ekdos' \) -print0 | xargs -0 -n1 php -l
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: backend-build
|
||||
path: |
|
||||
backend
|
||||
!backend/.env
|
||||
retention-days: 7
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────
|
||||
# Ausrollen. Jede Veröffentlichung landet in einem eigenen Verzeichnis; erst
|
||||
# der Symlink-Tausch macht sie sichtbar. Ein Fehlschlag lässt die laufende
|
||||
# Fassung unberührt.
|
||||
# ──────────────────────────────────────────────────────────────────────────
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [frontend, backend]
|
||||
if: gitea.ref == 'refs/heads/main'
|
||||
steps:
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: frontend-dist
|
||||
path: dist
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: backend-build
|
||||
path: backend
|
||||
- name: Werkzeuge zum Ausrollen installieren
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
run: |
|
||||
set -eu
|
||||
apt-get update
|
||||
apt-get install -y --no-install-recommends rsync openssh-client
|
||||
|
||||
- name: SSH einrichten
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
env:
|
||||
DEPLOY_SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
|
||||
DEPLOY_KNOWN_HOSTS: ${{ secrets.DEPLOY_KNOWN_HOSTS }}
|
||||
run: |
|
||||
set -eu
|
||||
test -n "${DEPLOY_SSH_KEY:-}" || { echo "Secret DEPLOY_SSH_KEY fehlt."; exit 1; }
|
||||
|
||||
install -m 700 -d ~/.ssh
|
||||
echo "${{ secrets.DEPLOY_SSH_KEY }}" > ~/.ssh/id_ed25519
|
||||
printf '%s\n' "$DEPLOY_SSH_KEY" > ~/.ssh/id_ed25519
|
||||
chmod 600 ~/.ssh/id_ed25519
|
||||
ssh-keyscan -H "$DEPLOY_HOST" >> ~/.ssh/known_hosts
|
||||
|
||||
# Bevorzugt der hinterlegte Fingerabdruck. Fehlt er, wird er erfragt --
|
||||
# bequem, aber ohne Schutz vor einem untergeschobenen Ziel. Für den
|
||||
# Dauerbetrieb setzen: ssh-keyscan -t ed25519 10.0.11.131
|
||||
if [ -n "${DEPLOY_KNOWN_HOSTS:-}" ]; then
|
||||
printf '%s\n' "$DEPLOY_KNOWN_HOSTS" > ~/.ssh/known_hosts
|
||||
else
|
||||
echo "Hinweis: DEPLOY_KNOWN_HOSTS ist nicht gesetzt, Fingerabdruck wird ungeprüft übernommen."
|
||||
ssh-keyscan -H "$DEPLOY_HOST" >> ~/.ssh/known_hosts 2>/dev/null
|
||||
fi
|
||||
chmod 600 ~/.ssh/known_hosts
|
||||
|
||||
# Lieber hier scheitern als nach dem halben Hochladen.
|
||||
- name: Ziel prüfen
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
run: |
|
||||
set -eu
|
||||
ssh "$DEPLOY_USER@$DEPLOY_HOST" "
|
||||
set -eu
|
||||
test -d '$DEPLOY_PATH/releases' || { echo '$DEPLOY_PATH/releases fehlt -- siehe docs/DEPLOYMENT.md'; exit 1; }
|
||||
test -f '$DEPLOY_PATH/shared/.env' || { echo '$DEPLOY_PATH/shared/.env fehlt -- einmalig anlegen'; exit 1; }
|
||||
command -v composer >/dev/null || { echo 'composer fehlt auf dem Webserver'; exit 1; }
|
||||
command -v php >/dev/null || { echo 'php fehlt auf dem Webserver'; exit 1; }
|
||||
# Prüft die sudo-Regel, ohne etwas auszuführen.
|
||||
sudo -n -l /bin/systemctl reload php8.5-fpm >/dev/null 2>&1 \
|
||||
|| echo 'Hinweis: sudo-Regel für den fpm-Neustart fehlt (siehe docs/DEPLOYMENT.md) -- release.sh wird daran scheitern.'
|
||||
mkdir -p '$DEPLOY_PATH/releases/${{ github.sha }}' '$DEPLOY_PATH/deploy'
|
||||
"
|
||||
|
||||
- name: Oberfläche übertragen
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
run: |
|
||||
RELEASE="$DEPLOY_ROOT/releases/${{ gitea.sha }}"
|
||||
ssh "$DEPLOY_USER@$DEPLOY_HOST" "mkdir -p '$RELEASE'"
|
||||
rsync -az --delete dist/ "$DEPLOY_USER@$DEPLOY_HOST:$RELEASE/"
|
||||
set -eu
|
||||
rsync -az --delete \
|
||||
frontend/dist/ \
|
||||
"$DEPLOY_USER@$DEPLOY_HOST:$DEPLOY_PATH/releases/${{ github.sha }}/public/"
|
||||
|
||||
- name: Backend übertragen
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
run: |
|
||||
# .env bleibt auf dem Server und wird nie überschrieben.
|
||||
set -eu
|
||||
# vendor/ entsteht auf dem Server; .env liegt in shared/ und wird dort
|
||||
# verknüpft; var/ ist der Zwischenspeicher dieser Veröffentlichung.
|
||||
rsync -az --delete \
|
||||
--exclude 'vendor/' \
|
||||
--exclude '.env' \
|
||||
--exclude 'var/cache' \
|
||||
backend/ "$DEPLOY_USER@$DEPLOY_HOST:$DEPLOY_ROOT/backend/"
|
||||
--exclude 'var/' \
|
||||
backend/ \
|
||||
"$DEPLOY_USER@$DEPLOY_HOST:$DEPLOY_PATH/releases/${{ github.sha }}/backend/"
|
||||
|
||||
- name: Umschalten und neu laden
|
||||
- name: Ausrollskripte übertragen
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
run: |
|
||||
ssh "$DEPLOY_USER@$DEPLOY_HOST" bash -euo pipefail <<'REMOTE'
|
||||
cd /var/www/ekdos
|
||||
set -eu
|
||||
rsync -az --chmod=F755 \
|
||||
deploy/release.sh deploy/rollback.sh \
|
||||
"$DEPLOY_USER@$DEPLOY_HOST:$DEPLOY_PATH/deploy/"
|
||||
|
||||
# Schema fortschreiben, bevor die neue Oberfläche sichtbar wird.
|
||||
php backend/bin/ekdos migrate
|
||||
|
||||
# Atomarer Tausch: ln -sfn auf ein temporäres Ziel, dann umbenennen.
|
||||
ln -sfn "releases/${GITEA_SHA:-$(ls -1t releases | head -1)}" current.new
|
||||
mv -Tf current.new current
|
||||
|
||||
# Opcache hält den alten Quelltext, bis fpm neu lädt.
|
||||
sudo systemctl reload php8.5-fpm
|
||||
|
||||
# Zwischengespeicherte n8n-Antworten passen womöglich nicht mehr
|
||||
# zum neuen Abbildungscode.
|
||||
php backend/bin/ekdos cache:flush
|
||||
|
||||
# Die letzten fünf Veröffentlichungen behalten.
|
||||
ls -1t releases | tail -n +6 | xargs -r -I{} rm -rf "releases/{}"
|
||||
REMOTE
|
||||
env:
|
||||
GITEA_SHA: ${{ gitea.sha }}
|
||||
|
||||
- name: Gesundheitsprüfung
|
||||
# Hier passiert das Eigentliche: composer install, Migration, atomarer
|
||||
# Symlink-Tausch, php-fpm neu laden, n8n-Cache verwerfen,
|
||||
# Gesundheitsprüfung. Das Skript liegt unter deploy/release.sh und lässt
|
||||
# sich genauso von Hand aufrufen.
|
||||
- name: Veröffentlichung aktivieren
|
||||
id: aktivieren
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
run: |
|
||||
set -eu
|
||||
ssh "$DEPLOY_USER@$DEPLOY_HOST" \
|
||||
'curl -fsS -o /dev/null -w "%{http_code}\n" http://127.0.0.1/api/health'
|
||||
"$DEPLOY_PATH/deploy/release.sh '${{ github.sha }}'"
|
||||
|
||||
# KEIN automatischer Rückschritt.
|
||||
#
|
||||
# release.sh tauscht den Symlink erst nach composer und Migration. Scheitert
|
||||
# es davor, läuft die alte Fassung unverändert weiter -- ein Rückschritt
|
||||
# würde dann auf die *vorletzte* schalten und aus einem folgenlosen
|
||||
# Fehlschlag einen Ausfall machen. Deshalb hier nur berichten, was live ist.
|
||||
- name: Bei Fehlschlag Lage berichten
|
||||
if: failure() && steps.aktivieren.outcome == 'failure'
|
||||
run: |
|
||||
echo "Das Aktivieren ist fehlgeschlagen. Aktueller Stand auf dem Server:"
|
||||
ssh "$DEPLOY_USER@$DEPLOY_HOST" "
|
||||
echo -n ' aktiv: '; readlink '$DEPLOY_PATH/current' || echo '(keiner)'
|
||||
echo -n ' vorher: '; cat '$DEPLOY_PATH/previous' 2>/dev/null || echo '(nichts vermerkt)'
|
||||
curl -fsS --max-time 5 http://127.0.0.1/api/health && echo || echo ' /api/health antwortet NICHT'
|
||||
" || true
|
||||
echo
|
||||
echo "Läuft die alte Fassung noch, ist nichts weiter zu tun -- Ursache beheben und erneut pushen."
|
||||
echo "Sonst von Hand zurück: ssh $DEPLOY_USER@$DEPLOY_HOST $DEPLOY_PATH/deploy/rollback.sh"
|
||||
exit 1
|
||||
|
||||
# /api/health ist im Port-80-Block von der HTTPS-Umleitung ausgenommen,
|
||||
# sonst wäre die 301 für `curl -f` ein Erfolg und PHP nie erreicht.
|
||||
- name: Ergebnis
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
run: |
|
||||
set -eu
|
||||
echo "Ausgerollt: ${{ github.sha }}"
|
||||
ssh "$DEPLOY_USER@$DEPLOY_HOST" \
|
||||
"curl -fsS --max-time 5 -H 'Accept: application/json' http://127.0.0.1/api/health"
|
||||
echo
|
||||
|
||||
Reference in New Issue
Block a user