Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 52 additions & 19 deletions scripts/install-k8s.sh
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ trap 'exit 130' INT
trap 'exit 143' TERM

# ── Main ─────────────────────────────────────────────────────────────────────
# Structured as the six-step first-run run-through (a–f). Each step prints a
# gerund header via step_header and a trailing blank-line pair (the run-through's
# spacing); print_roadmap lists the plan up front. Step b owns the prerequisites
# AND the tracebloc CLI (moved out of provisioning — step d needs it to sign in).
main() {
[[ "${1:-}" == "--help" || "${1:-}" == "-h" ]] && print_help
# Support bundle: collect redacted diagnostics and exit, before any install
Expand All @@ -88,13 +92,36 @@ main() {
validate_config
setup_log_file
print_banner

# ── Stop-and-check gate — SLOT for client#339 (do NOT implement here) ─────
# After the banner, before the roadmap: client#339 adds a READ-ONLY assessment
# (assess.sh::assess_existing_install) that short-circuits an already-healthy
# machine straight to the `tracebloc` home screen and exits 0, so a plain re-run
# doesn't repeat every step. The gate LOGIC lives in that branch's assess.sh
# (plus its --force/--reinstall / TRACEBLOC_FORCE_REINSTALL bypass) — this is
# only the call site, left here so the two changes reconcile cleanly. The call
# is a guarded NO-OP until assess.sh ships (declare -F is false), so nothing
# changes on this branch. NOTE: the bootstrap (install.sh) already performs the
# pre-download `tracebloc doctor` bailout; this is the deeper post-download check.
if declare -F assess_existing_install >/dev/null 2>&1; then
assess_existing_install
fi

print_roadmap

# ── Step 1/5: Check system requirements ──────────────────────────────────
step 1 5 "Checking system requirements"
# ── a) Check your machine ────────────────────────────────────────────────
step_header a "Checking your machine"
run_preflight
detect_gpu
echo ""; echo ""

# ── b) Install what tracebloc needs ──────────────────────────────────────
# Prerequisites (Docker + system tools) AND the tracebloc CLI. The CLI moved
# here from provisioning: step d (provision_client) needs it to sign in and
# mint the credential, so it must exist before then. install_tracebloc_cli is
# non-fatal on its own; step d's `has tracebloc` guard makes a genuinely
# missing CLI fatal at the point it's actually required.
step_header b "Installing what tracebloc needs"
case "$OS" in
Darwin) install_macos ;;
Linux) install_linux ;;
Expand All @@ -103,35 +130,41 @@ main() {
irm https://raw.githubusercontent.com/tracebloc/client/main/scripts/install.ps1 | iex" ;;
*) error "Unsupported OS: $OS" ;;
esac
# Guarded: a stale bootstrap may not have fetched install-cli.sh — then step d's
# guard (or install_client_helm's dual-mode path) surfaces it.
if declare -F install_tracebloc_cli >/dev/null 2>&1; then
install_tracebloc_cli
fi
echo ""; echo ""

# ── Step 2/5: Set up secure compute environment ──────────────────────────
step 2 5 "Setting up secure compute environment"
# ── c) Create your secure environment ────────────────────────────────────
step_header c "Creating your secure environment"
create_cluster
deploy_gpu_device_plugin
verify_gpu
echo ""; echo ""

# ── Step 3/5: sign in + provision the client (install CLI, login, client
# create) BEFORE Helm, so the minted credential + derived namespace feed the
# chart (#838). On the dual-mode path (TRACEBLOC_VALUES_FILE / pre-supplied
# credentials) this skips sign-in. Guarded so a stale bootstrap that didn't
# fetch provision.sh degrades to the dual-mode credential path rather than
# aborting; in that case the operator must supply credentials/values. ──────
# ── d) Register this machine ─────────────────────────────────────────────
# Sign in + `client create` BEFORE Helm, so the minted credential + derived
# namespace feed the chart (#838). Dual-mode (TRACEBLOC_VALUES_FILE / pre-
# supplied credentials) skips sign-in. Guarded so a stale bootstrap that
# didn't fetch provision.sh degrades to the dual-mode credential path inside
# install_client_helm rather than aborting.
step_header d "Registering this machine"
if declare -F provision_client >/dev/null 2>&1; then
provision_client
elif declare -F install_tracebloc_cli >/dev/null 2>&1; then
# Stale bootstrap: provision.sh wasn't fetched, but install-cli.sh was. Keep
# the old post-Helm Step 5 behavior so the CLI still gets installed (non-fatal,
# for `tracebloc data ingest`); provisioning then falls through to the dual-mode
# credential path inside install_client_helm.
install_tracebloc_cli
fi
echo ""; echo ""

# ── Step 4/5 + 5/5 are handled inside install_client_helm ────────────────
# ── e) Install tracebloc ─────────────────────────────────────────────────
step_header e "Installing tracebloc"
install_client_helm
echo ""; echo ""

# ── Verify the client actually came up before reporting anything ─────────
# ── f) Connect to the tracebloc network ──────────────────────────────────
# Wait for the client's workloads to actually come up, then the rich summary.
step_header f "Connecting to the tracebloc network"
wait_for_client_ready

print_summary

# Exit code reflects reality: connected/starting are OK; failures are non-zero
Expand Down
101 changes: 96 additions & 5 deletions scripts/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,16 @@
# =============================================================================
set -euo pipefail

# ── Minimal colours for THIS script's own output ─────────────────────────────
# common.sh (which owns the shared colour palette + helpers) is one of the files
# this bootstrap is about to fetch and verify, so it isn't sourced yet. Define a
# small local palette, disabled when stdout isn't a terminal or NO_COLOR is set.
if [[ -t 1 && -z "${NO_COLOR:-}" ]]; then
_B=$'\033[1m'; _C=$'\033[0;36m'; _D=$'\033[2m'; _G=$'\033[0;32m'; _R=$'\033[0m'
else
_B=""; _C=""; _D=""; _G=""; _R=""
fi

# ── Platform gate ────────────────────────────────────────────────────────────
case "$(uname -s)" in
MINGW*|MSYS*|CYGWIN*)
Expand All @@ -44,6 +54,61 @@ case "$(uname -s)" in
exit 1 ;;
esac

# ── Early bailout: already set up and healthy → skip the whole download ──────
# Before fetching ANYTHING, if the tracebloc CLI is already present AND reports a
# healthy setup, there is nothing to download or install — hand straight to the
# home screen. `tracebloc doctor` is the health probe; it is BOUNDED (stock macOS
# has no coreutils `timeout`, so we background + poll + kill on overrun) and its
# EXIT CODE is the gate: an old CLI that doesn't know `doctor` returns non-zero, so
# we simply fall through to a normal install. Skipped on a forced reinstall
# (--force/--reinstall or TRACEBLOC_FORCE_REINSTALL=1), on the dev/unverified path,
# and whenever the operator explicitly pinned a REF/BRANCH (an explicit version
# request must always (re)install, never bail).
_tb_bail_ok=1
[[ "${TRACEBLOC_FORCE_REINSTALL:-0}" == "1" ]] && _tb_bail_ok=0
[[ "${TRACEBLOC_ALLOW_UNVERIFIED:-0}" == "1" ]] && _tb_bail_ok=0
[[ -n "${REF:-}" || -n "${BRANCH:-}" ]] && _tb_bail_ok=0
for _a in "$@"; do
case "$_a" in --force|--reinstall) _tb_bail_ok=0 ;; esac
done

_tb_check_healthy() {
# Run `tracebloc doctor` behind a small inline spinner, bounded so a wedged CLI
# can't hang the bootstrap. Returns doctor's exit code (0 = healthy), or 124 on
# timeout. 0.2s ticks; TRACEBLOC_DOCTOR_TIMEOUT (default 20s) bounds it.
local timeout="${TRACEBLOC_DOCTOR_TIMEOUT:-20}" maxticks tick=0 rc
local frames='⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏' i=0 f
maxticks=$(( timeout * 5 ))
tracebloc doctor >/dev/null 2>&1 &
local pid=$!
tput civis 2>/dev/null || true
while kill -0 "$pid" 2>/dev/null; do
f="${frames:i:1}"; i=$(( (i + 1) % ${#frames} ))
printf '\r %s%s%s Checking your tracebloc setup…' "$_C" "$f" "$_R"
if [[ "$tick" -ge "$maxticks" ]]; then
kill "$pid" 2>/dev/null || true; wait "$pid" 2>/dev/null || true
printf '\r\033[K'; tput cnorm 2>/dev/null || true
return 124
fi
sleep 0.2; tick=$(( tick + 1 ))
done
wait "$pid"; rc=$?
printf '\r\033[K'; tput cnorm 2>/dev/null || true
return "$rc"
}

if [[ "$_tb_bail_ok" == "1" ]] && command -v tracebloc >/dev/null 2>&1; then
printf '\n'

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bailout misses ~/.local/bin CLI

Medium Severity

The new early bailout gates on command -v tracebloc before any PATH adjustment. The tracebloc CLI installer commonly places the binary in ~/.local/bin, which provision_client explicitly prepends to PATH, but this bootstrap does not. A healthy install can be skipped for detection and trigger a full re-download.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit ff2e69c. Configure here.

if _tb_check_healthy; then
printf ' %s✓%s %sAlready set up and healthy — nothing to download or install.%s\n' "$_G" "$_R" "$_B" "$_R"
printf " %sRun%s %stracebloc%s%s to use it. Here's your environment:%s\n" "$_D" "$_R" "$_C" "$_R" "$_D" "$_R"
# Hand off to the home screen (exec replaces this process); if that somehow
# fails, we've already told the user it's healthy, so exit 0.
exec tracebloc || true
exit 0

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Healthy bailout exits after failed exec

Low Severity

When tracebloc doctor succeeds, the bootstrap prints that the machine is already healthy, then runs exec tracebloc || true followed by unconditional exit 0. If exec fails (missing execute bit, bad interpreter, or similar), the script still exits successfully without running the installer or launching the home screen.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 5a0fe0b. Configure here.

fi
fi

# ── Pinned, immutable release ref ──────────────────────────────────────────
# DEFAULT_REF is the immutable git tag this bootstrap fetches from. The release
# pipeline rewrites this line on every release so the published installer always
Expand Down Expand Up @@ -135,7 +200,24 @@ REPO_REL="https://github.com/tracebloc/client/releases/download/${REF}"
TMPDIR="$(mktemp -d)"
trap 'rm -rf "$TMPDIR"' EXIT

echo "tracebloc client installer · $REF"
# ── Banner ───────────────────────────────────────────────────────────────────
# Draw the first-run title here (mirrors common.sh::print_banner) so the
# curl|bash path shows it above "1. Downloading". Export TRACEBLOC_BANNER_SHOWN
# so the install-k8s.sh we're about to fetch doesn't draw a SECOND banner, and
# TRACEBLOC_INSTALL_REF so its title can state the version on the direct path.
export TRACEBLOC_BANNER_SHOWN=1
export TRACEBLOC_INSTALL_REF="$REF"
_tb_ver_suffix=""
case "$REF" in
v[0-9]*) _tb_ver_suffix="${_D} · ${REF}${_R}" ;; # only a real vX.Y.Z tag is a "version"
esac
printf '\n\n'
printf ' Setting up %s%stracebloc%s on your machine%s\n' "$_B" "$_C" "$_R" "$_tb_ver_suffix"
printf '\n'
printf ' %s────────────────────────────────────────%s\n' "$_D" "$_R"
printf '\n'
printf ' %s1. Downloading%s\n' "$_B" "$_R"
printf '\n'

mkdir -p "$TMPDIR/lib"

Expand Down Expand Up @@ -178,10 +260,16 @@ download_with_retry() {
}

# ── Fetch the sub-scripts ─────────────────────────────────────────────────
# One transient status frame while the (quiet on success) fetch loop runs; a
# retry/failure inside download_with_retry prints its own [WARN]/[ERROR] and, on
# hard failure, exits — so we only reach the success line when every file landed.
printf ' %s⠋%s Fetching the installer…' "$_C" "$_R"
for f in "${FILES[@]}"; do
dest="$TMPDIR/${f#scripts/}"
download_with_retry "$REPO_RAW/$f" "$dest"
done
printf '\r\033[K'
printf ' %s✔%s Installer downloaded — %s files\n' "$_G" "$_R" "${#FILES[@]}"

# ── Pick a sha256 tool (coreutils on Linux, shasum on macOS) ───────────────
_sha256_of() {
Expand All @@ -203,8 +291,7 @@ _sha256_of() {
verify_against_manifest() {
local manifest="$TMPDIR/manifest.sha256"

echo ""
echo "Verifying the installer is authentic before anything runs…"
printf " %sVerifying it's authentic (cosign)…%s\n" "$_D" "$_R"

if ! _sha256_of "$TMPDIR/install-k8s.sh" >/dev/null 2>&1; then
echo "[ERROR] No sha256 tool (sha256sum / shasum) on PATH — can't verify the" >&2
Expand Down Expand Up @@ -248,7 +335,7 @@ verify_against_manifest() {
exit 1
fi
done
echo " ✔ All ${#FILES[@]} installer files verifiednone were altered"
printf ' %s✔%s All %s files intactnothing was altered\n' "$_G" "$_R" "${#FILES[@]}"
}

download_manifest() {
Expand Down Expand Up @@ -308,7 +395,7 @@ verify_manifest_signature() {
--certificate "$cert" \
--signature "$sig" \
"$manifest" >/dev/null 2>&1; then
echo " ✔ Signature verified — published by tracebloc (Sigstore keyless)"
printf ' %s✔%s Signature verified — published by tracebloc (Sigstore keyless)\n' "$_G" "$_R"
else
echo "[ERROR] cosign signature verification FAILED for manifest.sha256 — refusing" >&2
echo " to install." >&2
Expand Down Expand Up @@ -364,6 +451,10 @@ ensure_cosign() {

verify_against_manifest

# Spacing before install-k8s.sh renders "2. Installing" (its banner is suppressed
# by TRACEBLOC_BANNER_SHOWN, so it goes straight to the roadmap).
printf '\n\n'

chmod +x "$TMPDIR/install-k8s.sh"

bash "$TMPDIR/install-k8s.sh" "$@"
29 changes: 19 additions & 10 deletions scripts/lib/cluster.sh
Original file line number Diff line number Diff line change
Expand Up @@ -191,11 +191,11 @@ _handle_existing_cluster() {
CLUSTER_STATUS="${CLUSTER_STATUS:-0}"

if [[ "$CLUSTER_STATUS" -gt "0" ]]; then
success "Compute environment already running."
success "Secure environment already running."
else
log "Cluster '$CLUSTER_NAME' exists but is stopped — starting it..."
k3d cluster start "$CLUSTER_NAME"
success "Compute environment started."
success "Secure environment started."
fi

_check_existing_cluster_proxy
Expand Down Expand Up @@ -320,7 +320,9 @@ _create_new_cluster() {
else
log "Creating cluster with $SERVERS server(s) + $AGENTS agent(s) (CPU-only)..."
fi
hint "First run may take 1-2 minutes to download components."
echo -e " ${DIM}Downloading the runtime that hosts your environment — a lightweight,${RESET}"
echo -e " ${DIM}self-contained Kubernetes that runs entirely on your machine.${RESET}"
echo ""

# Propagate corporate proxy env so k3s/containerd can reach external registries
# behind an HTTP/HTTPS proxy (hospital/banking/government tenants). Passed via a
Expand All @@ -339,10 +341,15 @@ _create_new_cluster() {

local create_out create_rc
create_out="$(mktemp)"
# Capture the exit code WITHOUT tripping `set -e`: a bare failing command here
# would abort the script immediately, skipping the 'already exists' reuse path,
# the error dump, and the temp-dir cleanup below.
k3d "${K3D_ARGS[@]}" >"$create_out" 2>&1 && create_rc=0 || create_rc=$?
# Wrap the create in a spinner. k3d pulls the runtime image + boots the node
# (1-2 min on first run) while printing nothing, which reads as a frozen
# installer — the real fix here. Run it backgrounded and animate; spin() waits
# for the PID, so create_rc is k3d's real exit code (captured WITHOUT tripping
# `set -e`, so the 'already exists' reuse path, error dump, and temp-dir cleanup
# below still run) and the proxy-config cleanup can't race the finished create.
( k3d "${K3D_ARGS[@]}" >"$create_out" 2>&1 ) &
create_rc=0
spin "$!" "Creating your secure environment…" || create_rc=$?
[[ -n "$proxy_cfg" ]] && rm -rf "${proxy_cfg%/*}"
if [[ $create_rc -ne 0 ]]; then
if grep -qi "already exists\|a cluster with that name already exists" "$create_out" 2>/dev/null; then
Expand All @@ -358,7 +365,9 @@ _create_new_cluster() {
fi
cat "$create_out" >> "${LOG_FILE:-/dev/null}" 2>/dev/null
rm -f "$create_out"
success "Compute environment ready."
# No success line here — _wait_for_api prints the single "Secure environment
# ready" once the API server actually answers (the true ready signal).
log "k3d cluster '$CLUSTER_NAME' created."
}

_merge_kubeconfig() {
Expand Down Expand Up @@ -402,10 +411,10 @@ _wait_for_api() {
if kubectl cluster-info &>/dev/null 2>&1; then
printf "\r\033[K"
tput cnorm 2>/dev/null || true
success "Compute environment online."
success "Secure environment ready"
return
fi
printf "\r ${CYAN}%s${RESET} Starting compute environment..." "${frames[f]}"
printf "\r ${CYAN}%s${RESET} Starting your secure environment" "${frames[f]}"
f=$(( (f + 1) % ${#frames[@]} ))
sleep 2
done
Expand Down
Loading
Loading