chore(deploy): nginx härten, deploy.sh mit Sperre, Zeitlimit und ohne Webhook-Geheimnis
nginx: server_tokens off, HSTS, exaktes /v1 liefert 404 (Regex-Block statt ^~ /v1/). deploy.sh: AIRCARD_WEBHOOK_TOKEN nicht an npm, Sperrverzeichnis gegen parallele Läufe (verwaiste Sperren werden übernommen), timeout 900 für npm ci und Build. Neuer Test deploy/deploy.test.js gegen das echte Skript. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
436ea4f4a5
commit
144181f2c9
5 files changed
+302
-8
No files matched your search
+59
-6
@@ -13,17 +13,32 @@
|
||||
# DEPLOY_TRIGGER (leer = manual)
|
||||
#
|
||||
# Fehlerzeilen beginnen mit "ERROR:"; der letzte Satz eines erfolgreichen Builds ist
|
||||
# "Deployment abgeschlossen".
|
||||
# "Deployment abgeschlossen". Läuft schon ein Deployment (Sperrverzeichnis in .git), meldet das
|
||||
# Skript "läuft bereits" und endet mit 0. npm ci und der Build sind auf 15 Minuten begrenzt (timeout).
|
||||
set -e
|
||||
|
||||
# Jeder Abbruch soll eine ERROR-Zeile hinterlassen, auch wenn set -e ihn auslöst.
|
||||
# Die eigene Sperre (LOCK_HELD) wird bei jedem Ende freigegeben, eine fremde nie.
|
||||
ERROR_PRINTED=""
|
||||
LOCK_HELD=""
|
||||
LOCK_DIR=""
|
||||
fail() {
|
||||
echo "ERROR: $*"
|
||||
ERROR_PRINTED=1
|
||||
exit 1
|
||||
}
|
||||
trap 'status=$?; if [ "${status}" -ne 0 ] && [ -z "${ERROR_PRINTED}" ]; then echo "ERROR: Deployment unerwartet abgebrochen (Exit-Code ${status})"; fi' EXIT
|
||||
cleanup() {
|
||||
status=$?
|
||||
if [ "${status}" -ne 0 ] && [ -z "${ERROR_PRINTED}" ]; then
|
||||
echo "ERROR: Deployment unerwartet abgebrochen (Exit-Code ${status})"
|
||||
fi
|
||||
if [ -n "${LOCK_HELD}" ] && [ -n "${LOCK_DIR}" ]; then
|
||||
rm -rf "${LOCK_DIR}"
|
||||
fi
|
||||
}
|
||||
trap cleanup EXIT
|
||||
# Ein Signal (z. B. Zeitlimit des Webhook-Servers) soll ebenfalls über den EXIT-Trap aufräumen.
|
||||
trap 'exit 143' HUP INT TERM
|
||||
|
||||
REPO_DIR="${REPO_DIR:-/app}"
|
||||
OUT_DIR="${OUT_DIR:-/out}"
|
||||
@@ -78,6 +93,33 @@ fi
|
||||
|
||||
cd "${REPO_DIR}" || fail "Verzeichnis ${REPO_DIR} nicht erreichbar"
|
||||
|
||||
# Nur ein Deployment gleichzeitig (Webhook, Polling und der manuelle Aufruf teilen sich REPO_DIR und OUT_DIR).
|
||||
# mkdir ist atomar. Eine verwaiste Sperre (Prozess weg oder über 60 Minuten alt) wird übernommen.
|
||||
LOCK_DIR="${REPO_DIR}/.git/aircard-deploy.lock"
|
||||
acquire_lock() {
|
||||
mkdir "${LOCK_DIR}" 2>/dev/null || return 1
|
||||
echo "$$" >"${LOCK_DIR}/pid"
|
||||
LOCK_HELD=1
|
||||
}
|
||||
if ! acquire_lock; then
|
||||
_lock_pid="$(cat "${LOCK_DIR}/pid" 2>/dev/null || true)"
|
||||
_lock_stale=""
|
||||
if [ -n "${_lock_pid}" ]; then
|
||||
kill -0 "${_lock_pid}" 2>/dev/null || _lock_stale=1
|
||||
fi
|
||||
if [ -n "$(find "${LOCK_DIR}" -maxdepth 0 -mmin +60 2>/dev/null)" ]; then
|
||||
_lock_stale=1
|
||||
fi
|
||||
if [ -n "${_lock_stale}" ]; then
|
||||
echo "Verwaiste Sperre gefunden, übernehme sie."
|
||||
rm -rf "${LOCK_DIR}"
|
||||
acquire_lock || { echo "Deployment läuft bereits, dieser Lauf wird übersprungen."; exit 0; }
|
||||
else
|
||||
echo "Deployment läuft bereits, dieser Lauf wird übersprungen."
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "Aktualisiere Repository..."
|
||||
# Remote-URL bei jedem Lauf neu setzen, damit ein getauschtes Token greift.
|
||||
git remote set-url origin "${REPO_URL}" || fail "git remote set-url fehlgeschlagen"
|
||||
@@ -111,18 +153,29 @@ echo "${CURRENT_COMMIT}" >"${MARKER}" || fail "Marker ${MARKER} nicht schreibbar
|
||||
[ -d "${REPO_DIR}/${APP_SUBDIR}" ] || fail "Ordner ${APP_SUBDIR} fehlt im Repository"
|
||||
cd "${REPO_DIR}/${APP_SUBDIR}"
|
||||
|
||||
# Zugangsdaten nicht an npm und die Build-Skripte weiterreichen.
|
||||
unset GITEA_TOKEN REPO_URL
|
||||
# Zugangsdaten nicht an npm und die Build-Skripte weiterreichen (auch das Webhook-Geheimnis steht im
|
||||
# Container und käme sonst beim manuellen Aufruf `docker exec ... sh /deploy.sh` bei npm an).
|
||||
unset GITEA_TOKEN REPO_URL AIRCARD_WEBHOOK_TOKEN
|
||||
|
||||
# Ein hängender Build soll nicht ewig laufen (und alle späteren Deployments sperren): 15 Minuten.
|
||||
# `timeout` gibt es in busybox (node:22-alpine); fehlt es, läuft der Build ohne Limit.
|
||||
if command -v timeout >/dev/null 2>&1; then
|
||||
LIMIT="timeout 900"
|
||||
else
|
||||
LIMIT=""
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Installiere Abhängigkeiten..."
|
||||
npm ci --no-audit --no-fund || fail "npm ci fehlgeschlagen"
|
||||
# shellcheck disable=SC2086 # LIMIT besteht absichtlich aus zwei Wörtern
|
||||
${LIMIT} npm ci --no-audit --no-fund || fail "npm ci fehlgeschlagen (oder das Zeitlimit von 15 Minuten ist erreicht)"
|
||||
|
||||
echo ""
|
||||
echo "Baue die Seite..."
|
||||
# VITE_*-Variablen kommen direkt aus der Container-Umgebung (compose).
|
||||
rm -rf dist
|
||||
npm run build || fail "Build fehlgeschlagen"
|
||||
# shellcheck disable=SC2086
|
||||
${LIMIT} npm run build || fail "Build fehlgeschlagen (oder das Zeitlimit von 15 Minuten ist erreicht)"
|
||||
[ -f dist/index.html ] || fail "dist/index.html fehlt nach dem Build"
|
||||
|
||||
echo ""
|
||||
|
||||
Reference in new issue
Block a user