Deploy auf den Timer-Server / deploy (push) Failing after 13s
rsync scheitert im Runner-Container am Docker-Host ('pipe: Permission
denied', auch im 22.04-Image). Das Paket geht jetzt per tar hinueber und
wird auf dem Server abgeglichen, wo rsync normal laeuft.
Dabei die Rechte geradegezogen: der Deploy-Benutzer besitzt den Code und
braucht dafuer kein sudo. Das frueher vorgesehene 'chown -R timerapp' nach
jedem Lauf haette ihm die Schreibrechte fuer den naechsten Lauf entzogen -
und die Einrichtungsanleitung haette dem Dienst das Datenverzeichnis
weggenommen. Jetzt: Code gehoert deploy, data/ gehoert timerapp.
Ein Diagnoseschritt meldet zu Beginn Benutzer, Werkzeuge, Rechte und
sudo-Regeln, damit solche Faelle im Protokoll sichtbar sind.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
160 lines
6.8 KiB
YAML
160 lines
6.8 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 ausloesen.
|
|
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", rsync
|
|
# mit "pipe: Permission denied". Ursache ist das seccomp-Profil des
|
|
# Docker-Hosts, nicht der Workflow. Das aeltere Image kommt damit klar.
|
|
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.
|
|
- 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
|
|
|
|
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"
|
|
# 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)"
|
|
|
|
- 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: Verbindung 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
|
|
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"
|
|
else
|
|
echo " FEHLGESCHLAGEN - der Deploy-Benutzer darf dort nicht schreiben"
|
|
fi
|
|
echo "sudo-Rechte:"; sudo -n -l 2>&1 | tail -5 | sed 's/^/ /'
|
|
PRUEF
|
|
|
|
- name: Dateien uebertragen und abgleichen
|
|
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.
|
|
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
|
|
|
|
# --delete raeumt geloeschte Dateien weg. Die ausgeschlossenen Pfade
|
|
# sind davon ausgenommen (kein --delete-excluded!): Konfiguration,
|
|
# Datenbank und node_modules bleiben unangetastet.
|
|
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
|
|
|
|
- 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 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
|
|
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'
|
|
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
|
|
fi
|
|
|
|
if [ "$RESTART" = nein ]; then
|
|
echo "--- Keine Backend-Aenderung: Neustart nicht noetig"
|
|
exit 0
|
|
fi
|
|
|
|
echo "--- Dienst neu starten: $DEPLOY_SERVICE"
|
|
$SUDO 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 journalctl -u "$DEPLOY_SERVICE" -n 40 --no-pager || true
|
|
exit 1
|
|
REMOTE
|