From ea9bb2bee5f04137dffeccf6a9a4399429fa6566 Mon Sep 17 00:00:00 2001 From: A <258483684+la14-1@users.noreply.github.com> Date: Sat, 21 Feb 2026 15:44:39 -0800 Subject: [PATCH] fix: use direct Node.js binary tarball on Fly instead of apt/npm/n (#1637) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the Node.js install chain (apt nodejs+npm → npm install -g n → n 22 → symlinks) with a single curl of the v22 binary tarball from nodejs.org. Eliminates python3 dependency, npm bloat, and the n version manager. Bun is installed first as the primary package manager. Fly-only change — other clouds unchanged pending validation. Co-authored-by: Claude Co-authored-by: Claude Opus 4.6 (1M context) --- cli/src/fly/agents.ts | 2 +- cli/src/fly/fly.ts | 13 +++++++------ test/mock-curl-script.sh | 4 ++-- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/cli/src/fly/agents.ts b/cli/src/fly/agents.ts index a2584d30..14f2ace1 100644 --- a/cli/src/fly/agents.ts +++ b/cli/src/fly/agents.ts @@ -129,7 +129,7 @@ async function installClaudeCode(): Promise { `export PATH="${claudePath}:$PATH"`, `if command -v claude >/dev/null 2>&1; then ${finalize}; exit 0; fi`, // Ensure Node.js for npm method - `if ! command -v node >/dev/null 2>&1; then DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends nodejs npm 2>/dev/null && npm install -g n && n 22 && ln -sf /usr/local/bin/node /usr/bin/node && ln -sf /usr/local/bin/npm /usr/bin/npm && ln -sf /usr/local/bin/npx /usr/bin/npx || true; fi`, + `if ! command -v node >/dev/null 2>&1; then curl -fsSL https://nodejs.org/dist/v22.15.0/node-v22.15.0-linux-x64.tar.xz | tar -xJ -C /usr/local --strip-components=1 || true; fi`, // Method 2: npm `echo "==> Installing Claude Code (method 2/2: npm)..."`, `npm install -g @anthropic-ai/claude-code || true`, diff --git a/cli/src/fly/fly.ts b/cli/src/fly/fly.ts index 2d76162b..e7a8456e 100644 --- a/cli/src/fly/fly.ts +++ b/cli/src/fly/fly.ts @@ -748,20 +748,21 @@ export async function waitForSsh(maxAttempts = 20): Promise { export async function waitForCloudInit(): Promise { await waitForSsh(); - logStep("Installing packages (Node.js, bun)..."); + logStep("Installing packages (bun, Node.js)..."); // Batch all package installs into a single remote script to avoid multiple // round-trips (each of which was previously a separate fly machine exec call). + // Install bun first (single binary, no deps), then Node.js as a direct binary + // download from nodejs.org — no apt nodejs/npm/python3, no NodeSource, no n. const setupScript = [ `echo "==> Installing base packages..."`, `export DEBIAN_FRONTEND=noninteractive`, - `apt-get update -y && apt-get install -y --no-install-recommends curl unzip git ca-certificates || true`, - `echo "==> Checking Node.js..."`, - `if ! command -v node >/dev/null 2>&1; then apt-get install -y --no-install-recommends nodejs npm || true; fi`, - `npm install -g n && n 22 && ln -sf /usr/local/bin/node /usr/bin/node && ln -sf /usr/local/bin/npm /usr/bin/npm && ln -sf /usr/local/bin/npx /usr/bin/npx || true`, - `echo "node: $(node --version 2>/dev/null || echo not installed)"`, + `apt-get update -y && apt-get install -y --no-install-recommends curl unzip git ca-certificates xz-utils || true`, `echo "==> Checking bun..."`, `if ! command -v bun >/dev/null 2>&1 && [ ! -f "$HOME/.bun/bin/bun" ]; then curl -fsSL https://bun.sh/install | bash || true; fi`, `for rc in ~/.bashrc ~/.zshrc; do grep -q '.bun/bin' "$rc" 2>/dev/null || echo 'export PATH="$HOME/.local/bin:$HOME/.bun/bin:$PATH"' >> "$rc"; done`, + `echo "==> Checking Node.js..."`, + `if ! command -v node >/dev/null 2>&1; then curl -fsSL https://nodejs.org/dist/v22.15.0/node-v22.15.0-linux-x64.tar.xz | tar -xJ -C /usr/local --strip-components=1 || true; fi`, + `echo "node: $(node --version 2>/dev/null || echo not installed)"`, ].join('\n'); try { diff --git a/test/mock-curl-script.sh b/test/mock-curl-script.sh index 26f2648d..292a4f28 100644 --- a/test/mock-curl-script.sh +++ b/test/mock-curl-script.sh @@ -34,7 +34,7 @@ _parse_args() { _maybe_inject_error() { [ -n "${MOCK_ERROR_SCENARIO:-}" ] || return 1 case "$URL" in - *openrouter.ai*|*raw.githubusercontent.com*|*claude.ai/install*|*bun.sh*|*nodesource*|*opencode*|*zeroclaw*|*pip.pypa.io*|*get.docker.com*|*npmjs.org*|*github.com/*/releases*) + *openrouter.ai*|*raw.githubusercontent.com*|*claude.ai/install*|*bun.sh*|*nodesource*|*nodejs.org*|*opencode*|*zeroclaw*|*pip.pypa.io*|*get.docker.com*|*npmjs.org*|*github.com/*/releases*) return 1 ;; esac case "${MOCK_ERROR_SCENARIO}" in @@ -65,7 +65,7 @@ _maybe_inject_error() { _handle_special_urls() { case "$URL" in - *claude.ai/install*|*bun.sh*|*nodesource*|*opencode*install*|*zeroclaw*install*|\ + *claude.ai/install*|*bun.sh*|*nodesource*|*nodejs.org*|*opencode*install*|*zeroclaw*install*|\ *pip.pypa.io*|*get.docker.com*|*install.python-poetry.org*|\ *npmjs.org*|*deb.nodesource.com*|*github.com/*/releases*|*cli.github.com*) printf '#!/bin/bash\nexit 0\n'