Harden pre-production deployment with external environment and preflights

This commit is contained in:
2026-09-16 00:22:19 +02:00
parent 5d3ae10036
commit 2813795c9d
17 changed files with 1002 additions and 76 deletions
+61 -18
View File
@@ -1,7 +1,29 @@
#!/usr/bin/env bash
set +x
set -Eeuo pipefail
umask 077
trap 'status=$?; printf "FEHLER: Deployment in Zeile %s abgebrochen (Exit %s).\n" "$LINENO" "$status" >&2' ERR
# The only active Pre-Production env source. Never source the checkout's .env.
ENV_FILE='/home/kai/.config/metalcircle/preprod.env'
DEPLOYMENT_STARTED=0
on_error() {
local status=$?
if [[ "$DEPLOYMENT_STARTED" == 0 ]]; then
printf 'Deployment aborted. Running container unchanged.\n' >&2
else
printf 'Deployment verification failed after the container update. Manual investigation required.\n' >&2
fi
exit "$status"
}
trap on_error ERR
CHECK_ONLY=0
if [[ "${1:-}" == '--check' && "$#" == 1 ]]; then
CHECK_ONLY=1
elif [[ "$#" != 0 ]]; then
printf 'Usage: ./scripts/deploy-preprod.sh [--check]\n' >&2
exit 1
fi
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
REPO_ROOT="$(cd -- "${SCRIPT_DIR}/.." && pwd -P)"
@@ -18,48 +40,69 @@ if [[ "$BRANCH" != "main" ]]; then
exit 1
fi
if [[ -n "$(git status --porcelain --untracked-files=all)" ]]; then
WORKTREE_STATUS="$(git status --porcelain --untracked-files=all)"
if [[ -n "$WORKTREE_STATUS" ]]; then
printf 'FEHLER: Working Tree ist nicht sauber. Änderungen zuerst committen oder entfernen.\n' >&2
exit 1
fi
printf 'Aktualisiere main mit Fast-Forward ...\n'
git pull --ff-only origin main
if [[ "$CHECK_ONLY" == 0 ]]; then
printf 'Aktualisiere main mit Fast-Forward ...\n'
git pull --ff-only origin main
fi
host_check() {
python3 "$SCRIPT_DIR/preprod_config.py" --env-file "$ENV_FILE" "$@"
}
compose() {
sudo docker compose -f compose.yml -f compose.preprod.yml "$@"
# Executes sudo docker compose --env-file "$ENV_FILE" -f compose.yml -f compose.preprod.yml.
# The helper clears ambient overrides and suppresses secret-bearing raw diagnostics.
host_check compose "$@"
}
run_preflight() {
local output
output="$("$@" python push_preflight.py \
--expected-service-account metalcircle-push-preprod@metalcircle-30d9b.iam.gserviceaccount.com)"
printf '%s\n' "$output"
if [[ "$output" != PASS:* ]]; then
printf 'FEHLER: Push-Preflight hat kein PASS geliefert.\n' >&2
return 1
fi
"$@" python push_preflight.py \
--expected-service-account metalcircle-push-preprod@metalcircle-30d9b.iam.gserviceaccount.com
"$@" python gitea_preflight.py
}
printf 'Prüfe externe Environment-Datei und Host-Secret ...\n'
host_check check
CONFIG_FINGERPRINT="$(host_check fingerprint)"
printf 'Prüfe Pre-Production-Compose-Konfiguration ...\n'
compose config --quiet
printf 'Baue Web-Image ...\n'
compose build web
printf 'Prüfe Firebase-Credential im temporären Container ...\n'
run_preflight compose run --rm --no-deps web
printf 'Prüfe Firebase und Gitea im temporären Container ...\n'
run_preflight compose run --rm --no-deps -T web
if [[ "$(host_check fingerprint)" != "$CONFIG_FINGERPRINT" ]]; then
printf 'ERROR: Environment or Firebase secret changed during deployment. Start again.\n' >&2
false
fi
if [[ "$CHECK_ONLY" == 1 ]]; then
printf 'PASS: Pre-Production preflights completed; running container unchanged.\n'
exit 0
fi
printf 'Sichere geprüfte Environment-Datei außerhalb des Checkouts ...\n'
host_check backup
printf 'Aktualisiere ausschließlich den Webcontainer ...\n'
DEPLOYMENT_STARTED=1
compose up -d --no-deps web
printf 'Prüfe Firebase-Credential im laufenden Webcontainer ...\n'
printf 'Prüfe Firebase und Gitea im laufenden Webcontainer ...\n'
run_preflight compose exec -T web
printf '\nCompose-Status:\n'
compose ps
compose ps --format json
printf '\nWeb-Logs der letzten 2 Minuten (maximal 100 Zeilen):\n'
printf '\nWeb-Logs der letzten 2 Minuten (maximal 100 Zeilen, sicher gefiltert):\n'
compose logs --since=2m --tail=100 --no-color web
printf '\nDeployter Git-Commit:\n'