Skip to content

Commit 5363113

Browse files
committed
feat(claude-code): install from script as npm is deprecated by Anthropic
1 parent 2dcb4f7 commit 5363113

File tree

4 files changed

+168
-190
lines changed

4 files changed

+168
-190
lines changed

src/claude-code/bootstrap.sh

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
#!/bin/bash
2+
3+
set -e
4+
5+
# Parse command line arguments
6+
TARGET="$1" # Optional target parameter
7+
8+
# Validate target if provided
9+
if [[ -n "$TARGET" ]] && [[ ! "$TARGET" =~ ^(stable|latest|[0-9]+\.[0-9]+\.[0-9]+(-[^[:space:]]+)?)$ ]]; then
10+
echo "Usage: $0 [stable|latest|VERSION]" >&2
11+
exit 1
12+
fi
13+
14+
GCS_BUCKET="https://storage.googleapis.com/claude-code-dist-86c565f3-f756-42ad-8dfa-d59b1c096819/claude-code-releases"
15+
DOWNLOAD_DIR="$HOME/.claude/downloads"
16+
17+
# Check for required dependencies
18+
DOWNLOADER=""
19+
if command -v curl >/dev/null 2>&1; then
20+
DOWNLOADER="curl"
21+
elif command -v wget >/dev/null 2>&1; then
22+
DOWNLOADER="wget"
23+
else
24+
echo "Either curl or wget is required but neither is installed" >&2
25+
exit 1
26+
fi
27+
28+
# Check if jq is available (optional)
29+
HAS_JQ=false
30+
if command -v jq >/dev/null 2>&1; then
31+
HAS_JQ=true
32+
fi
33+
34+
# Download function that works with both curl and wget
35+
download_file() {
36+
local url="$1"
37+
local output="$2"
38+
39+
if [ "$DOWNLOADER" = "curl" ]; then
40+
if [ -n "$output" ]; then
41+
curl -fsSL -o "$output" "$url"
42+
else
43+
curl -fsSL "$url"
44+
fi
45+
elif [ "$DOWNLOADER" = "wget" ]; then
46+
if [ -n "$output" ]; then
47+
wget -q -O "$output" "$url"
48+
else
49+
wget -q -O - "$url"
50+
fi
51+
else
52+
return 1
53+
fi
54+
}
55+
56+
# Simple JSON parser for extracting checksum when jq is not available
57+
get_checksum_from_manifest() {
58+
local json="$1"
59+
local platform="$2"
60+
61+
# Normalize JSON to single line and extract checksum
62+
json=$(echo "$json" | tr -d '\n\r\t' | sed 's/ \+/ /g')
63+
64+
# Extract checksum for platform using bash regex
65+
if [[ $json =~ \"$platform\"[^}]*\"checksum\"[[:space:]]*:[[:space:]]*\"([a-f0-9]{64})\" ]]; then
66+
echo "${BASH_REMATCH[1]}"
67+
return 0
68+
fi
69+
70+
return 1
71+
}
72+
73+
# Detect platform
74+
case "$(uname -s)" in
75+
Darwin) os="darwin" ;;
76+
Linux) os="linux" ;;
77+
MINGW*|MSYS*|CYGWIN*) echo "Windows is not supported by this script. See https://code.claude.com/docs for installation options." >&2; exit 1 ;;
78+
*) echo "Unsupported operating system: $(uname -s). See https://code.claude.com/docs for supported platforms." >&2; exit 1 ;;
79+
esac
80+
81+
case "$(uname -m)" in
82+
x86_64|amd64) arch="x64" ;;
83+
arm64|aarch64) arch="arm64" ;;
84+
*) echo "Unsupported architecture: $(uname -m)" >&2; exit 1 ;;
85+
esac
86+
87+
# Detect Rosetta 2 on macOS: if the shell is running as x64 under Rosetta on an ARM Mac,
88+
# download the native arm64 binary instead of the x64 one
89+
if [ "$os" = "darwin" ] && [ "$arch" = "x64" ]; then
90+
if [ "$(sysctl -n sysctl.proc_translated 2>/dev/null)" = "1" ]; then
91+
arch="arm64"
92+
fi
93+
fi
94+
95+
# Check for musl on Linux and adjust platform accordingly
96+
if [ "$os" = "linux" ]; then
97+
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
98+
platform="linux-${arch}-musl"
99+
else
100+
platform="linux-${arch}"
101+
fi
102+
else
103+
platform="${os}-${arch}"
104+
fi
105+
mkdir -p "$DOWNLOAD_DIR"
106+
107+
# Always download latest version (which has the most up-to-date installer)
108+
version=$(download_file "$GCS_BUCKET/latest")
109+
110+
# Download manifest and extract checksum
111+
manifest_json=$(download_file "$GCS_BUCKET/$version/manifest.json")
112+
113+
# Use jq if available, otherwise fall back to pure bash parsing
114+
if [ "$HAS_JQ" = true ]; then
115+
checksum=$(echo "$manifest_json" | jq -r ".platforms[\"$platform\"].checksum // empty")
116+
else
117+
checksum=$(get_checksum_from_manifest "$manifest_json" "$platform")
118+
fi
119+
120+
# Validate checksum format (SHA256 = 64 hex characters)
121+
if [ -z "$checksum" ] || [[ ! "$checksum" =~ ^[a-f0-9]{64}$ ]]; then
122+
echo "Platform $platform not found in manifest" >&2
123+
exit 1
124+
fi
125+
126+
# Download and verify
127+
binary_path="$DOWNLOAD_DIR/claude-$version-$platform"
128+
if ! download_file "$GCS_BUCKET/$version/$platform/claude" "$binary_path"; then
129+
echo "Download failed" >&2
130+
rm -f "$binary_path"
131+
exit 1
132+
fi
133+
134+
# Pick the right checksum tool
135+
if [ "$os" = "darwin" ]; then
136+
actual=$(shasum -a 256 "$binary_path" | cut -d' ' -f1)
137+
else
138+
actual=$(sha256sum "$binary_path" | cut -d' ' -f1)
139+
fi
140+
141+
if [ "$actual" != "$checksum" ]; then
142+
echo "Checksum verification failed" >&2
143+
rm -f "$binary_path"
144+
exit 1
145+
fi
146+
147+
chmod +x "$binary_path"
148+
149+
# Run claude install to set up launcher and shell integration
150+
echo "Setting up Claude Code..."
151+
"$binary_path" install ${TARGET:+"$TARGET"}
152+
153+
# Clean up downloaded file
154+
rm -f "$binary_path"
155+
156+
echo ""
157+
echo "✅ Installation complete!"
158+
echo ""

src/claude-code/devcontainer-feature.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"id": "claude-code",
3-
"version": "1.0.0",
4-
"name": "claude-code (via npm)",
3+
"version": "2.0.0",
4+
"name": "claude-code",
55
"documentationURL": "http://github.com/devcontainers-extra/features/tree/main/src/claude-code",
66
"description": "Claude Code is an agentic coding tool that lives in your terminal",
77
"options": {

src/claude-code/install.sh

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,13 @@
22

33
set -e
44

5-
source ./library_scripts.sh
6-
7-
# nanolayer is a cli utility which keeps container layers as small as possible
8-
# source code: https://github.com/devcontainers-extra/nanolayer
9-
# `ensure_nanolayer` is a bash function that will find any existing nanolayer installations,
10-
# and if missing - will download a temporary copy that automatically get deleted at the end
11-
# of the script
12-
ensure_nanolayer nanolayer_location "v0.5.6"
13-
14-
# Example nanolayer installation via devcontainer-feature
15-
$nanolayer_location \
16-
install \
17-
devcontainer-feature \
18-
"ghcr.io/devcontainers-extra/features/npm-package:1.0.4" \
19-
--option package='@anthropic-ai/claude-code' --option version="$VERSION"
5+
# Install via Anthropic's recommended method
6+
# Script downloaded from them:
7+
# https://storage.googleapis.com/claude-code-dist-86c565f3-f756-42ad-8dfa-d59b1c096819/claude-code-releases/bootstrap.sh
8+
bash "$(dirname "$0")/bootstrap.sh" "$VERSION"
9+
10+
# Copy the binary to /usr/local/bin for global access
11+
# It's installed in /root/.local/bin/claude and there's no option to override the install location
12+
cp "$HOME/.local/bin/claude" /usr/local/bin/claude
2013

2114
echo 'Done!'

src/claude-code/library_scripts.sh

Lines changed: 0 additions & 173 deletions
This file was deleted.

0 commit comments

Comments
 (0)