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>
171 lines
7.2 KiB
YAML
171 lines
7.2 KiB
YAML
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
|