Cosmovisor is only zero-touch if the binary was already there. The shell is what puts it there, correctly, days early, and refuses to lie about whether it succeeded.
Today's Ops lesson made one claim central: a chain upgrade is boring only if the pre-swap staging gate was closed before the halt arrived. Closing that gate is a shell problem from end to end. You fetch a binary over the network, you verify it against a published hash, you place it at the exact path Cosmovisor expects, and you confirm Cosmovisor can see it. None of that is exotic. All of it fails silently if the script is written casually.
Bash is the right language here for the same reason it fit the 2026-07-04 genesis-restart pipeline and the 2026-06-20 liveness watchdog: the work is orchestration of existing tools around a filesystem, and the failure surface is unhandled error and non-idempotent re-runs. This lesson builds three components. The binary staging pipeline does the fetch-checksum-place. The upgrade-info watcher confirms the daemon actually recorded the plan. The idempotent pre-swap guard wraps the whole thing so an operator can run it five times without corrupting a staged binary or a live node.
The first idiom is strict mode. The header set -euo pipefail turns three classes of silent failure into loud ones. -e exits on any un-handled non-zero return. -u treats an unset variable as an error, which stops a mistyped path variable from expanding to nothing and running against /. -o pipefail makes a pipeline fail if any stage fails, not just the last, so curl ... | tar ... no longer reports success when curl handed tar an empty stream.
The second is the trap. A trap registers a handler that runs when the script exits, cleanly or on error. The Linux canon treats this as the mechanism for cleanup that must happen regardless of how a script terminates (The Linux Command Line, Chapter 36, Traps, p. 487). For a staging pipeline the trap owns the temp directory: whatever happens, the half-downloaded binary in scratch is removed, so a failed run never leaves a partial artifact a later run might mistake for a finished one.
The third is the guard clause: an early-return check that answers "is this work already done?" before doing it. If the correct binary is already staged with the correct checksum, the staging function returns immediately rather than re-downloading. Idempotence is the property that lets the same script run from cron, from a Makefile, and by hand during an incident without any invocation fighting the others.
The staging pipeline fetches a release binary, verifies it against a published SHA-256, and places it at the Cosmovisor upgrade path. The strict-mode header and the trap come first; the trap fires on any exit and removes the scratch directory, so no failed run leaves a partial download behind.
#!/usr/bin/env bash
set -euo pipefail
DAEMON="gaiad"
COSMOVISOR_ROOT="${HOME}/.gaia/cosmovisor"
WORKDIR="$(mktemp -d)"
trap 'rm -rf "${WORKDIR}"' EXIT
The next block is the guard clause. Before spending a network round-trip, the function asks whether the target binary already exists at the upgrade path and already matches the expected hash. If both hold, staging is complete and it returns zero. This is what makes the script safe to run repeatedly in the days before an upgrade.
stage_binary() {
local name="$1" url="$2" want_sha="$3"
local dest_dir="${COSMOVISOR_ROOT}/upgrades/${name}/bin"
local dest="${dest_dir}/${DAEMON}"
if [[ -x "${dest}" ]] && echo "${want_sha} ${dest}" | sha256sum --check --status; then
echo "already staged: ${name} (checksum matches)"
return 0
fi
If the guard did not short-circuit, the pipeline downloads into scratch, verifies the checksum there, and only then moves the verified binary into place. Verification happens against the scratch copy, never the destination, so an unverified download never touches the path Cosmovisor reads. Under set -e, a sha256sum --check --status mismatch aborts the run before the move.
local tmp="${WORKDIR}/${DAEMON}"
curl --fail --location --silent --show-error "${url}" -o "${tmp}"
echo "${want_sha} ${tmp}" | sha256sum --check --status
install -D -m 0755 "${tmp}" "${dest}"
echo "staged: ${name} -> ${dest}"
}
The verified move uses install -D, which creates the parent directory and sets the executable mode in one call. The binary lands executable and complete, or it does not land at all. There is no window in which Cosmovisor could see a half-written file.
The Ops lesson named the pre-swap staging gate as the one thing that makes Cosmovisor's zero-touch swap actually zero-touch. This script is that gate, expressed as shell. Run it when the proposal passes, run it again the day before as a confirmation, and the guard clause makes the second run a no-op that prints "already staged."
The second component the Ops lesson pointed at is the upgrade-info watcher. After the daemon panics at the upgrade height it writes upgrade-info.json into the data directory. A short watcher confirms the daemon recorded the expected plan, which is the difference between "the chain halted for our upgrade" and "the chain halted for some other reason."
watch_upgrade_info() {
local info="${HOME}/.gaia/data/upgrade-info.json"
local expect="$1"
while [[ ! -f "${info}" ]]; do sleep 5; done
local got
got="$(grep -o '"name":"[^"]*"' "${info}" | head -1 | cut -d'"' -f4)"
[[ "${got}" == "${expect}" ]] || { echo "PLAN MISMATCH: got ${got}, want ${expect}"; return 1; }
echo "upgrade-info confirms plan: ${got}"
}
The watcher polls for the file, reads the plan name out of it, and compares against the name the operator expects. A mismatch is a loud failure, not a silent pass: strict-mode discipline applied to a semantic check rather than a syntactic one.
The 2026-07-04 genesis-restart Bash lesson built the export-edit-verify pipeline with a genesis-checksum gate. Today's checksum-gated stage is the same reflex applied to a binary: verify against a published hash before the artifact is allowed to matter. Both lessons share the conviction that in chain operations nothing is trusted until its hash is checked, because a single wrong byte fragments the network.
The 2026-06-20 liveness-watchdog lesson introduced the idempotent auto-unjail guard: a script that could run on every poll without double-acting. Today's guard clause is the same pattern moved from the runtime loop to the staging phase. The Bash arc is accreting a single operational grammar: strict mode for loud failure, traps for guaranteed cleanup, guard clauses for idempotence, and checksums as the gate on anything that will affect consensus.
The pre-swap staging gate is not a Cosmovisor feature. It is a shell script the operator runs days before the halt, and everything Cosmovisor does at the boundary depends on that script having run correctly. Write it in strict mode so failure is loud, give it a trap so a failed run leaves no partial artifact, gate every byte behind a checksum, and open with a guard clause so it is safe to run as many times as an anxious operator wants. Do that, and the zero-touch swap earns its name because the human touched the problem early, in a re-runnable script, instead of late, at the halt.