Abgleich ohne rsync ueber einen Commit-Merkzettel
Deploy auf den Timer-Server / deploy (push) Successful in 18s
Deploy auf den Timer-Server / deploy (push) Successful in 18s
Auf dem Zielhost gibt es kein rsync (und im Runner-Container laeuft es wegen des Docker-Hosts ohnehin nicht). Der ausgerollte Commit steht jetzt in /opt/timer-app/.deploy-commit; die Aenderungsliste kommt aus git diff gegen diesen Stand. Das ist genauer als ein Zeitstempelvergleich: geloeschte Dateien sind als solche benannt und werden gezielt entfernt, und ob npm ci und ein Neustart noetig sind, entscheidet dieselbe Liste. Ist kein Vorgaengerstand bekannt, gilt alles als neu. Der Pruefschritt bricht jetzt ab, wenn der Deploy-Benutzer im Zielpfad nicht schreiben darf - das ist besser als ein halb ausgerollter Stand. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
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 ausloesen.
|
||||
# ("Actions" -> Workflow -> "Run workflow") auch von Hand starten.
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
@@ -10,32 +10,28 @@ on:
|
||||
jobs:
|
||||
deploy:
|
||||
# Nicht ubuntu-latest (24.04): in dem Image scheitern auf diesem Runner
|
||||
# moderne Syscalls - actions/checkout stirbt mit "read ENOTCONN", rsync
|
||||
# mit "pipe: Permission denied". Ursache ist das seccomp-Profil des
|
||||
# Docker-Hosts, nicht der Workflow. Das aeltere Image kommt damit klar.
|
||||
# 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:
|
||||
# Repository-Variablen (Settings -> Actions -> Variables)
|
||||
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 }}
|
||||
# Repository-Secrets (Settings -> Actions -> Secrets)
|
||||
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
|
||||
# von Gitea 1.27 beim Aufruf von git mit "read ENOTCONN" (Node-24-Fehler
|
||||
# im Container). Ein Klon per Shell umgeht das und braucht kein Node.
|
||||
# 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 rsync openssh-client curl
|
||||
apt-get install -y -qq git openssh-client curl
|
||||
|
||||
rm -rf quelle && mkdir quelle && cd quelle
|
||||
git init -q
|
||||
@@ -43,7 +39,6 @@ jobs:
|
||||
# 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"
|
||||
# Nach SHA, falls der Server das nicht erlaubt ersatzweise nach Branch.
|
||||
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)"
|
||||
@@ -69,47 +64,65 @@ jobs:
|
||||
chmod 600 ~/.ssh/id_ed25519
|
||||
chmod 644 ~/.ssh/known_hosts
|
||||
|
||||
- name: Verbindung pruefen
|
||||
- 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 rsync npm node systemctl; do
|
||||
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/^/ /'
|
||||
echo "Schreibtest im Anwendungsverzeichnis:"
|
||||
if touch "$DEPLOY_PATH/.schreibtest" 2>/dev/null; then
|
||||
rm -f "$DEPLOY_PATH/.schreibtest"; echo " ok"
|
||||
rm -f "$DEPLOY_PATH/.schreibtest"; echo "Schreibtest: ok"
|
||||
else
|
||||
echo " FEHLGESCHLAGEN - der Deploy-Benutzer darf dort nicht schreiben"
|
||||
echo "Schreibtest: FEHLGESCHLAGEN - $(id -un) darf in $DEPLOY_PATH nicht schreiben"; exit 1
|
||||
fi
|
||||
echo "sudo-Rechte:"; sudo -n -l 2>&1 | tail -5 | sed 's/^/ /'
|
||||
echo "sudo-Rechte:"; sudo -n -l 2>&1 | tail -4 | sed 's/^/ /'
|
||||
PRUEF
|
||||
|
||||
- name: Dateien uebertragen und abgleichen
|
||||
- name: Aenderungen gegen den ausgerollten Stand ermitteln
|
||||
run: |
|
||||
# rsync laeuft NICHT im Runner-Container: dessen Docker-Host verbietet
|
||||
# den pipe()-Aufruf ("pipe: Permission denied"). Deshalb geht das
|
||||
# Paket per tar hinueber und der Abgleich passiert auf dem Zielhost.
|
||||
# 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" \
|
||||
'rm -rf /tmp/timer-app-release && mkdir -p /tmp/timer-app-release && tar -xzf - -C /tmp/timer-app-release' \
|
||||
< paket.tgz
|
||||
"tar -xzf - -C '$DEPLOY_PATH'" < paket.tgz
|
||||
echo "Paket entpackt."
|
||||
|
||||
# --delete raeumt geloeschte Dateien weg. Die ausgeschlossenen Pfade
|
||||
# sind davon ausgenommen (kein --delete-excluded!): Konfiguration,
|
||||
# Datenbank und node_modules bleiben unangetastet.
|
||||
# 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" \
|
||||
"DEPLOY_PATH='$DEPLOY_PATH' bash -euo pipefail -s" > uebertragen.txt <<'SYNC'
|
||||
rsync -rlpt --delete --out-format='%n' \
|
||||
--exclude='.env' --exclude='.env.*' \
|
||||
--exclude='data/' --exclude='node_modules/' \
|
||||
/tmp/timer-app-release/ "$DEPLOY_PATH/"
|
||||
rm -rf /tmp/timer-app-release
|
||||
SYNC
|
||||
cat uebertragen.txt
|
||||
"echo '$GITHUB_SHA' > '$DEPLOY_PATH/.deploy-commit'"
|
||||
|
||||
- name: Abhaengigkeiten, Neustart und Health-Check
|
||||
run: |
|
||||
@@ -118,9 +131,9 @@ jobs:
|
||||
# den Zielhost und nicht in den Runner-Container.
|
||||
NPM=nein
|
||||
RESTART=nein
|
||||
if grep -q 'backend/package' uebertragen.txt; then NPM=ja; fi
|
||||
if grep -qE '^(backend/|deploy/)' uebertragen.txt; then RESTART=ja; fi
|
||||
if [ ! -s uebertragen.txt ]; then echo "Nichts geaendert."; fi
|
||||
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" \
|
||||
@@ -128,12 +141,10 @@ jobs:
|
||||
DEPLOY_PATH='$DEPLOY_PATH' DEPLOY_SERVICE='$DEPLOY_SERVICE' \
|
||||
HEALTH_URL='${HEALTH_URL:-http://127.0.0.1:3003/health}' \
|
||||
bash -euo pipefail -s" <<'REMOTE'
|
||||
SUDO=""; [ "$(id -u)" -eq 0 ] || SUDO="sudo -n"
|
||||
|
||||
if [ "$NPM" = ja ]; then
|
||||
echo "--- npm ci (nur Produktionsabhaengigkeiten)"
|
||||
cd "$DEPLOY_PATH/backend"
|
||||
$SUDO npm ci --omit=dev --no-audit --no-fund
|
||||
npm ci --omit=dev --no-audit --no-fund
|
||||
fi
|
||||
|
||||
if [ "$RESTART" = nein ]; then
|
||||
@@ -142,7 +153,7 @@ jobs:
|
||||
fi
|
||||
|
||||
echo "--- Dienst neu starten: $DEPLOY_SERVICE"
|
||||
$SUDO systemctl restart "$DEPLOY_SERVICE"
|
||||
sudo -n systemctl restart "$DEPLOY_SERVICE"
|
||||
|
||||
echo "--- Health-Check"
|
||||
for i in $(seq 1 15); do
|
||||
@@ -154,6 +165,6 @@ jobs:
|
||||
done
|
||||
|
||||
echo "Dienst antwortet nach 30 s nicht. Letzte Zeilen aus dem Journal:"
|
||||
$SUDO journalctl -u "$DEPLOY_SERVICE" -n 40 --no-pager || true
|
||||
sudo -n journalctl -u "$DEPLOY_SERVICE" -n 40 --no-pager || true
|
||||
exit 1
|
||||
REMOTE
|
||||
|
||||
@@ -487,12 +487,14 @@ Stand selbsttaetig auf den Server aus. Der Workflow liegt in
|
||||
|
||||
### Was der Workflow tut
|
||||
|
||||
1. Uebertraegt die versionierten Dateien als tar-Paket auf den Server und
|
||||
gleicht sie dort per `rsync` gegen `/opt/timer-app` ab. (Im Runner-Container
|
||||
laesst der Docker-Host rsync nicht laufen - `pipe: Permission denied`.)
|
||||
1. Uebertraegt die versionierten Dateien als tar-Paket und entpackt sie ueber
|
||||
`/opt/timer-app`. Kein rsync: im Runner-Container verbietet der Docker-Host
|
||||
dessen `pipe()`-Aufruf, und auf dem Zielhost ist rsync nicht installiert.
|
||||
**Nicht angefasst** werden `backend/.env`, `data/` und `node_modules/` —
|
||||
sie sind vom Abgleich ausgenommen und ueberleben auch das `--delete`,
|
||||
das geloeschte Dateien auf dem Server aufraeumt.
|
||||
sie liegen nicht im Repo und sind damit auch nicht im Paket.
|
||||
Im Repo geloeschte Dateien werden auf dem Server entfernt: die Liste kommt
|
||||
aus `git diff` gegen den zuletzt ausgerollten Commit, der als Merkzettel
|
||||
in `/opt/timer-app/.deploy-commit` liegt.
|
||||
2. `npm ci --omit=dev` **nur**, wenn sich `backend/package*.json` geaendert hat.
|
||||
Das laeuft bewusst auf dem Zielhost: `bcrypt` und `sqlite3` sind native
|
||||
Module und muessen zur dortigen Architektur passen.
|
||||
|
||||
Reference in New Issue
Block a user