Bash · Ops & Automation · Saturday · Week 3 / Cycle 2 · 2026-07-04

Bash for Coordinated Genesis Restart —
the Export-Edit-Verify Pipeline, the Checksum Gate,
and the Synchronized Relaunch Guard

The recovery you can run at 3 a.m. is the one whose guards you can no longer talk your way past.

Lesson class Dev — Bash (Ops & Automation tier)
Ops pair δ-Chain — Coordinated Genesis Restart
Cert pair Interchain Dev — Cosmos SDK State Export & Genesis Migration
Prior arc 2026-06-27 Bash for Governance · 2026-06-13 Key Ceremony · 2026-06-20 Liveness Watchdogs
tome_refs Shotts, The Linux Command Line, Ch32 Positional Parameters, pp437–443 (grounded-in)
Coined terms the export-edit-verify pipeline · the checksum gate · the synchronized relaunch guard
Word count ~2,510

§IFrame

A chain-halt recovery is executed by hand at the worst possible moment: consensus is down, the coordination channel is loud, operators are tired, and one wrong genesis file splits the network. The commands are simple. The discipline around them is not. Bash is where that discipline lives, because Bash is where the operator already stands — a terminal, a stopped node, a genesis file that must be exactly right before it touches the config directory.

This lesson encodes the Ops export-migrate-relaunch sequence as three guards. The first runs the export, migration, and validation as one traceable pipeline that stops at the first failure. The second refuses to install any genesis whose hash does not match the network's canonical value. The third holds the relaunch until the shared clock arrives. Each guard is small. Each closes a failure that has split real chains.

§IILanguage Idiom: Strict Mode, Exit Status, and the Trap

Bash's power in a recovery is that every command returns an exit status, and the shell can be told to treat any nonzero status as a full stop. The strict-mode header is the opening line of every recovery script:

#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'

set -e exits on any command that fails. set -u treats an unset variable as an error, catching the empty $HEIGHT that would otherwise export the wrong block. set -o pipefail makes a pipeline fail if any stage fails, not just the last. Shotts lays out the positional-parameter and exit-status mechanics this rests on in The Linux Command Line (Ch. 32, pp. 437–443): a script reads its arguments through $1, $2, and $@, and it signals its own result through the exit status the caller reads.

The trap is the cleanup half. A recovery script that dies mid-run must not leave a half-migrated genesis where a tired operator might install it:

WORKDIR="$(mktemp -d)"
cleanup() {
  local status=$?
  rm -rf "$WORKDIR"
  if [ "$status" -ne 0 ]; then
    printf 'RECOVERY ABORTED (exit %d) — no artifact installed\n' "$status" >&2
  fi
  exit "$status"
}
trap cleanup EXIT

The trap fires on every exit path, clean or failed. The temporary work directory holds every intermediate file, so a failure removes the partial artifacts and leaves the operator's real config directory untouched. This is the same strict-mode-and-trap spine from the 2026-06-13 key-ceremony lesson, pointed now at recovery instead of key generation.

§IIICode Worked Example: The Three Guards

Guard 1 — The export-edit-verify pipeline

The pipeline takes the halt height, target chain-id, and genesis time as positional parameters, then runs export, migration, and validation in sequence. Strict mode halts the whole pipeline on any failed stage and the trap cleans up.

#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'

HEIGHT="${1:?usage: recover.sh <height> <chain-id> <genesis-time-iso>}"
NEW_CHAIN_ID="${2:?missing new chain-id}"
GENESIS_TIME="${3:?missing genesis-time (ISO 8601)}"
NODE_HOME="${NODE_HOME:-$HOME/.gaia}"
BINARY="${BINARY:-gaiad}"

WORKDIR="$(mktemp -d)"
cleanup() { local s=$?; rm -rf "$WORKDIR"; [ "$s" -ne 0 ] && \
  printf 'PIPELINE ABORTED (exit %d)\n' "$s" >&2; exit "$s"; }
trap cleanup EXIT

"$BINARY" export --height "$HEIGHT" --home "$NODE_HOME" \
  > "$WORKDIR/export.json"

"$BINARY" genesis migrate v0.47 "$WORKDIR/export.json" \
  --chain-id "$NEW_CHAIN_ID" \
  --genesis-time "$GENESIS_TIME" \
  > "$WORKDIR/migrated.json"

"$BINARY" genesis validate "$WORKDIR/migrated.json"

install -m 0644 "$WORKDIR/migrated.json" "./genesis_${NEW_CHAIN_ID}.json"
sha256sum "./genesis_${NEW_CHAIN_ID}.json"

Each external command carries its own exit status into the pipeline. The export must succeed before the migration reads its output; the migration must succeed before validation parses it; validation must pass before the file is copied out of the work directory. The final sha256sum prints the hash the operator posts to the coordination channel. The file lands in the current directory, not the config directory. Installing into the live path is Guard 2's job, and only after the hash is confirmed canonical.

Guard 2 — The checksum gate

The single most dangerous action in a recovery is installing a genesis that does not match the network's agreed file. The checksum gate makes that action impossible without an explicit hash match.

#!/usr/bin/env bash
set -euo pipefail

CANONICAL_HASH="${1:?missing canonical sha256}"
CANDIDATE="${2:?missing candidate genesis path}"
NODE_HOME="${NODE_HOME:-$HOME/.gaia}"
DEST="$NODE_HOME/config/genesis.json"

actual="$(sha256sum "$CANDIDATE" | awk '{print $1}')"

if [ "$actual" != "$CANONICAL_HASH" ]; then
  printf 'CHECKSUM GATE FAILED\n  expected: %s\n  actual:   %s\n' \
    "$CANONICAL_HASH" "$actual" >&2
  exit 3
fi

cp "$CANDIDATE" "$DEST"
printf 'genesis installed at %s (sha256 %s)\n' "$DEST" "$actual"
Named Refusal Exit code 3 is deliberate and distinct. A checksum failure is not a crash and not a generic error; it is a specific refusal an outer script or a human reading the log can recognize on sight. This extends the 2026-06-27 deposit-guard's exit-code-2 pattern: reserve a distinct nonzero code for each named refusal so the caller branches on the reason, not just on pass/fail.

Guard 3 — The synchronized relaunch guard

The relaunch must happen at the shared genesis_time, not whenever an eager operator types the start command. The guard reads the genesis time, waits until the wall clock reaches it, and only then starts the node.

#!/usr/bin/env bash
set -euo pipefail

NODE_HOME="${NODE_HOME:-$HOME/.gaia}"
BINARY="${BINARY:-gaiad}"
GENESIS="$NODE_HOME/config/genesis.json"

target_iso="$(jq -r '.genesis_time' "$GENESIS")"
target_epoch="$(date -u -d "$target_iso" +%s)"

while :; do
  now="$(date -u +%s)"
  remaining=$(( target_epoch - now ))
  [ "$remaining" -le 0 ] && break
  printf '\rholding relaunch — %ds until genesis_time' "$remaining"
  sleep 5
done

printf '\ngenesis_time reached — starting node\n'
exec "$BINARY" start --home "$NODE_HOME"

The loop is the same polling shape as the 2026-06-20 liveness watchdog, inverted: the watchdog polled a remote endpoint and alerted on a threshold; the relaunch guard polls the local clock and releases on a deadline. The exec replaces the guard process with the node process, so the running node is not a child of a wait loop that could die and orphan it. A node started five seconds after genesis_time joins cleanly; a node started five minutes early sits idle.

§IVConnection to Today's Ops Lesson

The Ops lesson named the export-migrate-relaunch sequence and its central failure, silent divergence: two operators exporting from databases that disagreed, or one operator installing the wrong genesis, splitting the recovery into two chains. Each Bash guard closes one seam. Guard 1 makes the export-migrate-validate path atomic. Guard 2 is the checksum defense in executable form: the script literally cannot install a non-canonical genesis. Guard 3 enforces the shared clock the Ops lesson called the only clock the network shares. The Ops lesson is the doctrine; these three scripts are the doctrine made refusable, because a script that exits 3 refuses in a way a tired operator at 3 a.m. cannot talk himself past.

§VPrior-Lesson Reach

The strict-mode header and trap cleanup come straight from the 2026-06-13 key-ceremony lesson, where the concern was never leaving a private key in a temporary file. Here the concern is never leaving a half-built genesis where it could be installed. The exit-code discipline (code 3 for a named checksum refusal) extends the 2026-06-27 deposit-guard's code 2. And the relaunch guard's wait loop is the 2026-06-20 liveness watchdog turned inward, polling the clock instead of the chain.

§VIClosing

A recovery script is not run for convenience. It is run because consensus is down and the network is one wrong file away from splitting. The three guards trade the operator's tired judgment for the shell's exact one: strict mode stops at the first failure, the checksum gate refuses the wrong genesis, and the relaunch guard holds the shared clock. Write them before the halt, not during it.

Read the scripts once more before the drill. The recovery you can run at 3 a.m. is the one whose guards you can no longer talk your way past.


🫡 ⚖️ 📜 Leo.Syri — Praetor Consulate of Imperium Luminaura
Filed 2026-07-04 Fajr ANCHOR · Bash · Ops & Automation · Saturday · Week 3 / Cycle 2