Deploy per Gitea Actions auf 192.168.1.5
Deploy auf den Timer-Server / deploy (push) Failing after 2s
Deploy auf den Timer-Server / deploy (push) Failing after 2s
Push auf main rollt den Stand selbst aus, statt ihn von Hand zu kopieren. Der Runner (pve-runner) liegt im selben LAN wie der Zielhost. Drei Dinge, die dabei nicht passieren duerfen und deshalb abgesichert sind: - Die .env, das data-Verzeichnis und node_modules sind vom rsync-Abgleich ausgenommen. Das --delete raeumt zwar geloeschte Dateien weg, laesst diese Pfade aber in Ruhe (kein --delete-excluded). - npm ci laeuft auf dem Zielhost, nicht im Runner-Container: bcrypt und sqlite3 sind native Module und muessen zur dortigen Umgebung passen. Aufgerufen wird es nur, wenn sich backend/package*.json geaendert hat. - Neu gestartet wird nur bei Aenderungen unter backend/ oder deploy/. Eine geaenderte Frontend-Datei wird ausgeliefert, ohne den laufenden Dienst anzufassen. Danach ein Health-Check gegen /health; bleibt die Antwort aus, schlaegt der Lauf fehl und die letzten Journal-Zeilen stehen im Protokoll. Die Einrichtung (Deploy-Benutzer, eng gefasste sudo-Regel, Variablen und Secrets) steht in DEPLOY.md. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,117 @@
|
|||||||
|
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:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
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 }}
|
||||||
|
DEPLOY_OWNER: ${{ vars.DEPLOY_OWNER }}
|
||||||
|
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:
|
||||||
|
- name: Code auschecken
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Werkzeuge installieren
|
||||||
|
run: |
|
||||||
|
apt-get update -qq
|
||||||
|
apt-get install -y -qq rsync openssh-client
|
||||||
|
|
||||||
|
- 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: Dateien uebertragen
|
||||||
|
run: |
|
||||||
|
# --delete raeumt geloeschte Dateien auf dem Server weg. Die
|
||||||
|
# ausgeschlossenen Pfade sind davon ausgenommen (kein --delete-excluded!):
|
||||||
|
# Konfiguration, Datenbank und node_modules bleiben unangetastet.
|
||||||
|
rsync -rlpt --delete --out-format='%n' \
|
||||||
|
--exclude='.git/' --exclude='.gitea/' \
|
||||||
|
--exclude='.env' --exclude='.env.*' \
|
||||||
|
--exclude='data/' --exclude='node_modules/' \
|
||||||
|
-e 'ssh -o BatchMode=yes' \
|
||||||
|
./ "$DEPLOY_USER@$DEPLOY_HOST:$DEPLOY_PATH/" | tee 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' \
|
||||||
|
DEPLOY_OWNER='${DEPLOY_OWNER:-timerapp:timerapp}' \
|
||||||
|
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
|
||||||
|
|
||||||
|
# Alles unter dem Anwendungsverzeichnis gehoert dem Dienstbenutzer.
|
||||||
|
$SUDO chown -R "$DEPLOY_OWNER" "$DEPLOY_PATH"
|
||||||
|
|
||||||
|
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
|
||||||
@@ -475,3 +475,73 @@ CSV, Filter, Löschen, Abmelden) sowie ein Durchlauf im echten Browser.
|
|||||||
Die App ist klein genug, um sie vollständig zu lesen: `server.js` ~470 Zeilen,
|
Die App ist klein genug, um sie vollständig zu lesen: `server.js` ~470 Zeilen,
|
||||||
`app.js` ~460 Zeilen, beide durchgehend kommentiert und auf Deutsch beschriftet.
|
`app.js` ~460 Zeilen, beide durchgehend kommentiert und auf Deutsch beschriftet.
|
||||||
Im Zweifel ist das Lesen der Quelle schneller als das Raten.
|
Im Zweifel ist das Lesen der Quelle schneller als das Raten.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Automatischer Deploy per Gitea Actions
|
||||||
|
|
||||||
|
Jeder Push auf `main` in `git.handy-insel.de/kerimatilgan/timer-app` rollt den
|
||||||
|
Stand selbsttaetig auf den Server aus. Der Workflow liegt in
|
||||||
|
`.gitea/workflows/deploy.yaml` und laesst sich in der Gitea-Oberflaeche unter
|
||||||
|
"Actions" auch von Hand starten.
|
||||||
|
|
||||||
|
### Was der Workflow tut
|
||||||
|
|
||||||
|
1. Uebertraegt die versionierten Dateien per `rsync` nach `/opt/timer-app`.
|
||||||
|
**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.
|
||||||
|
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.
|
||||||
|
3. Neustart des Dienstes **nur**, wenn sich etwas unter `backend/` oder
|
||||||
|
`deploy/` geaendert hat. Reine Frontend-Aenderungen brauchen keinen.
|
||||||
|
4. Health-Check gegen `/health` (15 Versuche, 30 s). Antwortet der Dienst
|
||||||
|
nicht, schlaegt der Lauf fehl und die letzten 40 Journal-Zeilen stehen
|
||||||
|
im Protokoll.
|
||||||
|
|
||||||
|
### Einmalige Einrichtung
|
||||||
|
|
||||||
|
Auf dem Zielhost einen Deploy-Benutzer anlegen, ihm das Verzeichnis geben und
|
||||||
|
den Neustart des einen Dienstes erlauben:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
useradd -r -m -s /bin/bash deploy
|
||||||
|
mkdir -p /home/deploy/.ssh && chmod 700 /home/deploy/.ssh
|
||||||
|
echo '<oeffentlicher Schluessel>' >> /home/deploy/.ssh/authorized_keys
|
||||||
|
chown -R deploy:deploy /home/deploy/.ssh && chmod 600 /home/deploy/.ssh/authorized_keys
|
||||||
|
|
||||||
|
# Schreibrecht auf das Anwendungsverzeichnis
|
||||||
|
chown -R deploy:timerapp /opt/timer-app && chmod -R g+rX /opt/timer-app
|
||||||
|
|
||||||
|
# Genau die drei Befehle ohne Passwort - nicht mehr
|
||||||
|
cat > /etc/sudoers.d/deploy-timer-app <<'EOF'
|
||||||
|
deploy ALL=(root) NOPASSWD: /bin/systemctl restart timer-app, \
|
||||||
|
/bin/journalctl -u timer-app *, \
|
||||||
|
/bin/chown -R timerapp\:timerapp /opt/timer-app
|
||||||
|
EOF
|
||||||
|
chmod 440 /etc/sudoers.d/deploy-timer-app
|
||||||
|
visudo -c
|
||||||
|
```
|
||||||
|
|
||||||
|
Im Repo unter **Settings -> Actions** hinterlegen:
|
||||||
|
|
||||||
|
| Art | Name | Wert |
|
||||||
|
|---|---|---|
|
||||||
|
| Variable | `DEPLOY_HOST` | `192.168.1.5` |
|
||||||
|
| Variable | `DEPLOY_USER` | `deploy` |
|
||||||
|
| Variable | `DEPLOY_PATH` | `/opt/timer-app` |
|
||||||
|
| Variable | `DEPLOY_SERVICE` | `timer-app` |
|
||||||
|
| Variable | `DEPLOY_OWNER` | `timerapp:timerapp` |
|
||||||
|
| Variable | `HEALTH_URL` | `http://127.0.0.1:3003/health` |
|
||||||
|
| Secret | `DEPLOY_SSH_KEY` | privater Deploy-Schluessel (ganze Datei inkl. BEGIN/END-Zeilen) |
|
||||||
|
| Secret | `DEPLOY_KNOWN_HOSTS` | `ssh-keyscan 192.168.1.5` — Zeile(n) fuer den Zielhost |
|
||||||
|
|
||||||
|
### Grenzen
|
||||||
|
|
||||||
|
- Die `.env` wird **nie** ausgerollt. Kommt eine neue Einstellung dazu, gehoert
|
||||||
|
sie von Hand in die `.env` auf dem Server (Vorlage: `backend/.env.example`),
|
||||||
|
sonst startet der Dienst nach dem Deploy womoeglich nicht.
|
||||||
|
- Es gibt keinen automatischen Rollback. Faellt der Health-Check durch, laeuft
|
||||||
|
der Dienst weiter im Fehlerzustand — der Lauf ist rot und das Journal steht
|
||||||
|
im Protokoll. Zurueck geht es mit einem Revert-Commit auf `main`.
|
||||||
|
|||||||
Reference in New Issue
Block a user