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: - 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 - 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