248 lines
11 KiB
YAML
248 lines
11 KiB
YAML
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_PATH: /var/www/ekdos
|
|
|
|
jobs:
|
|
# ──────────────────────────────────────────────────────────────────────────
|
|
# 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.
|
|
# ──────────────────────────────────────────────────────────────────────────
|
|
backend-pruefen:
|
|
runs-on: ubuntu-latest
|
|
container: php:8.5-cli
|
|
steps:
|
|
# MUSS vor dem Checkout stehen.
|
|
#
|
|
# actions/checkout ist eine JavaScript-Aktion. act_runner führt solche
|
|
# Aktionen mit dem `node` AUS DEM JOB-CONTAINER aus -- anders als die
|
|
# gehosteten Runner von GitHub, die ihr eigenes node mitbringen.
|
|
# php:8.5-cli enthält weder node noch git, deshalb scheitert der Checkout
|
|
# dort mit „exit code 127" (command not found), ohne eine Zeile Ausgabe.
|
|
#
|
|
# Dieser Schritt ist ein reines Shell-Kommando und braucht kein node,
|
|
# läuft also auch im nackten Container.
|
|
- name: git und node für den Checkout bereitstellen
|
|
run: |
|
|
set -eu
|
|
apt-get update
|
|
apt-get install -y --no-install-recommends git nodejs curl ca-certificates
|
|
echo "node $(node --version), $(git --version)"
|
|
|
|
- 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
|
|
|
|
# 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."
|
|
echo
|
|
echo "Einmalig auf dem Webserver erzeugen -- nur dort stimmen PHP-Fassung"
|
|
echo "und Erweiterungen, sodass die Auflösung zur Zielmaschine passt:"
|
|
echo
|
|
echo " scp backend/composer.json deploy@10.0.11.131:/tmp/"
|
|
echo " ssh deploy@10.0.11.131 'cd /tmp && composer update --no-interaction --no-autoloader --no-scripts'"
|
|
echo " scp deploy@10.0.11.131:/tmp/composer.lock backend/"
|
|
echo
|
|
echo "Danach backend/composer.lock einchecken."
|
|
exit 1
|
|
}
|
|
|
|
# --strict, damit eine veraltete Sperrdatei den Lauf anhält statt nur
|
|
# zu warnen: sonst rollt der Server etwas anderes aus als hier geprüft.
|
|
# (composer validate meldet das ohne --strict nur als Warnung.)
|
|
composer validate --no-check-publish --strict
|
|
|
|
# ──────────────────────────────────────────────────────────────────────────
|
|
# 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
|
|
|
|
- name: pnpm bereitstellen
|
|
run: corepack enable && corepack prepare pnpm@10.16.1 --activate
|
|
|
|
- name: Abhängigkeiten installieren
|
|
working-directory: frontend
|
|
run: pnpm install --frozen-lockfile
|
|
|
|
# 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
|
|
|
|
- name: Bündel prüfen
|
|
working-directory: frontend
|
|
run: |
|
|
set -eu
|
|
test -f dist/index.html || { echo "dist/index.html fehlt -- der Bau hat nichts erzeugt."; exit 1; }
|
|
du -sh dist
|
|
|
|
# ── Ab hier nur noch aus main ──────────────────────────────────────────
|
|
|
|
- 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
|
|
printf '%s\n' "$DEPLOY_SSH_KEY" > ~/.ssh/id_ed25519
|
|
chmod 600 ~/.ssh/id_ed25519
|
|
|
|
# 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: |
|
|
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: |
|
|
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/' \
|
|
backend/ \
|
|
"$DEPLOY_USER@$DEPLOY_HOST:$DEPLOY_PATH/releases/${{ github.sha }}/backend/"
|
|
|
|
- name: Ausrollskripte übertragen
|
|
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
|
run: |
|
|
set -eu
|
|
rsync -az --chmod=F755 \
|
|
deploy/release.sh deploy/rollback.sh \
|
|
"$DEPLOY_USER@$DEPLOY_HOST:$DEPLOY_PATH/deploy/"
|
|
|
|
# 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" \
|
|
"$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
|