#!/bin/sh # Sets a Mac or a Linux computer up to build Metal America tools with Claude. # # curl -fsSL https://labs.metal-america.com/install.sh | sh # # It installs what is missing and skips what is there: Node, the Google Cloud # CLI, Claude Code and the developer kit. Everything goes into your home # folder, so it never asks for the computer's password. It signs you in to # Google with your Metal America account, which opens your browser once. # # This file is public and holds no secret. The kit itself is private: it is # downloaded at the end, and only for somebody signed in as staff. # # Safe to run again. MA_SETUP_SKIP_SIGN_IN=1 stops before the sign-in, for tests. set -eu NODE_MAJOR=24 # A standalone Python for the Google Cloud CLI on a Mac. Pinned, with the # digests further down; changing one means changing both. PYTHON_VERSION=3.12.15 PYTHON_BUILD=20261009 HOME_DIR="$HOME/.metal-america" KIT_BUCKET="https://storage.googleapis.com/ma-developer-kit" say() { printf ' %s\n' "$*"; } step() { printf '\n %s\n' "$*"; } stop() { printf '\n %s\n' "$1" shift for line in "$@"; do printf ' %s\n' "$line"; done printf '\n' exit 1 } have() { command -v "$1" >/dev/null 2>&1; } case "$(uname -s)" in Darwin) OS=darwin ;; Linux) OS=linux ;; *) stop "This script is for a Mac or Linux." "On Windows, use the PowerShell line on https://labs.metal-america.com" ;; esac case "$(uname -m)" in arm64 | aarch64) CPU=arm64 ;; x86_64 | amd64) CPU=x64 ;; *) stop "This computer's processor ($(uname -m)) is not one this script knows." "Tell engineering." ;; esac have curl || stop "This computer has no curl, which the setup downloads with." "Install curl, then run this again." # Every new terminal should find what is installed here. remember() { for profile in "$HOME/.zshrc" "$HOME/.bashrc" "$HOME/.profile"; do [ -e "$profile" ] || [ "$profile" = "$HOME/.profile" ] || continue grep -qsF "$1" "$profile" || printf '\n# Added by the Metal America setup.\n%s\n' "$1" >>"$profile" done } remember_path() { remember "export PATH=\"$1:\$PATH\"" PATH="$1:$PATH" export PATH } sha256() { if have sha256sum; then sha256sum "$1" | cut -d' ' -f1; else shasum -a 256 "$1" | cut -d' ' -f1; fi } # --- Git --------------------------------------------------------------------- step "1 of 5 Git" if [ "$OS" = darwin ] && ! xcode-select -p >/dev/null 2>&1; then xcode-select --install >/dev/null 2>&1 || true stop "Your Mac needs Apple's developer tools, which include Git." \ "A window has opened asking to install them. Click Install and wait for it to finish," \ "then run this line again." fi have git || stop "This computer has no Git." "Install it with your system's package manager, for example: sudo apt-get install git" "Then run this line again." say "Git is here." # --- Node -------------------------------------------------------------------- step "2 of 5 Node" node_ok() { have node && [ "$(node -p 'process.versions.node.split(".")[0]')" -ge "$NODE_MAJOR" ]; } have node || [ ! -x "$HOME_DIR/node/bin/node" ] || PATH="$HOME_DIR/node/bin:$PATH" if node_ok; then say "Node $(node --version) is here." else dist="https://nodejs.org/dist/latest-v$NODE_MAJOR.x" sums=$(curl -fsSL "$dist/SHASUMS256.txt") file=$(printf '%s\n' "$sums" | awk -v want="$OS-$CPU.tar.gz" 'index($2, want) { print $2; exit }') [ -n "$file" ] || stop "Could not find Node $NODE_MAJOR for this computer." "Tell engineering." say "Installing ${file%.tar.gz} into your home folder." mkdir -p "$HOME_DIR" curl -fsSL "$dist/$file" -o "$HOME_DIR/$file" [ "$(sha256 "$HOME_DIR/$file")" = "$(printf '%s\n' "$sums" | awk -v f="$file" '$2 == f { print $1 }')" ] || stop "The Node download does not match what Node published. Nothing was installed." "Run this line again." rm -rf "$HOME_DIR/node" mkdir -p "$HOME_DIR/node" tar -xzf "$HOME_DIR/$file" -C "$HOME_DIR/node" --strip-components=1 rm -f "$HOME_DIR/$file" remember_path "$HOME_DIR/node/bin" node_ok || stop "Node was installed but does not run." "Tell engineering." say "Node $(node --version) is installed." fi # pnpm, which installs a tool's building blocks, comes with Node this way. have pnpm || corepack enable >/dev/null 2>&1 || say "Could not switch pnpm on. Claude will sort that out when it is needed." # --- Google Cloud CLI ---------------------------------------------------------- step "3 of 5 Google Cloud CLI" have gcloud || [ ! -x "$HOME/google-cloud-sdk/bin/gcloud" ] || PATH="$HOME/google-cloud-sdk/bin:$PATH" if have gcloud; then say "The Google Cloud CLI is here." else [ "$CPU" = arm64 ] && gcpu=arm || gcpu=x86_64 say "Installing the Google Cloud CLI into your home folder. This takes a minute or two." curl -fsSL "https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-cli-$OS-$gcpu.tar.gz" | tar -xz -C "$HOME" # The CLI is written in Python and needs 3.10 or newer. A Mac comes with 3.9 # (measured 2026-10-12: the install fails on one with nothing else on it), # and Google's own fix asks for the computer's password. So: a Python of its # own, in the home folder, that only the CLI is pointed at. if [ "$OS" = darwin ] && ! python3 -c 'import sys; sys.exit(sys.version_info < (3, 10))' 2>/dev/null; then if [ "$CPU" = arm64 ]; then py=aarch64 pysum=83dcb04777c798bd95f3687acf9ae84b09860272ee6bdfb05be8bcf9447f75b8 else py=x86_64 pysum=4c84ef5abe4bc145646175fce9b881936d60126b9a3f9a24c6308e2d035ca5c6 fi mkdir -p "$HOME_DIR" curl -fsSL "https://github.com/astral-sh/python-build-standalone/releases/download/$PYTHON_BUILD/cpython-$PYTHON_VERSION%2B$PYTHON_BUILD-$py-apple-darwin-install_only.tar.gz" -o "$HOME_DIR/python.tar.gz" [ "$(sha256 "$HOME_DIR/python.tar.gz")" = "$pysum" ] || stop "The Python download does not match the one this script was written for. Nothing was installed." "Tell engineering." rm -rf "$HOME_DIR/python" tar -xzf "$HOME_DIR/python.tar.gz" -C "$HOME_DIR" rm -f "$HOME_DIR/python.tar.gz" CLOUDSDK_PYTHON="$HOME_DIR/python/bin/python3" export CLOUDSDK_PYTHON remember "export CLOUDSDK_PYTHON=\"$CLOUDSDK_PYTHON\"" fi # Told not to install a Python itself: that is the step that asks for a password. "$HOME/google-cloud-sdk/install.sh" --quiet --usage-reporting=false --path-update=false --install-python false >/dev/null 2>&1 || true remember_path "$HOME/google-cloud-sdk/bin" have gcloud || stop "The Google Cloud CLI was installed but does not run." "Tell engineering." say "The Google Cloud CLI is installed." fi # --- Claude Code --------------------------------------------------------------- step "4 of 5 Claude Code" have claude || [ ! -x "$HOME/.local/bin/claude" ] || PATH="$HOME/.local/bin:$PATH" if have claude; then say "Claude Code is here." else say "Installing Claude Code." curl -fsSL https://claude.ai/install.sh | bash >/dev/null remember_path "$HOME/.local/bin" have claude || stop "Claude Code was installed but does not run." "Tell engineering." say "Claude Code is installed." fi [ "${MA_SETUP_SKIP_SIGN_IN:-}" = 1 ] && { step "Stopping before the sign-in, as asked."; exit 0; } # --- Google sign-in, then the kit --------------------------------------------- step "5 of 5 Your Metal America sign-in, and the developer kit" if ! gcloud auth print-access-token >/dev/null 2>&1; then say "Your browser is about to open. Choose your Metal America account and click Allow." # Standard input is closed: when this script is piped in, it IS standard input. gcloud auth login --quiet /dev/null 2>&1 || stop "The Google sign-in was not finished." "Run this line again, and choose your Metal America account in the browser." fi say "Signed in to Google as $(gcloud config get-value account 2>/dev/null)." token=$(gcloud auth print-access-token) mkdir -p "$HOME_DIR" curl -fsS -H "Authorization: Bearer $token" "$KIT_BUCKET/setup.cjs" -o "$HOME_DIR/setup.cjs" 2>/dev/null || stop "Google would not let this account read the developer kit." \ "Sign in with your Metal America account, not a personal one: gcloud auth login" \ "Then run this line again." node "$HOME_DIR/setup.cjs" rm -f "$HOME_DIR/setup.cjs" printf '\n All set. Close this window and open a new terminal, then:\n\n' printf ' mkdir -p ~/coding/my-tool && cd ~/coding/my-tool && claude\n\n' printf ' The first time, Claude opens your browser to sign in. Then type /new-project\n\n'