#!/usr/bin/env bash # spdk-ctl.sh — host-side fleet release ops for the LONG-LIVED SPDK target. # # Stages a release's artifacts into ./releases// and adds/removes it on # the running nvmf_tgt ONLINE — no container restart, no dropped connections. # This is the operator entrypoint for rolling updates and cleanup; it wraps the # in-container spdk-release control script (driven via `docker compose exec`). # # ./spdk-ctl.sh load [oci_ref] # stage + add live (oci_ref default: $OCI_IMAGE:) # ./spdk-ctl.sh unload [--force] # drain-gated remove + delete staged files # ./spdk-ctl.sh list # served versions + connected hosts # # Typical rollout: # ./spdk-ctl.sh load 1.3.0 # serve the new version alongside the old # # ... reboot hosts onto 1.3.0 (deployment manifest digest bump) ... # ./spdk-ctl.sh list # watch 1.2.0 CONNS fall to 0 # ./spdk-ctl.sh unload 1.2.0 # cleanup once drained (refuses if hosts remain) set -euo pipefail cd "$(dirname "$0")" RELEASES_DIR="${RELEASES_DIR:-./releases}" SVC="${NVMEOF_SVC:-nvmeof}" # Pull OCI_IMAGE default from the stack .env if present. if [ -f ../.env ]; then set -a; . ../.env; set +a fi # Run compose from the stack dir (one level up, where docker-compose.yml lives). dc() { ( cd .. && docker compose "$@" ); } usage() { echo "usage: $0 {load [oci_ref] | unload [--force] | list}" >&2 exit 2 } cmd_load() { local v=${1:-}; [ -n "$v" ] || usage local ref=${2:-} if [ -z "$ref" ]; then [ -n "${OCI_IMAGE:-}" ] || { echo "ERROR: set OCI_IMAGE in ../.env or pass an oci_ref" >&2; exit 1; } ref="${OCI_IMAGE}:${v}" fi local dest="$RELEASES_DIR/$v" if [ -s "$dest/verity.json" ]; then echo "==> release $v already staged in $dest (skipping extract)" else echo "==> extracting $ref -> $dest" mkdir -p "$dest" local cid cid=$(docker create "$ref") # FROM-scratch artifact image: copy the three files the target needs. docker cp "$cid:/artifacts/rootfs.erofs" "$dest/rootfs.erofs" docker cp "$cid:/artifacts/rootfs.hash" "$dest/rootfs.hash" docker cp "$cid:/artifacts/verity.json" "$dest/verity.json" docker rm "$cid" >/dev/null fi echo "==> adding release $v on the live target (no restart)" dc exec -T "$SVC" spdk-release add "$v" dc exec -T "$SVC" spdk-release list } cmd_unload() { local v="" force="" local a for a in "$@"; do case "$a" in --force) force="--force" ;; *) v="$a" ;; esac done [ -n "$v" ] || usage echo "==> removing release $v from the live target" if dc exec -T "$SVC" spdk-release remove "$v" ${force:+$force}; then rm -rf "${RELEASES_DIR:?}/$v" echo "==> staged artifacts for $v deleted from $RELEASES_DIR/$v" else echo "remove refused (version still has connected hosts?) — staged files left in place." >&2 echo "Drain those hosts first, or re-run with --force to evict them." >&2 exit 1 fi } case "${1:-}" in load) shift; cmd_load "$@" ;; unload|rm) shift; cmd_unload "$@" ;; list|ls) dc exec -T "$SVC" spdk-release list ;; *) usage ;; esac