Files
club-3090/scripts/cluster.sh
noonghunna 70c87a139b feat(cluster): scripts/cluster.sh — GPU-cluster management CLI (#610 Phase A′)
The CLI seam the c3 cluster UX (Phase C) will consume, and a full headless/
SSH cluster capability on its own. A "cluster" = a named model on a chosen
GPU set + port (an estate instance).

- estate_cli.py gains create / list / status / rm verbs (D2: NEW verbs go
  IN estate_cli.py, which owns the schema + validate_estate + boot/down —
  ONE validation path shared with hand-written estate files and the wizard).
  up/down reuse `boot`/`down --only <name>`.
- create runs the D1 fit-vs-set logic (#610 addendum 3): kv-calc --card is
  single-card + registry TP, so count != compose TP is a HARD REJECT, a
  heterogeneous set is estimated against its min-VRAM card (+ note), and the
  whole set is re-validated (validate_estate: GPU collision, port collision,
  per-instance fits) before append. GPU indices stay index-based in the
  estate file; UUIDs resolve at boot (Phase A).
- status carries the {requested, actual, placement} verdict per cluster
  (the shape the c3 badge reads); list/status take --json.
- scripts/cluster.sh: the ergonomic bash front (create/list/status/up/down/
  rm), a thin wrapper over estate_cli.py.
- test-cluster-cli.sh (NEW): full lifecycle hardware-free via CLUB3090_FAKE_
  GPUS — create/D1-reject/collision-reject/list/status/rm.

LIVE-VERIFIED (2x3090): create chat (fit-clean ~20.2 GiB) → up → estate boot
→ "✓ placement verified" → status "serving ✓ placement=ok", GPU0 idle /
GPU1 loaded → down → rm. D1 count!=TP + GPU-collision rejects confirmed.

Part of #610 (Phase A′). Next: Phase C (c3 cluster view + wizard).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EfF565T9eSLaqGzidyJ1Pm
2026-07-07 01:10:52 +00:00

56 lines
1.9 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
# cluster.sh — GPU-cluster management (#610 Phase A).
#
# A "cluster" = a named model on a chosen GPU set + port (an estate instance).
# This is the ergonomic front over the estate CLI (estate_cli.py owns the
# schema, validate_estate, kv-calc fit, and boot/down — ONE validation path
# shared with hand-written estate files and, later, the c3 wizard).
#
# Usage:
# bash scripts/cluster.sh create <name> --gpus 1,2 --slug vllm/dual [--port N]
# bash scripts/cluster.sh list [--json]
# bash scripts/cluster.sh status [--json]
# bash scripts/cluster.sh up <name> # boot just this cluster
# bash scripts/cluster.sh down <name> # stop just this cluster
# bash scripts/cluster.sh rm <name> # remove from the estate file
#
# The artifact is the estate file (default scripts/lib/profiles/estate.yml;
# override with --file). GPU indices stay index-based in the file and are
# resolved to UUIDs at boot (#610 Phase A) so clusters land on the cards they
# claimed on both container runtimes (classic nvidia + CDI/NixOS).
set -euo pipefail
ROOT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)"
ESTATE_CLI="${ROOT_DIR}/scripts/lib/profiles/estate_cli.py"
usage() {
sed -n '2,26p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'
}
cmd="${1:-}"
[[ -n "$cmd" ]] || { usage; exit 2; }
shift || true
case "$cmd" in
create|list|status|rm)
exec python3 "$ESTATE_CLI" "$cmd" "$@"
;;
up)
name="${1:-}"; shift || true
[[ -n "$name" ]] || { echo "cluster.sh up <name>" >&2; exit 2; }
exec python3 "$ESTATE_CLI" boot --only "$name" "$@"
;;
down)
name="${1:-}"; shift || true
[[ -n "$name" ]] || { echo "cluster.sh down <name>" >&2; exit 2; }
exec python3 "$ESTATE_CLI" down --only "$name" "$@"
;;
-h|--help|help)
usage
;;
*)
echo "cluster.sh: unknown command '$cmd'" >&2
usage
exit 2
;;
esac