Files
adminandClaude Opus 5 5e83999950
Deploy auf den Timer-Server / deploy (push) Successful in 18s
Abgleich ohne rsync ueber einen Commit-Merkzettel
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>
2026-09-04 18:10:07 +02:00

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