Skip to content

Instantly share code, notes, and snippets.

@kibotu
Last active July 12, 2026 09:59
Show Gist options
  • Select an option

  • Save kibotu/3b55148c2c2a58fb1ddbd90dd66380cc to your computer and use it in GitHub Desktop.

Select an option

Save kibotu/3b55148c2c2a58fb1ddbd90dd66380cc to your computer and use it in GitHub Desktop.
Install LTS on RaspberryPi 5 aarch64
#!/usr/bin/env bash
#
# install-node.sh
# Installs the latest Node.js Active LTS release (official arm64 build)
# for 64-bit Raspberry Pi OS / Debian aarch64 (e.g. Raspberry Pi 5, Pi 4 64-bit, Pi 400, etc).
#
# Originally based on a script by Steven de Salas, itself based on
# Richard Stanley's Node-MongoDb-Pi script for the armv6l Pi Zero.
# Rewritten for 64-bit Pi hardware using official nodejs.org arm64 builds
# (the unofficial-builds armv6l tarball is no longer needed on 64-bit Pis).
#
# Usage:
# ./install-node.sh # installs latest Active LTS
# NODE_VERSION=v22.20.0 ./install-node.sh # pin a specific version
set -euo pipefail
INSTALL_ROOT="/opt/nodejs"
BIN_DIR="/usr/local/bin"
TMP_DIR="$(mktemp -d /tmp/node-install.XXXXXX)"
log() { printf '\033[1;34m[node-install]\033[0m %s\n' "$*"; }
err() { printf '\033[1;31m[node-install]\033[0m %s\n' "$*" >&2; }
cleanup() { rm -rf "$TMP_DIR"; }
trap cleanup EXIT
# ---------------------------------------------------------------------------
# 0. Sanity checks
# ---------------------------------------------------------------------------
if [[ $EUID -eq 0 ]]; then
err "Please run this as a normal user (it will sudo when needed), not as root directly."
exit 1
fi
for cmd in curl tar sudo python3; do
if ! command -v "$cmd" >/dev/null 2>&1; then
err "Required command '$cmd' not found. Install it first, e.g.: sudo apt update && sudo apt install -y $cmd"
exit 1
fi
done
ARCH_RAW="$(uname -m)"
case "$ARCH_RAW" in
aarch64|arm64)
NODE_ARCH="arm64"
;;
armv7l)
NODE_ARCH="armv7l"
;;
armv6l)
err "armv6l (32-bit Pi Zero/1) detected. Official Node.js no longer ships this build;"
err "you need the unofficial-builds.nodejs.org armv6l tarball instead of this script."
exit 1
;;
x86_64)
NODE_ARCH="x64"
;;
*)
err "Unsupported architecture: $ARCH_RAW"
exit 1
;;
esac
log "Detected architecture: $ARCH_RAW -> node arch '$NODE_ARCH'"
# ---------------------------------------------------------------------------
# 1. Resolve the version to install
# ---------------------------------------------------------------------------
if [[ -z "${NODE_VERSION:-}" ]]; then
log "No NODE_VERSION set, looking up latest Active LTS release..."
# Download the full release index to a file first (rather than piping straight
# into grep/head), so curl always finishes writing and can't be killed by
# SIGPIPE from a downstream reader closing early (which curl reports as exit 23).
INDEX_FILE="$TMP_DIR/index.json"
curl -fsSL --retry 3 -o "$INDEX_FILE" https://nodejs.org/dist/index.json
# Parse with python3's json module rather than grep/sed: the index's exact
# formatting (single-line vs pretty-printed) isn't something to rely on, and
# a real parser handles it regardless. The index is newest-first, and each
# LTS release has "lts" set to its codename string; Current (non-LTS)
# releases have "lts": false.
NODE_VERSION="$(
python3 - "$INDEX_FILE" <<'PYEOF'
import json, sys
with open(sys.argv[1]) as f:
releases = json.load(f)
for r in releases:
if r.get("lts"):
print(r["version"])
break
PYEOF
)"
if [[ -z "$NODE_VERSION" ]]; then
err "Could not auto-detect the latest LTS version. Set NODE_VERSION manually, e.g.:"
err " NODE_VERSION=v24.18.0 $0"
exit 1
fi
fi
log "Target Node.js version: $NODE_VERSION"
TARBALL="node-${NODE_VERSION}-linux-${NODE_ARCH}.tar.xz"
BASE_URL="https://nodejs.org/dist/${NODE_VERSION}"
DOWNLOAD_URL="${BASE_URL}/${TARBALL}"
SHASUMS_URL="${BASE_URL}/SHASUMS256.txt"
# ---------------------------------------------------------------------------
# 2. Skip if already installed
# ---------------------------------------------------------------------------
if command -v node >/dev/null 2>&1; then
CURRENT_VERSION="$(node --version || true)"
if [[ "$CURRENT_VERSION" == "$NODE_VERSION" ]]; then
log "Node.js $NODE_VERSION is already installed at $(command -v node). Nothing to do."
exit 0
fi
log "Found existing Node.js $CURRENT_VERSION, will replace with $NODE_VERSION."
fi
# ---------------------------------------------------------------------------
# 3. Download + verify + extract
# ---------------------------------------------------------------------------
log "Downloading $DOWNLOAD_URL"
curl -fsSL --retry 3 -o "$TMP_DIR/$TARBALL" "$DOWNLOAD_URL"
log "Verifying checksum"
curl -fsSL --retry 3 -o "$TMP_DIR/SHASUMS256.txt" "$SHASUMS_URL"
EXPECTED_SHA="$(grep " ${TARBALL}\$" "$TMP_DIR/SHASUMS256.txt" | awk '{print $1}')"
if [[ -z "$EXPECTED_SHA" ]]; then
err "Could not find checksum entry for $TARBALL - aborting for safety."
exit 1
fi
ACTUAL_SHA="$(sha256sum "$TMP_DIR/$TARBALL" | awk '{print $1}')"
if [[ "$EXPECTED_SHA" != "$ACTUAL_SHA" ]]; then
err "Checksum mismatch! Expected $EXPECTED_SHA, got $ACTUAL_SHA. Aborting."
exit 1
fi
log "Checksum OK"
log "Extracting"
tar -xf "$TMP_DIR/$TARBALL" -C "$TMP_DIR"
EXTRACTED_DIR="$TMP_DIR/node-${NODE_VERSION}-linux-${NODE_ARCH}"
# ---------------------------------------------------------------------------
# 4. Install into /opt, versioned, with a stable "current" symlink
# ---------------------------------------------------------------------------
VERSIONED_TARGET="${INSTALL_ROOT}-${NODE_VERSION}"
log "Installing to $VERSIONED_TARGET"
sudo rm -rf "$VERSIONED_TARGET"
sudo mv "$EXTRACTED_DIR" "$VERSIONED_TARGET"
log "Updating $INSTALL_ROOT symlink -> $VERSIONED_TARGET"
sudo ln -sfn "$VERSIONED_TARGET" "$INSTALL_ROOT"
# ---------------------------------------------------------------------------
# 5. Symlink binaries onto PATH
# ---------------------------------------------------------------------------
log "Linking binaries into $BIN_DIR"
for bin in node npm npx corepack; do
if [[ -e "$INSTALL_ROOT/bin/$bin" ]]; then
sudo ln -sf "$INSTALL_ROOT/bin/$bin" "$BIN_DIR/$bin"
fi
done
# ---------------------------------------------------------------------------
# 6. Verify
# ---------------------------------------------------------------------------
hash -r
log "Installed:"
node --version
npm --version
log "Done. Node.js ${NODE_VERSION} is installed at ${VERSIONED_TARGET}, symlinked via ${INSTALL_ROOT} and ${BIN_DIR}."
log "To roll back, re-run with NODE_VERSION=<old-version>, or manually repoint the ${INSTALL_ROOT} symlink."
@kibotu

kibotu commented Jul 12, 2026

Copy link
Copy Markdown
Author
scp install-node.sh 192.168.178.XXX:~/
ssh 192.168.178.XXX './install-node.sh'

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment