tidaldb/scripts/build-release.sh
jx12n 580142df49 feat(m12): election-divergence-fix + soak-eval streak + release tooling
Durable `leader_acked` frontier in `ShardReplica` tracks the highest seqno
acked under `ack=leader` (journal-only, un-replicated); `decide_join` now
quarantines on THIS node's own frontier rather than comparing stream numbers
across stream boundaries — eliminates false-quarantine churn on rolling
restarts. `SHUTDOWN_HANDOFF_WAIT` (3s) drains the leader's tail to quorum
before step-down so the next leader inherits a clean prefix. New
`load_leader_acked`/`persist_leader_acked` helpers; `cluster_reseed.rs` gains
the divergence-fix regression suite; `replication_ops.rs` threads the signal.

Soak-eval: `tidal_stress::soak_eval` + `soak-eval` binary implement the
30-night streak (ledger.tsv × restarts.tsv → streak.tsv); monitor and nightly
CronJob k8s YAMLs updated; phase-9 doc clarifies the dual-stream streak
definition (ledger PASS AND zero pod restarts in window). `run-reliability.sh`
gates the election-divergence suite before any k8s push.

Release tooling: `docker/release/` multi-stage Dockerfile + DR image;
`scripts/build-release.sh` single repeatable cross-compile+buildx path.
2026-06-18 13:08:53 -06:00

111 lines
5.1 KiB
Bash
Executable File

#!/usr/bin/env bash
# build-release.sh — the one committed, repeatable tidalDB release path.
#
# Replaces the ad-hoc hand-typed cross-compile + buildx + push with a single
# command on a clean checkout:
#
# ./scripts/build-release.sh <tag> [server|dr|stress|all]
#
# It HOST cross-compiles (mac-arm64 -> x86_64-unknown-linux-gnu, glibc 2.41 via the
# homebrew toolchain), packages the binaries into the committed docker/release/*
# Dockerfiles via the amd64 buildx builder, pushes, and prints the resulting
# @sha256 digest for each image (pin that digest in k8s/cluster/statefulset.yaml).
#
# The project bans CI/CD pipelines (orchard9-k3sf/CLAUDE.md) — this is a SCRIPT,
# run manually or from an operator's shell, never a pipeline. Registry creds come
# from the Docker daemon's existing login (run `docker login registry.threesix.ai`
# first); NO secret is embedded here.
set -euo pipefail
TAG="${1:-}"
COMPONENT="${2:-all}"
REGISTRY="${TIDAL_REGISTRY:-registry.threesix.ai/tidal}"
TARGET="x86_64-unknown-linux-gnu"
BUILDER="${TIDAL_BUILDX_BUILDER:-amd64builder}"
die() { echo "build-release: $*" >&2; exit 1; }
[ -n "$TAG" ] || die "usage: build-release.sh <tag> [server|dr|stress|all]"
case "$COMPONENT" in server|dr|stress|all) ;; *) die "component must be server|dr|stress|all" ;; esac
# ── Toolchain preflight (fail fast, no half-built image) ─────────────────────
command -v cargo >/dev/null || die "cargo not on PATH"
command -v docker >/dev/null || die "docker not on PATH"
command -v "${TARGET}-gcc" >/dev/null || die "missing ${TARGET}-gcc (brew install ${TARGET})"
PROTOC_BIN="${PROTOC:-$(command -v protoc || true)}"
[ -n "$PROTOC_BIN" ] || die "protoc not found (brew install protobuf), or set PROTOC"
rustup target list --installed 2>/dev/null | grep -qx "$TARGET" \
|| die "rust target $TARGET not installed (rustup target add $TARGET)"
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
cd "$REPO_ROOT"
# ── Pinned cross-compile environment (the documented recipe) ─────────────────
export CC_x86_64_unknown_linux_gnu="${TARGET}-gcc"
export CXX_x86_64_unknown_linux_gnu="${TARGET}-g++"
export AR_x86_64_unknown_linux_gnu="${TARGET}-ar"
export CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER="${TARGET}-gcc"
export PROTOC="$PROTOC_BIN"
# Which crates to cross-compile for the selected component(s).
PKGS=()
case "$COMPONENT" in
server) PKGS=(-p tidal-server) ;;
dr) PKGS=(-p tidalctl -p tidal-server) ;;
stress) PKGS=() ;; # stress builds IN-container (pure Rust, no engine deps)
all) PKGS=(-p tidal-server -p tidalctl) ;;
esac
if [ "${#PKGS[@]}" -gt 0 ]; then
echo "==> cross-compiling ${PKGS[*]} for $TARGET (release)"
cargo build "${PKGS[@]}" --release --target "$TARGET" --locked
fi
# ── buildx amd64 builder (idempotent) ────────────────────────────────────────
if ! docker buildx inspect "$BUILDER" >/dev/null 2>&1; then
echo "==> creating buildx builder $BUILDER"
docker buildx create --name "$BUILDER" --driver docker-container >/dev/null
fi
BIN_DIR="target/$TARGET/release"
STAGE="$(mktemp -d)"
trap 'rm -rf "$STAGE"' EXIT
# Build one image from a staged context; print its pushed digest.
build_image() { # $1=image_name $2=dockerfile $3=stage_subdir
local image="$REGISTRY/$1:$TAG" dockerfile="$2" ctx="$STAGE/$3"
echo "==> building $image"
docker buildx build --builder "$BUILDER" --platform linux/amd64 \
-f "$dockerfile" -t "$image" --push "$ctx"
local digest
digest="$(docker buildx imagetools inspect "$image" --format '{{.Manifest.Digest}}' 2>/dev/null || true)"
echo "RELEASE $1: $REGISTRY/$1@${digest:-<digest-unavailable>} (tag $TAG)"
}
if [ "$COMPONENT" = server ] || [ "$COMPONENT" = all ]; then
mkdir -p "$STAGE/server"
cp "$BIN_DIR/tidal-server" "$STAGE/server/"
cp -a docker/release/config "$STAGE/server/config"
cp docker/release/Dockerfile "$STAGE/server/Dockerfile"
build_image server "$STAGE/server/Dockerfile" server
fi
if [ "$COMPONENT" = dr ] || [ "$COMPONENT" = all ]; then
mkdir -p "$STAGE/dr"
cp "$BIN_DIR/tidalctl" "$BIN_DIR/tidal-server" "$STAGE/dr/"
cp docker/release/dr.Dockerfile "$STAGE/dr/Dockerfile"
build_image tidalctl "$STAGE/dr/Dockerfile" dr
fi
if [ "$COMPONENT" = stress ] || [ "$COMPONENT" = all ]; then
# Pure-Rust, in-container build (includes the soak-eval binary). Context = repo
# root so the workspace manifests resolve; the .dockerignore prunes it.
echo "==> building $REGISTRY/stress:$TAG (in-container, includes soak-eval)"
docker buildx build --builder "$BUILDER" --platform linux/amd64 \
-f docker/stress/Dockerfile -t "$REGISTRY/stress:$TAG" --push .
d="$(docker buildx imagetools inspect "$REGISTRY/stress:$TAG" --format '{{.Manifest.Digest}}' 2>/dev/null || true)"
echo "RELEASE stress: $REGISTRY/stress@${d:-<digest-unavailable>} (tag $TAG)"
fi
echo "==> done. Pin the printed @sha256 digest(s) in k8s/cluster/statefulset.yaml and the DR/soak manifests."