MyGemmaNPC / install.sh
satendrakumar's picture
install claude
e71db58 verified
Raw
History Blame Contribute Delete
7.98 kB
#!/bin/bash
set -e
# Parse command line arguments
TARGET="$1" # Optional target parameter
# Validate target if provided
if [[ -n "$TARGET" ]] && [[ ! "$TARGET" =~ ^(stable|latest|[0-9]+\.[0-9]+\.[0-9]+(-[^[:space:]]+)?)$ ]]; then
echo "Usage: $0 [stable|latest|VERSION]" >&2
exit 1
fi
# Refuse to run under sudo from a regular user's shell. This installer puts
# everything under $HOME, which under sudo typically resolves to root's home:
# the binary lands in /root/.local/bin (or is left root-owned in the user's
# home, depending on the distro's sudo configuration), and the 'claude'
# command is then not found in the user's own shell. Plain root with no sudo
# (containers, CI, root-only systems) is unaffected by this check.
if [ "$(id -u)" -eq 0 ] && [ -n "${SUDO_USER:-}" ] && [ "$SUDO_USER" != "root" ] && [ -z "${CLAUDE_INSTALL_ALLOW_SUDO:-}" ]; then
echo "Error: do not run this installer with sudo." >&2
echo "" >&2
echo "Claude Code installs into your home directory and does not need root access." >&2
echo "With sudo, the installation would go into root's home directory instead of" >&2
echo "yours, and the 'claude' command would not work from your own shell." >&2
echo "" >&2
echo "Please re-run the same command without sudo, e.g.:" >&2
# pinned-dep-allow: display-only guidance text in an error message, not an executed install; install.sh is Anthropic's own installer
echo " curl -fsSL https://claude.ai/install.sh | bash" >&2
echo "" >&2
echo "To intentionally install Claude Code for the root user, re-run with" >&2
echo "CLAUDE_INSTALL_ALLOW_SUDO=1 set in the installer's environment, e.g.:" >&2
# pinned-dep-allow: display-only guidance text in an error message, not an executed install; install.sh is Anthropic's own installer
echo " curl -fsSL https://claude.ai/install.sh | sudo CLAUDE_INSTALL_ALLOW_SUDO=1 bash" >&2
exit 1
fi
DOWNLOAD_BASE_URL="https://downloads.claude.ai/claude-code-releases"
DOWNLOAD_DIR="$HOME/.claude/downloads"
# Check for required dependencies
DOWNLOADER=""
if command -v curl >/dev/null 2>&1; then
DOWNLOADER="curl"
elif command -v wget >/dev/null 2>&1; then
DOWNLOADER="wget"
else
echo "Either curl or wget is required but neither is installed" >&2
exit 1
fi
# Check if jq is available (optional)
HAS_JQ=false
if command -v jq >/dev/null 2>&1; then
HAS_JQ=true
fi
# Download function that works with both curl and wget
download_file() {
local url="$1"
local output="$2"
if [ "$DOWNLOADER" = "curl" ]; then
if [ -n "$output" ]; then
curl -fsSL -o "$output" "$url"
else
curl -fsSL "$url"
fi
elif [ "$DOWNLOADER" = "wget" ]; then
if [ -n "$output" ]; then
wget -q -O "$output" "$url"
else
wget -q -O - "$url"
fi
else
return 1
fi
}
# Simple JSON parser for extracting checksum when jq is not available
get_checksum_from_manifest() {
local json="$1"
local platform="$2"
# Normalize JSON to single line and extract checksum
json=$(echo "$json" | tr -d '\n\r\t' | sed 's/ \+/ /g')
# Extract checksum for platform using bash regex
if [[ $json =~ \"$platform\"[^}]*\"checksum\"[[:space:]]*:[[:space:]]*\"([a-f0-9]{64})\" ]]; then
echo "${BASH_REMATCH[1]}"
return 0
fi
return 1
}
# Detect platform
case "$(uname -s)" in
Darwin) os="darwin" ;;
Linux) os="linux" ;;
MINGW*|MSYS*|CYGWIN*) echo "Windows is not supported by this script. See https://code.claude.com/docs for installation options." >&2; exit 1 ;;
*) echo "Unsupported operating system: $(uname -s). See https://code.claude.com/docs for supported platforms." >&2; exit 1 ;;
esac
case "$(uname -m)" in
x86_64|amd64) arch="x64" ;;
arm64|aarch64) arch="arm64" ;;
*) echo "Unsupported architecture: $(uname -m)" >&2; exit 1 ;;
esac
# Detect Rosetta 2 on macOS: if the shell is running as x64 under Rosetta on an ARM Mac,
# download the native arm64 binary instead of the x64 one
if [ "$os" = "darwin" ] && [ "$arch" = "x64" ]; then
if [ "$(sysctl -n sysctl.proc_translated 2>/dev/null)" = "1" ]; then
arch="arm64"
fi
fi
# Check for musl on Linux and adjust platform accordingly
if [ "$os" = "linux" ]; then
if [ -f /lib/libc.musl-x86_64.so.1 ] || [ -f /lib/libc.musl-aarch64.so.1 ] || ldd /bin/ls 2>&1 | grep -q musl; then
platform="linux-${arch}-musl"
else
platform="linux-${arch}"
fi
else
platform="${os}-${arch}"
fi
mkdir -p "$DOWNLOAD_DIR"
# Always download latest version (which has the most up-to-date installer)
version=$(download_file "$DOWNLOAD_BASE_URL/latest")
# Reject non-version content (e.g. an HTML error page) before it reaches the manifest URL
if [[ ! "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+ ]]; then
echo "Failed to get a valid version from downloads.claude.ai (got unexpected content)." >&2
echo "This can happen if the download service is unreachable or not available in your region - see https://www.anthropic.com/supported-countries" >&2
exit 1
fi
# Download manifest and extract checksum
manifest_json=$(download_file "$DOWNLOAD_BASE_URL/$version/manifest.json")
# Use jq if available, otherwise fall back to pure bash parsing
if [ "$HAS_JQ" = true ]; then
checksum=$(echo "$manifest_json" | jq -r ".platforms[\"$platform\"].checksum // empty")
else
checksum=$(get_checksum_from_manifest "$manifest_json" "$platform")
fi
# Validate checksum format (SHA256 = 64 hex characters)
if [ -z "$checksum" ] || [[ ! "$checksum" =~ ^[a-f0-9]{64}$ ]]; then
echo "Platform $platform not found in manifest" >&2
exit 1
fi
# Download and verify
binary_path="$DOWNLOAD_DIR/claude-$version-$platform"
if ! download_file "$DOWNLOAD_BASE_URL/$version/$platform/claude" "$binary_path"; then
echo "Download failed" >&2
rm -f "$binary_path"
exit 1
fi
# Pick the right checksum tool
if [ "$os" = "darwin" ]; then
actual=$(shasum -a 256 "$binary_path" | cut -d' ' -f1)
else
actual=$(sha256sum "$binary_path" | cut -d' ' -f1)
fi
if [ "$actual" != "$checksum" ]; then
echo "Checksum verification failed" >&2
rm -f "$binary_path"
exit 1
fi
chmod +x "$binary_path"
# Run claude install to set up launcher and shell integration
echo "Setting up Claude Code..."
install_code=0
"$binary_path" install ${TARGET:+"$TARGET"} || install_code=$?
# Clean up downloaded file
rm -f "$binary_path"
if [ "$install_code" -ne 0 ]; then
# A signal death mid-install kills the binary's TUI with no chance to
# restore the terminal, leaving the user's shell in raw mode (typed
# characters stop echoing). Restore it before printing anything.
if [ "$install_code" -ge 128 ] && [ -t 0 ]; then
stty sane 2>/dev/null || true
fi
# Red when stderr is a terminal, so the explanation stands out from the
# surrounding install output; plain when piped or captured
red="" reset=""
if [ -t 2 ]; then
red=$'\033[31m'
reset=$'\033[0m'
fi
# Signal deaths (exit code 128+N) print nothing of their own — bash shows
# only e.g. "Killed". 137 = SIGKILL, which on Linux is almost always the
# kernel OOM killer on small hosts; macOS has no equivalent OOM kill, so
# the out-of-memory explanation is Linux-only.
if [ "$install_code" -eq 137 ] && [ "$os" = "linux" ]; then
echo "${red}Installation was killed before it could finish (exit code 137). This usually means the system ran out of memory.${reset}" >&2
echo "${red}Claude Code needs roughly 512MB of free memory to install. Free up memory, then run this script again.${reset}" >&2
elif [ "$install_code" -ge 128 ]; then
echo "${red}Installation was killed before it could finish (exit code $install_code).${reset}" >&2
fi
exit "$install_code"
fi
echo ""
echo "✅ Installation complete!"
echo ""