Files
EKDOS/.gitea/workflows/build.yml
T
Kyle Müller 937b6ee682
EK-DOS-WEB bauen und ausrollen / backend-pruefen (push) Failing after 20s
EK-DOS-WEB bauen und ausrollen / bauen-und-ausrollen (push) Skipped
Update PHP version constraint in composer.json and enhance composer.lock validation in build workflow
2026-09-06 22:49:12 +02:00

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