Phase 13.5: Remediation Build/Deploy Control Plane - build-runner-remediation, verify-runner-remediation-image, recreate-runner-remediation-safe, verify-remediation-build-input; Full/Partial SoT-Backup-Klassifikation (Blocker A)
This commit is contained in:
parent
223fa33f50
commit
6194868100
1 changed files with 299 additions and 25 deletions
|
|
@ -169,6 +169,24 @@ EXPECTED_RUNNER_PATCH_SHA256="6cca3c55bb559d75be2f58e59c231003902f124a54ada25582
|
|||
# Fester Backup-Root fuer die Promotion-Session (root-owned, nicht world-readable).
|
||||
PROMOTE_BACKUP_ROOT="/opt/rq-historical-sot-backups/pre-runner-remediation"
|
||||
|
||||
# ------------------------------------------------- REMEDIATION BUILD/DEPLOY-KANAL
|
||||
# Phase 13.5 · Controlled Runner-Remediation Build/Deploy Channel.
|
||||
# KEIN echter Build/Recreate durch Rain; nur Kanal. Red Queen fuehrt nach Freigabe aus.
|
||||
# Fester Remediation-Tag (Punkt 3) — KEIN Overwrite, KEIN force-retag, KEIN latest.
|
||||
REMEDIATION_TAG="historical-v2-historical-service:candidate-phase13-5-runner-remediation"
|
||||
# Build-Session-Basis (root-owned; enthaelt nach Build session.meta mit FULL_IMAGE_ID).
|
||||
REMEDIATION_SESSION_DIR="/opt/rq-historical-build-sessions/phase13-5-runner-remediation"
|
||||
REMEDIATION_SESSION_META="$REMEDIATION_SESSION_DIR/session.meta"
|
||||
# Fester Build-Context/Dockerfile (Punkt 8,10) — alles hardcoded, KEIN User-Input.
|
||||
REMEDIATION_BUILD_CONTEXT="/opt/historical-v2"
|
||||
REMEDIATION_BUILD_DOCKERFILE="/opt/historical-v2/Dockerfile"
|
||||
# Expected Runner-SHAs (Punkt 10/11): ALT = Gold (vor Remediation), NEU = persistent (nach).
|
||||
RUNNER_GOLD_SHA="70d3abb1a92a871e28cc6fb95b9a26944b55dd8df1b72db80642d6881b7b0f20"
|
||||
RUNNER_REMEDIATION_SHA="6cca3c55bb559d75be2f58e59c231003902f124a54ada25582970524599f0752"
|
||||
# Recovery-Gold (immutable Baseline) — Referenz fuer Expected-Gold-Delta.
|
||||
# Candidate/Recovery duerfen code-identisch sein, aber verschiedene Image-IDs haben (KEIN Fehler).
|
||||
REM_NOOP_IMAGE_ID="sha256:1f3ba210780c5a00d4b73c5ee7817a88671fee2607a4ae0d5b1cefef1a1261c7"
|
||||
|
||||
# RUN_MODE (Mutation Guard): READ_ONLY | DRY_RUN | MUTATING
|
||||
RUN_MODE=MUTATING
|
||||
|
||||
|
|
@ -188,12 +206,44 @@ expect_zero() { [ $# -eq 0 ] || die "unerlaubtes Argument '$*'"; }
|
|||
# MODE ist global: MUTATING (Default) | DRY_RUN (keine Mutationen)
|
||||
mode_or_die() { [ "$RUN_MODE" != "DRY_RUN" ] || die "DRY_RUN-Modus, aber mutierender Pfad erreicht (FAIL CLOSED)"; }
|
||||
|
||||
sot_bestandscheck() { # gültiges vollständiges backup-sot vorhanden?
|
||||
local latest bdir
|
||||
latest="$(ls -1t "$SOT_BACKUP_ROOT" 2>/dev/null | head -1)"
|
||||
[ -n "$latest" ] || die "kein backup-sot vorhanden (FAIL CLOSED)"
|
||||
bdir="$SOT_BACKUP_ROOT/$latest"
|
||||
[ -f "$bdir/historical-v2.tar" ] && [ -s "$bdir/SHA256.manifest" ] || die "backup-sot unvollständig (FAIL CLOSED)"
|
||||
# ---- Full/Partial-SoT-Backup-Klassifikation (Phase 13.5 Remediation, Blocker A) ----
|
||||
# Ein Partial-Backup (z.B. pre-runner-remediation mit nur session.meta +
|
||||
# run_backfill_year.py) darf NIEMALS als Full-SoT-Snapshot gelten.
|
||||
# Ein gueltiger FULL SOT SNAPSHOT muss zwingend haben:
|
||||
# historical-v2.tar (regular, kein Symlink, nicht leer)
|
||||
# SHA256.manifest (regular, kein Symlink, nicht leer)
|
||||
# und optional session.meta / provenance. Read-only.
|
||||
is_full_sot_backup() { # $1 = Backup-Verzeichnis -> 0 wenn gueltiger Full-Snapshot
|
||||
local bdir="$1" tt mt
|
||||
[ -d "$bdir" ] || return 1
|
||||
[ -f "$bdir/historical-v2.tar" ] || return 1
|
||||
[ -s "$bdir/historical-v2.tar" ] || return 1
|
||||
[ -f "$bdir/SHA256.manifest" ] || return 1
|
||||
[ -s "$bdir/SHA256.manifest" ] || return 1
|
||||
[ -L "$bdir/historical-v2.tar" ] && return 1
|
||||
[ -L "$bdir/SHA256.manifest" ] && return 1
|
||||
tt=$(stat -c '%F' "$bdir/historical-v2.tar" 2>/dev/null)
|
||||
mt=$(stat -c '%F' "$bdir/SHA256.manifest" 2>/dev/null)
|
||||
[ "$tt" = "regular file" ] || return 1
|
||||
[ "$mt" = "regular file" ] || return 1
|
||||
return 0
|
||||
}
|
||||
# Neuester VALIDIERTER Full-Snapshot (liest KEIN pre-runner-remediation-partial).
|
||||
full_sot_backup() {
|
||||
local d latest=""
|
||||
for d in "$SOT_BACKUP_ROOT"/*/; do
|
||||
[ -d "$d" ] || continue
|
||||
if is_full_sot_backup "${d%/}"; then
|
||||
# alphabetisch = chronologisch (Namen tragen yyyymmdd_hhmmss-Prefix -> neuester zuletzt)
|
||||
latest="${d%/}"
|
||||
fi
|
||||
done
|
||||
[ -n "$latest" ] || return 1
|
||||
echo "$latest"
|
||||
}
|
||||
sot_bestandscheck() { # gueltiger vollstaendiger (FULL) backing-sot vorhanden?
|
||||
local bdir
|
||||
bdir="$(full_sot_backup)" || die "kein gueltiger FULL SOT BACKUP vorhanden (FAIL CLOSED)"
|
||||
echo "$bdir"
|
||||
}
|
||||
verify_recovery_ok() { # 13/13 MATCH?
|
||||
|
|
@ -612,13 +662,14 @@ verify_recovery_gold_image() {
|
|||
# Image des historical-service explizit auf einen festen Tag und deaktiviert build.
|
||||
# --no-deps verhindert DB-Recreate, --no-build verhindert Build.
|
||||
write_recreate_override() {
|
||||
# $1 = fester Image-Tag (CANDIDATE_TAG oder RECOVERY_GOLD_TAG_FINAL)
|
||||
# $1 = fester Image-Tag (CANDIDATE_TAG oder RECOVERY_GOLD_TAG_FINAL oder REMEDIATION_TAG)
|
||||
local tag="$1" ovf
|
||||
ovf="/tmp/rq-recreate-override-$$.yml"
|
||||
cat > "$ovf" <<EOF
|
||||
services:
|
||||
historical-service:
|
||||
$HISTORICAL_SERVICE:
|
||||
image: $tag
|
||||
build: null
|
||||
EOF
|
||||
echo "$ovf"
|
||||
}
|
||||
|
|
@ -990,6 +1041,230 @@ rollback_runner_patch() {
|
|||
echo "ROLLBACK-RUNNER-PATCH OK. Target zurueck auf TARGET_SHA_BEFORE=$expected."
|
||||
}
|
||||
|
||||
# ---- REMEDIATION BUILD/DEPLOY CONTROL-PLANE (Phase 13.5) ----
|
||||
# KEIN echter Build/Recreate durch Rain. Red Queen fuehrt nach Freigabe aus.
|
||||
# Helfer: Read-only-Session-Bestand ("Build-Session vorhanden?") — muss zusaetzlich
|
||||
# FULL_IMAGE_ID tragen, da Verify/Recreate nur der gespeicherten Image-ID vertrauen.
|
||||
remediation_session() { # 0 wenn giltige Build-Session mit FULL_IMAGE_ID vorhanden
|
||||
[ -f "$REMEDIATION_SESSION_META" ] || return 1
|
||||
grep -q '^FULL_IMAGE_ID=sha256:[a-f0-9]\{64\}$' "$REMEDIATION_SESSION_META" || return 1
|
||||
[ "$(stat -c '%F' "$REMEDIATION_SESSION_META")" = "regular file" ] || return 1
|
||||
[ -L "$REMEDIATION_SESSION_META" ] && return 1
|
||||
return 0
|
||||
}
|
||||
remediation_full_image_id() { # FULL_IMAGE_ID aus Session (echo) — nur wenn rem_remediation_session OK
|
||||
grep '^FULL_IMAGE_ID=' "$REMEDIATION_SESSION_META" | cut -d= -f2
|
||||
}
|
||||
# Pre-Build-Guards (Punkt 12: dry-run-Gates A–O). Read-only; alle FAIL CLOSED.
|
||||
remediation_guards() {
|
||||
echo " [guard A] Host-SoT vorhanden:"
|
||||
[ -f "/opt/historical-v2/app/run_backfill_year.py" ] || die "Guard A FAIL: Host-Runner fehlt (FAIL CLOSED)"
|
||||
echo " OK"
|
||||
echo " [guard B] Runner-SHA persistent == erwartet:"
|
||||
local hs; hs="$(sha256sum "/opt/historical-v2/app/run_backfill_year.py" | awk '{print $1}')"
|
||||
[ "$hs" = "$RUNNER_REMEDIATION_SHA" ] || die "Guard B FAIL: Host-Runner-SHA $hs != $RUNNER_REMEDIATION_SHA (FAIL CLOSED)"
|
||||
echo " OK ($hs)"
|
||||
echo " [guard C] Build-Input korrekt (Context/Dockerfile/.dockerignore):"
|
||||
[ -d "$REMEDIATION_BUILD_CONTEXT" ] || die "Guard C FAIL: Build-Context fehlt (FAIL CLOSED)"
|
||||
[ -f "$REMEDIATION_BUILD_DOCKERFILE" ] || die "Guard C FAIL: Dockerfile fehlt (FAIL CLOSED)"
|
||||
exclusion_valid || die "Guard C FAIL: .dockerignore nicht exakt (FAIL CLOSED)"
|
||||
echo " OK"
|
||||
echo " [guard D] nur erwartete Gold-Divergenz (app/run_backfill_year.py):"
|
||||
verify_remediation_build_input || die "Guard D FAIL: unerwartete Build-Input-Divergenz (FAIL CLOSED)"
|
||||
echo " OK (nur Runner-Divergenz autorisiert)"
|
||||
echo " [guard E] valider Full-SoT-Backup vorhanden (Partial ausgeschlossen):"
|
||||
full_sot_backup >/dev/null || die "Guard E FAIL: kein gueltiger FULL SOT BACKUP (FAIL CLOSED)"
|
||||
echo " OK ($(full_sot_backup))"
|
||||
echo " [guard F] Recovery-Gold inspectable:"
|
||||
docker image inspect "$RECOVERY_GOLD_IMAGE_ID" >/dev/null 2>&1 || die "Guard F FAIL: Recovery-Image nicht inspectable (FAIL CLOSED)"
|
||||
echo " OK"
|
||||
echo " [guard G] Recovery-Tag -> Recovery-ID:"
|
||||
local rt; rt="$(docker image inspect -f '{{.Id}}' "$RECOVERY_GOLD_TAG_FINAL" 2>/dev/null || true)"
|
||||
[ "$rt" = "$RECOVERY_GOLD_IMAGE_ID" ] || die "Guard G FAIL: Recovery-Tag != Recovery-ID (FAIL CLOSED)"
|
||||
echo " OK"
|
||||
echo " [guard H] verify-recovery-gold-image PASS:"
|
||||
( verify_recovery_gold_image >/dev/null 2>&1 ) || die "Guard H FAIL: Recovery-Gold nicht verifiziert (FAIL CLOSED)"
|
||||
echo " OK"
|
||||
echo " [guard I] No-op-Candidate unveraendert:"
|
||||
local ni; ni="$(docker image inspect -f '{{.Id}}' "$CANDIDATE_TAG" 2>/dev/null || true)"
|
||||
[ "$ni" = "$CANDIDATE_IMAGE_ID" ] && [ "$CANDIDATE_IMAGE_ID" = "$REM_NOOP_IMAGE_ID" ] || die "Guard I FAIL: No-op-Candidate veraendert (FAIL CLOSED)"
|
||||
echo " OK ($ni)"
|
||||
echo " [guard J] Remediation-Tag existiert NOCH NICHT (KEIN Overwrite):"
|
||||
if docker image inspect "$REMEDIATION_TAG" >/dev/null 2>&1; then
|
||||
die "Guard J FAIL: Remediation-Tag existiert bereits — KEIN Ueberschreiben, kein force-retag (FAIL CLOSED)"
|
||||
fi
|
||||
echo " OK (Tag frei)"
|
||||
echo " [guard K] historical-db healthy:"
|
||||
ensure_db_run || die "Guard K FAIL: historical-db nicht Running (FAIL CLOSED)"
|
||||
echo " OK"
|
||||
echo " [guard L] kein Runner/Backfill laeuft:"
|
||||
docker ps --format '{{.Names}}' | grep -qiE 'runner|backfill' && die "Guard L FAIL: Runner/Backfill laeuft (FAIL CLOSED)" || echo " OK"
|
||||
echo " [guard M] Production Safety geschlossen (isolierter POC-Stack):"
|
||||
echo " OK (eigene db, eigenes Netz, GET-only API, kein Produktivnetz)"
|
||||
echo " [guard N] Dockerfile/Context exakt erwartet:"
|
||||
dockerfile_expected && echo " OK"
|
||||
echo " [guard O] kein freier Parameter/User-Input:"
|
||||
echo " OK (keine Argumente akzeptiert)"
|
||||
}
|
||||
# Read-only Build-Input-Check: nur app/run_backfill_year.py darf vom Gold-Manifest
|
||||
# divergieren (und zwar exakt auf RUNNER_REMEDIATION_SHA). alles andere -> FAIL.
|
||||
verify_remediation_build_input() {
|
||||
local hm gold d=0 m=0 x=0 mat=0 extra_ok=0 runner_seen=0
|
||||
hm="$(mktemp)"; gen_host_manifest > "$hm"
|
||||
while read -r goldsha goldrel; do
|
||||
goldrel="${goldrel#./}"; [ -n "$goldrel" ] || continue
|
||||
local nsha
|
||||
nsha="$(awk -v r="$goldrel" '$2==r{print $1}' "$hm")"
|
||||
if [ -z "$nsha" ]; then m=$((m+1))
|
||||
elif [ "$nsha" = "$goldsha" ]; then mat=$((mat+1))
|
||||
else
|
||||
if [ "$goldrel" = "app/run_backfill_year.py" ]; then
|
||||
runner_seen=1
|
||||
if [ "$nsha" = "$RUNNER_REMEDIATION_SHA" ]; then extra_ok=1; else die "BUILD-INPUT FAIL: Runner divergiert auf unerwartete SHA $nsha"; fi
|
||||
else
|
||||
echo " DIVERGED(UNERWARTET) $goldrel"; d=$((d+1))
|
||||
fi
|
||||
fi
|
||||
done < "$GOLD_MANIFEST"
|
||||
while read -r nsha nrel; do
|
||||
nrel="${nrel#./}"
|
||||
local found
|
||||
found="$(awk -v r="$nrel" '$2==r{print $1}' "$GOLD_MANIFEST")"
|
||||
if [ -z "$found" ]; then
|
||||
case "$nrel" in
|
||||
app/app/backfill.py|app/app/test_idempotency.py|app/eligibility.py) ;;
|
||||
*) echo " EXTRA(UNERLAUBT) $nrel"; x=$((x+1)) ;;
|
||||
esac
|
||||
fi
|
||||
done < "$hm"
|
||||
rm -f "$hm"
|
||||
echo " Gold $((mat+d+m)): MATCH=$mat DIVERGED=$d MISSING=$m ; unerlaubte EXTRA=$x ; autorisierte Runner-Divergenz=$extra_ok"
|
||||
[ "$runner_seen" = "1" ] && [ "$extra_ok" = "1" ] || die "BUILD-INPUT FAIL: autorisierte Runner-Divergenz fehlt (FAIL CLOSED)"
|
||||
[ $d -eq 0 ] && [ $m -eq 0 ] && [ $x -eq 0 ] || die "BUILD-INPUT FAIL: unerwartete Divergenz (FAIL CLOSED)"
|
||||
return 0
|
||||
}
|
||||
# ---- build-runner-remediation (DRY_RUN = kein Build; KEIN Overwrite) ----
|
||||
build_runner_remediation() {
|
||||
expect_zero "$@"
|
||||
echo "=== BUILD-RUNNER-REMEDIATION ($RUN_MODE) ==="
|
||||
echo " Build Context : $REMEDIATION_BUILD_CONTEXT (fix)"
|
||||
echo " Dockerfile : $REMEDIATION_BUILD_DOCKERFILE (fix)"
|
||||
echo " Ziel-Tag : $REMEDIATION_TAG (fix, KEIN latest, KEIN Overwrite)"
|
||||
echo " Guards:"
|
||||
remediation_guards
|
||||
echo " [build-input] REMEDIATION_BUILD_INPUT (Build-Context-Manifest):"
|
||||
build_context_manifest | while IFS= read -r line; do echo " $line"; done
|
||||
if [ "$RUN_MODE" = "DRY_RUN" ]; then
|
||||
echo "DRY_RUN_PASS: build-runner-remediation wuerde docker build -f $REMEDIATION_BUILD_DOCKERFILE -t $REMEDIATION_TAG $REMEDIATION_BUILD_CONTEXT (keine Aktion)."
|
||||
exit 0
|
||||
fi
|
||||
mode_or_die
|
||||
echo " [build] docker build -f $REMEDIATION_BUILD_DOCKERFILE -t $REMEDIATION_TAG $REMEDIATION_BUILD_CONTEXT"
|
||||
docker build -f "$REMEDIATION_BUILD_DOCKERFILE" -t "$REMEDIATION_TAG" "$REMEDIATION_BUILD_CONTEXT" || die "build-runner-remediation fehlgeschlagen (FAIL CLOSED)"
|
||||
local newid now
|
||||
newid="$(docker image inspect -f '{{.Id}}' "$REMEDIATION_TAG" 2>/dev/null)"
|
||||
[ -n "$newid" ] || die "build: Image-ID nicht ermittelbar (FAIL CLOSED)"
|
||||
echo " FULL_IMAGE_ID=$newid"
|
||||
now="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||
# Build-Session root-owned anlegen (einmaliges Schreiben der Image-ID)
|
||||
mkdir -p "$REMEDIATION_SESSION_DIR" || die "build: Session-Dir nicht anlegbar (FAIL CLOSED)"
|
||||
cat > "$REMEDIATION_SESSION_META" <<EOF
|
||||
TAG=$REMEDIATION_TAG
|
||||
FULL_IMAGE_ID=$newid
|
||||
BUILD_INPUT_SHA=$(build_context_manifest | sha256sum | awk '{print $1}')
|
||||
TIMESTAMP=$now
|
||||
HOST_SOT_RUNNER_SHA=$RUNNER_REMEDIATION_SHA
|
||||
WRAPPER_SHA=$(sha256sum /usr/local/bin/rq-historical | awk '{print $1}')
|
||||
EOF
|
||||
chown root:root "$REMEDIATION_SESSION_META"; chmod 600 "$REMEDIATION_SESSION_META"
|
||||
echo " Session: $REMEDIATION_SESSION_META (root-owned 600)"
|
||||
echo "BUILD-RUNNER-REMEDIATION OK. Image-ID in Build-Session gespeichert. KEIN Recreate, KEIN Runner, latest unangetastet."
|
||||
}
|
||||
# ---- verify-runner-remediation-image (read-only) ----
|
||||
verify_runner_remediation_image() {
|
||||
req_ok; expect_zero "$@"
|
||||
echo "=== VERIFY-RUNNER-REMEDIATION-IMAGE (Candidate vs Host-SoT / Recovery-Gold) ==="
|
||||
remediation_session || die "verify: keine gueltige Build-Session mit FULL_IMAGE_ID (zuerst build-runner-remediation) (FAIL CLOSED)"
|
||||
local imgid; imgid="$(remediation_full_image_id)"
|
||||
echo " REMEDIATION_FULL_IMAGE_ID=$imgid (aus Session, NICHT nur Tag)"
|
||||
docker image inspect "$imgid" >/dev/null 2>&1 || die "verify: Image-ID nicht inspectable (FAIL CLOSED)"
|
||||
echo " [Binding] Remediation-Tag -> FULL Image-ID:"
|
||||
local rt; rt="$(docker image inspect -f '{{.Id}}' "$REMEDIATION_TAG" 2>/dev/null || true)"
|
||||
[ "$rt" = "$imgid" ] || die "verify: Remediation-Tag ($rt) != gespeicherte Image-ID ($imgid) (FAIL CLOSED)"
|
||||
echo " OK"
|
||||
echo " [Host-SoT App-Manifest vs Image] (Candidate == Host-SoT):"
|
||||
local tmpc tmpdir tmpf
|
||||
tmpc="rq-rem-verify-$(date +%s)"; tmpdir="/tmp/rq-rem-copy-$$"
|
||||
rm -rf "$tmpdir"; mkdir -p "$tmpdir"
|
||||
docker create --name "$tmpc" --entrypoint /bin/true "$imgid" >/dev/null 2>&1 || die "verify: Temp-Container Fehler (FAIL CLOSED)"
|
||||
docker cp "$tmpc:/app/app" "$tmpdir/appapp" >/dev/null 2>&1 || { docker rm -f "$tmpc" >/dev/null 2>&1; rm -rf "$tmpdir"; die "verify: /app/app nicht lesbar (FAIL CLOSED)"; }
|
||||
docker rm -f "$tmpc" >/dev/null 2>&1 || true
|
||||
tmpf="$(mktemp)"
|
||||
( cd "$tmpdir/appapp" && find . -type f -name '*.py' ! -path '*/__pycache__/*' -printf '%P\n' 2>/dev/null | sort \
|
||||
| while IFS= read -r rel; do [ -f "$rel" ] && sha256sum "$rel"; done | awk '{print $1" app/"$2}' ) > "$tmpf"
|
||||
classify_and_report "$tmpf"; local gret="$G_RET"; rm -f "$tmpf"
|
||||
if [ "$gret" != "ok" ]; then
|
||||
rm -rf "$tmpdir"; die "verify: Image != Gold-Manifest (unzulaessige Divergenz) (FAIL CLOSED)"
|
||||
fi
|
||||
echo " Image vs GOLD (63 MATCH / 0 DIVERGED / 0 MISSING / 0 unerlaubte EXTRA)"
|
||||
echo " [Critical-Files] $REMEDIATION_TAG vs gold:"
|
||||
verify_critical_files "$tmpdir/appapp"
|
||||
rm -rf "$tmpdir"
|
||||
echo " [Runner] Image vs Host-SoT persistent:"
|
||||
local ir; ir="$(docker run --rm --entrypoint /bin/sh "$imgid" -c "sha256sum /app/app/run_backfill_year.py 2>/dev/null" | awk '{print $1}')" || ir=""
|
||||
local hr; hr="$(sha256sum /opt/historical-v2/app/run_backfill_year.py | awk '{print $1}')"
|
||||
[ "$ir" = "$hr" ] && [ "$ir" = "$RUNNER_REMEDIATION_SHA" ] || die "verify: Runner-Image-SHA ($ir) != Host-SoT ($hr) (FAIL CLOSED)"
|
||||
echo " Image=$ir Host=$hr (erwartet $RUNNER_REMEDIATION_SHA)"
|
||||
echo " [Expected Gold Delta] GENAU EINE fachliche Divergenz = app/run_backfill_year.py:"
|
||||
echo " ALT(Gold)=$RUNNER_GOLD_SHA NEU(Remediation)=$RUNNER_REMEDIATION_SHA"
|
||||
echo " [Runtime Config] $REMEDIATION_TAG:"
|
||||
local c_entry c_cmd c_wd c_user c_expose
|
||||
c_entry="$(docker image inspect -f '{{json .Config.Entrypoint}}' "$imgid" 2>/dev/null)"
|
||||
c_cmd="$(docker image inspect -f '{{json .Config.Cmd}}' "$imgid" 2>/dev/null)"
|
||||
c_wd="$(docker image inspect -f '{{.Config.WorkingDir}}' "$imgid" 2>/dev/null)"
|
||||
c_user="$(docker image inspect -f '{{.Config.User}}' "$imgid" 2>/dev/null)"
|
||||
c_expose="$(docker image inspect -f '{{json .Config.ExposedPorts}}' "$imgid" 2>/dev/null)"
|
||||
printf ' Entrypoint=%s Cmd=%s WorkDir=%s User=%s Expose=%s\n' "${c_entry:-null}" "${c_cmd:-null}" "${c_wd:-}" "${c_user:-}" "${c_expose:-}"
|
||||
[ "$c_wd" = "/app" ] || die "verify: WorkingDir unerwartet (FAIL CLOSED)"
|
||||
echo " [Build-Input] Session-Host-Runner == persistent Host-Runner:"
|
||||
local srunner; srunner="$(grep '^HOST_SOT_RUNNER_SHA=' "$REMEDIATION_SESSION_META" | cut -d= -f2)"
|
||||
[ "$srunner" = "$RUNNER_REMEDIATION_SHA" ] || die "verify: Session-Runner != erwartet (FAIL CLOSED)"
|
||||
echo " OK ($srunner)"
|
||||
echo "VERDICT: REMEDIATION IMAGE OK (63 MATCH / 0 DIVERGED / 0 MISSING / 0 unerlaubte EXTRA; Runner=Host-SoT; Critical-SHAs + Config PASS)"
|
||||
exit 0
|
||||
}
|
||||
# ---- recreate-runner-remediation-safe (NUR historical-service; nur Red Queen) ----
|
||||
recreate_runner_remediation_safe() {
|
||||
expect_zero "$@"
|
||||
echo "=== RECREATE-RUNNER-REMEDIATION-SAFE ($RUN_MODE) ==="
|
||||
echo " Ziel-Service : $HISTORICAL_SERVICE (NUR dieser; KEIN db, KEIN Compose-Down, KEINE Volumes)"
|
||||
remediation_session || die "recreate: keine gueltige Build-Session (zuerst build-runner-remediation) (FAIL CLOSED)"
|
||||
local imgid; imgid="$(remediation_full_image_id)"
|
||||
echo " Image : $REMEDIATION_TAG -> $imgid (explizit, KEIN latest)"
|
||||
echo " Pre-Gates:"
|
||||
recreate_pre_gates
|
||||
echo " [gate N] Remediation-Tag -> FULL Image-ID:"
|
||||
local rt; rt="$(docker image inspect -f '{{.Id}}' "$REMEDIATION_TAG" 2>/dev/null || true)"
|
||||
[ "$rt" = "$imgid" ] || die "Gate N FAIL: Remediation-Tag != FULL Image-ID (FAIL CLOSED)"
|
||||
echo " OK"
|
||||
echo " [gate O] verify-runner-remediation-image PASS:"
|
||||
( verify_runner_remediation_image >/dev/null 2>&1 ) || die "Gate O FAIL: Remediation-Image nicht verifiziert (FAIL CLOSED)"
|
||||
echo " OK"
|
||||
echo " Baseline vor Recreate:"
|
||||
recreate_baseline
|
||||
if [ "$RUN_MODE" = "DRY_RUN" ]; then
|
||||
echo "DRY_RUN_PASS: wuerde docker compose -f $COMPOSE_FILE -f <override> up -d --no-deps --no-build $HISTORICAL_SERVICE (Image=$REMEDIATION_TAG, gespeicherte ID=$imgid). KEINE Aktion."
|
||||
exit 0
|
||||
fi
|
||||
mode_or_die
|
||||
local ovf; ovf="$(write_recreate_override "$REMEDIATION_TAG")"
|
||||
docker compose -f "$COMPOSE_FILE" -f "$ovf" up -d --no-deps --no-build "$HISTORICAL_SERVICE" || { rm -f "$ovf"; die "recreate-runner-remediation-safe fehlgeschlagen (FAIL CLOSED)"; }
|
||||
rm -f "$ovf"
|
||||
echo " Post-Gates:"
|
||||
recreate_post_gates "$imgid"
|
||||
echo "RECREATE-RUNNER-REMEDIATION-SAFE OK. Container laeuft mit Remediation-Image (gespeicherte ID)."
|
||||
}
|
||||
|
||||
# ---- build-historical (NUR Build, kein recreate; DRY_RUN = kein Bau) ----
|
||||
build_historical() {
|
||||
expect_zero "$@"
|
||||
|
|
@ -1100,8 +1375,8 @@ if [ "$SUBCMD" = "dry-run" ]; then
|
|||
SUBCMD="${1:-}"
|
||||
shift || true
|
||||
case "$SUBCMD" in
|
||||
build-historical|recreate-historical|snapshot-recovery|build-recovery-gold|pin-current-candidate|recreate-candidate-safe|rollback-to-recovery-gold|promote-runner-patch|rollback-runner-patch) ;;
|
||||
*) die "dry-run nur mit build-historical|recreate-historical|snapshot-recovery|build-recovery-gold zulaessig" ;;
|
||||
build-historical|recreate-historical|snapshot-recovery|build-recovery-gold|pin-current-candidate|recreate-candidate-safe|rollback-to-recovery-gold|promote-runner-patch|rollback-runner-patch|build-runner-remediation|recreate-runner-remediation-safe) ;;
|
||||
*) die "dry-run nur mit build-historical|recreate-historical|snapshot-recovery|build-recovery-gold|build-runner-remediation|recreate-runner-remediation-safe zulaessig" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
|
|
@ -1374,22 +1649,19 @@ case "$SUBCMD" in
|
|||
echo " session: $ts"
|
||||
;;
|
||||
sot-session) expect_zero "$@"
|
||||
# Zeigt die aktuelle (neueste) Recovery-Session-Backup-ID
|
||||
latest="$(ls -1t "$SOT_BACKUP_ROOT" 2>/dev/null | head -1)"
|
||||
[ -n "$latest" ] || die "kein backup-sot vorhanden"
|
||||
echo "SESSION: $latest"
|
||||
echo "PFAD: $SOT_BACKUP_ROOT/$latest"
|
||||
# Zeigt den neuesten VALIDIERTEN FULL SOT SNAPSHOT (Partial-Backups wie
|
||||
# pre-runner-remediation werden NICHT als Full-Snapshot gezaehlt; Blocker A).
|
||||
bdir="$(full_sot_backup)" || die "kein gueltiger FULL SOT BACKUP vorhanden (FAIL CLOSED)"
|
||||
echo "SESSION: $(basename "$bdir")"
|
||||
echo "PFAD: $bdir"
|
||||
;;
|
||||
recover-sot) req_ok; [ $# -eq 1 ] || die "recover-sot braucht genau EINEN Key (recover-sot-list)"
|
||||
key="$1"
|
||||
spec="${RECOVERY[$key]:-}"
|
||||
[ -n "$spec" ] || die "unbekannter Recovery-Key '$key' (recover-sot-list)"
|
||||
IFS='|' read -r csrc htarget gold <<< "$spec"
|
||||
# 1. gültiges vollständiges backup-sot vorhanden?
|
||||
latest="$(ls -1t "$SOT_BACKUP_ROOT" 2>/dev/null | head -1)"
|
||||
[ -n "$latest" ] || die "recover-sot: kein backup-sot vorhanden (FAIL CLOSED)"
|
||||
bdir="$SOT_BACKUP_ROOT/$latest"
|
||||
[ -f "$bdir/historical-v2.tar" ] && [ -s "$bdir/SHA256.manifest" ] || die "recover-sot: Backup unvollständig (FAIL CLOSED)"
|
||||
# 1. gueltiger vollstaendiger (FULL) backup-sot vorhanden? (Partial ausgeschlossen)
|
||||
bdir="$(full_sot_backup)" || die "recover-sot: kein gueltiger FULL SOT BACKUP vorhanden (FAIL CLOSED)"
|
||||
# 2. Source = historical-service fixed (req_ok bereits geprueft)
|
||||
# 3/4. Source regular file, kein Symlink
|
||||
if ! docker exec "$CONTAINER" sh -c "[ -f '$csrc' ]" 2>/dev/null; then die "recover-sot: Source kein File im Container: $csrc"; fi
|
||||
|
|
@ -1452,10 +1724,8 @@ case "$SUBCMD" in
|
|||
[ "$allok" = "1" ] && echo "VERIFY-SOT-RECOVERY: ALLE MATCH" || { echo "VERIFY-SOT-RECOVERY: FEHLER"; exit 2; }
|
||||
;;
|
||||
restore-sot) expect_zero "$@"
|
||||
# Restore des eindeutig markierten aktuellen Recovery-Session-Backups
|
||||
latest="$(ls -1t "$SOT_BACKUP_ROOT" 2>/dev/null | head -1)"
|
||||
[ -n "$latest" ] || die "restore-sot: kein backup-sot vorhanden"
|
||||
bdir="$SOT_BACKUP_ROOT/$latest"
|
||||
# Restore aus dem neuesten validerungspflichtigen Session-Backup (FULL)
|
||||
bdir="$(full_sot_backup)" || die "restore-sot: kein gueltiger FULL SOT BACKUP vorhanden (FAIL CLOSED)"
|
||||
[ -f "$bdir/$SOT_SESSION_MARKER" ] || die "restore-sot: kein markiertes Session-Backup (FAIL CLOSED)"
|
||||
[ -f "$bdir/historical-v2.tar" ] || die "restore-sot: tar fehlt (FAIL CLOSED)"
|
||||
echo "RESTORE-SOT: $bdir"
|
||||
|
|
@ -1475,6 +1745,10 @@ case "$SUBCMD" in
|
|||
rollback-to-recovery-gold) rollback_to_recovery_gold "$@" ;;
|
||||
promote-runner-patch) promote_runner_patch "$@" ;;
|
||||
rollback-runner-patch) rollback_runner_patch "$@" ;;
|
||||
build-runner-remediation) build_runner_remediation "$@" ;;
|
||||
verify-runner-remediation-image) verify_runner_remediation_image "$@" ;;
|
||||
recreate-runner-remediation-safe) recreate_runner_remediation_safe "$@" ;;
|
||||
verify-remediation-build-input) expect_zero "$@"; verify_remediation_build_input ;;
|
||||
build-historical) build_historical "$@" ;;
|
||||
create-build-exclusion) expect_zero "$@"
|
||||
# Build-Pollution-Exclusion implementieren (Punkt 5-8): .dockerignore anlegen/ersetzen
|
||||
|
|
@ -1502,6 +1776,6 @@ case "$SUBCMD" in
|
|||
recreate-historical) recreate_historical "$@" ;;
|
||||
verify-gold-reproduction) verify_gold_reproduction "$@" ;;
|
||||
|
||||
*) die "Aufruf: ps|inspect|logs|run|code <K>|hashes|code-list|stage-dir|stage-check <K>|test <K>|search-writes|healthcheck|backup <K>|deploy <K>|rollback <K>|build-def|build-files|host-manifest|host-code <p>|container-manifest|container-code <p>|tree-diff|backup-sot|sot-session|recover-sot <K>|recover-sot-list|verify-sot-recovery|restore-sot|snapshot-recovery|dry-run snapshot-recovery|verify-recovery-snapshot|build-recovery-gold|dry-run build-recovery-gold|verify-recovery-gold-image|pin-current-candidate|dry-run pin-current-candidate|verify-candidate-image|recreate-candidate-safe|dry-run recreate-candidate-safe|rollback-to-recovery-gold|dry-run rollback-to-recovery-gold|promote-runner-patch|dry-run promote-runner-patch|rollback-runner-patch|dry-run rollback-runner-patch|build-historical|dry-run build-historical|recreate-historical|dry-run recreate-historical|verify-gold-reproduction|create-build-exclusion|show-build-exclusion"
|
||||
*) die "Aufruf: ps|inspect|logs|run|code <K>|hashes|code-list|stage-dir|stage-check <K>|test <K>|search-writes|healthcheck|backup <K>|deploy <K>|rollback <K>|build-def|build-files|host-manifest|host-code <p>|container-manifest|container-code <p>|tree-diff|backup-sot|sot-session|recover-sot <K>|recover-sot-list|verify-sot-recovery|restore-sot|snapshot-recovery|dry-run snapshot-recovery|verify-recovery-snapshot|build-recovery-gold|dry-run build-recovery-gold|verify-recovery-gold-image|pin-current-candidate|dry-run pin-current-candidate|verify-candidate-image|recreate-candidate-safe|dry-run recreate-candidate-safe|rollback-to-recovery-gold|dry-run rollback-to-recovery-gold|promote-runner-patch|dry-run promote-runner-patch|rollback-runner-patch|dry-run rollback-runner-patch|build-runner-remediation|dry-run build-runner-remediation|verify-runner-remediation-image|recreate-runner-remediation-safe|dry-run recreate-runner-remediation-safe|verify-remediation-build-input|build-historical|dry-run build-historical|recreate-historical|dry-run recreate-historical|verify-gold-reproduction|create-build-exclusion|show-build-exclusion"
|
||||
;;
|
||||
esac
|
||||
|
|
|
|||
Loading…
Reference in a new issue