name: Deploy auf den Timer-Server # Laeuft bei jedem Push auf main und laesst sich in der Gitea-Oberflaeche # ("Actions" -> Workflow -> "Run workflow") auch von Hand starten. on: push: branches: [main] workflow_dispatch: jobs: deploy: # Nicht ubuntu-latest (24.04): in dem Image scheitern auf diesem Runner # moderne Syscalls - actions/checkout stirbt mit "read ENOTCONN". # Ursache ist das seccomp-Profil des Docker-Hosts, nicht der Workflow. runs-on: ubuntu-22.04 env: DEPLOY_HOST: ${{ vars.DEPLOY_HOST }} DEPLOY_USER: ${{ vars.DEPLOY_USER }} DEPLOY_PATH: ${{ vars.DEPLOY_PATH }} DEPLOY_SERVICE: ${{ vars.DEPLOY_SERVICE }} HEALTH_URL: ${{ vars.HEALTH_URL }} DEPLOY_SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }} DEPLOY_KNOWN_HOSTS: ${{ secrets.DEPLOY_KNOWN_HOSTS }} steps: # Bewusst kein actions/checkout: die JS-Aktion stirbt im Runner-Image # beim Aufruf von git mit "read ENOTCONN". Ein Klon per Shell umgeht das. - name: Werkzeuge und Code holen env: TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | apt-get update -qq apt-get install -y -qq git openssh-client curl rm -rf quelle && mkdir quelle && cd quelle git init -q # Gitea meldet hier seine interne Adresse (http://192.168.1.5:3000), # deshalb Schema und Rest getrennt behandeln statt https anzunehmen. URL="${GITHUB_SERVER_URL%/}" git remote add origin "${URL%%://*}://x-access-token:$TOKEN@${URL#*://}/$GITHUB_REPOSITORY.git" git fetch -q --depth 1 origin "$GITHUB_SHA" || git fetch -q --depth 1 origin "$GITHUB_REF_NAME" git checkout -q FETCH_HEAD echo "Ausgecheckt: $(git log --oneline -1)" - name: Konfiguration pruefen run: | fehlt="" for v in DEPLOY_HOST DEPLOY_USER DEPLOY_PATH DEPLOY_SERVICE DEPLOY_SSH_KEY DEPLOY_KNOWN_HOSTS; do if [ -z "${!v:-}" ]; then fehlt="$fehlt $v"; fi done if [ -n "$fehlt" ]; then echo "Es fehlen noch Variablen/Secrets:$fehlt" echo "Nachzutragen unter Settings -> Actions -> Variables bzw. Secrets." exit 1 fi echo "Ziel: $DEPLOY_USER@$DEPLOY_HOST:$DEPLOY_PATH (Dienst: $DEPLOY_SERVICE)" - name: SSH einrichten run: | mkdir -p ~/.ssh && chmod 700 ~/.ssh printf '%s\n' "$DEPLOY_SSH_KEY" > ~/.ssh/id_ed25519 printf '%s\n' "$DEPLOY_KNOWN_HOSTS" > ~/.ssh/known_hosts chmod 600 ~/.ssh/id_ed25519 chmod 644 ~/.ssh/known_hosts - name: Zielhost pruefen run: | ssh -o BatchMode=yes "$DEPLOY_USER@$DEPLOY_HOST" "DEPLOY_PATH='$DEPLOY_PATH' bash -s" <<'PRUEF' echo "Angemeldet als $(id -un) auf $(hostname)" for w in tar npm node systemctl curl; do printf ' %-10s %s\n' "$w" "$(command -v $w || echo FEHLT)" done echo "Rechte:" ls -ld "$DEPLOY_PATH" "$DEPLOY_PATH/backend" "$DEPLOY_PATH/data" 2>&1 | sed 's/^/ /' if touch "$DEPLOY_PATH/.schreibtest" 2>/dev/null; then rm -f "$DEPLOY_PATH/.schreibtest"; echo "Schreibtest: ok" else echo "Schreibtest: FEHLGESCHLAGEN - $(id -un) darf in $DEPLOY_PATH nicht schreiben"; exit 1 fi echo "sudo-Rechte:"; sudo -n -l 2>&1 | tail -4 | sed 's/^/ /' PRUEF - name: Aenderungen gegen den ausgerollten Stand ermitteln run: | # Der zuletzt ausgerollte Commit liegt als Merkzettel auf dem Server. # Daraus wird die Dateiliste bestimmt - genauer als ein Zeitstempel- # vergleich und ohne rsync, das es auf dem Zielhost nicht gibt. ALT=$(ssh -o BatchMode=yes "$DEPLOY_USER@$DEPLOY_HOST" \ "cat '$DEPLOY_PATH/.deploy-commit' 2>/dev/null || true") cd quelle if [ -n "$ALT" ] && (git cat-file -e "${ALT}^{commit}" 2>/dev/null || git fetch -q --depth 1 origin "$ALT" 2>/dev/null); then echo "Vorheriger Stand: $ALT" git diff --name-status "$ALT" "$GITHUB_SHA" > ../aenderungen.txt || true else echo "Kein bekannter Vorgaengerstand - alle Dateien gelten als neu." git ls-files | sed 's/^/A\t/' > ../aenderungen.txt fi cd .. echo "--- Aenderungen ---"; cat aenderungen.txt - name: Dateien uebertragen run: | # Das Paket enthaelt ausschliesslich versionierte Dateien. Die .env, # data/ und node_modules/ sind darin gar nicht vorhanden und koennen # deshalb auch nicht ueberschrieben werden. tar -czf paket.tgz -C quelle --exclude=.git --exclude=.gitea . ssh -o BatchMode=yes "$DEPLOY_USER@$DEPLOY_HOST" \ "tar -xzf - -C '$DEPLOY_PATH'" < paket.tgz echo "Paket entpackt." # Im Repo geloeschte Dateien auch auf dem Server entfernen. GELOESCHT=$(awk '$1 ~ /^D/ {print $2}' aenderungen.txt) if [ -n "$GELOESCHT" ]; then echo "Entferne:"; echo "$GELOESCHT" | sed 's/^/ /' echo "$GELOESCHT" | ssh -o BatchMode=yes "$DEPLOY_USER@$DEPLOY_HOST" \ "cd '$DEPLOY_PATH' && xargs -r rm -f --" fi # Merkzettel fortschreiben - Grundlage fuer den naechsten Lauf. ssh -o BatchMode=yes "$DEPLOY_USER@$DEPLOY_HOST" \ "echo '$GITHUB_SHA' > '$DEPLOY_PATH/.deploy-commit'" - name: Abhaengigkeiten, Neustart und Health-Check run: | # Neu installiert wird nur, wenn sich die Paketliste geaendert hat. # bcrypt und sqlite3 sind native Module: npm ci gehoert deshalb auf # den Zielhost und nicht in den Runner-Container. NPM=nein RESTART=nein if awk '{print $2}' aenderungen.txt | grep -q '^backend/package'; then NPM=ja; fi if awk '{print $2}' aenderungen.txt | grep -qE '^(backend/|deploy/)'; then RESTART=ja; fi if [ ! -s aenderungen.txt ]; then echo "Nichts geaendert."; fi echo "npm ci: $NPM | Neustart: $RESTART" ssh -o BatchMode=yes "$DEPLOY_USER@$DEPLOY_HOST" \ "NPM='$NPM' RESTART='$RESTART' \ DEPLOY_PATH='$DEPLOY_PATH' DEPLOY_SERVICE='$DEPLOY_SERVICE' \ HEALTH_URL='${HEALTH_URL:-http://127.0.0.1:3003/health}' \ bash -euo pipefail -s" <<'REMOTE' if [ "$NPM" = ja ]; then echo "--- npm ci (nur Produktionsabhaengigkeiten)" cd "$DEPLOY_PATH/backend" npm ci --omit=dev --no-audit --no-fund fi if [ "$RESTART" = nein ]; then echo "--- Keine Backend-Aenderung: Neustart nicht noetig" exit 0 fi echo "--- Dienst neu starten: $DEPLOY_SERVICE" sudo -n systemctl restart "$DEPLOY_SERVICE" echo "--- Health-Check" for i in $(seq 1 15); do if curl -fsS --max-time 3 "$HEALTH_URL" >/dev/null 2>&1; then echo "Dienst antwortet:"; curl -fsS "$HEALTH_URL"; echo exit 0 fi sleep 2 done echo "Dienst antwortet nach 30 s nicht. Letzte Zeilen aus dem Journal:" sudo -n journalctl -u "$DEPLOY_SERVICE" -n 40 --no-pager || true exit 1 REMOTE