From 41d52782e71af9a7904a68b8b95583ecd369f9f4 Mon Sep 17 00:00:00 2001 From: Jason Garber Date: Mon, 30 Mar 2026 09:27:30 -0400 Subject: [PATCH 1/3] Node.js Feature installs from prebuilt binaries MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Nodesource APT repository has been misbehaving recently (e.g. a recent version of Node.js just went missing…?), so this change switches to installing Node.js from prebuilt binaries directly from the Node.js project. --- src/node/README.md | 8 +-- src/node/devcontainer-feature.json | 15 +++-- src/node/install.sh | 102 +++++++++++++++++------------ 3 files changed, 76 insertions(+), 49 deletions(-) diff --git a/src/node/README.md b/src/node/README.md index 1a3b50e..c59e5b2 100644 --- a/src/node/README.md +++ b/src/node/README.md @@ -1,12 +1,12 @@ # node -Install [Node.js](https://nodejs.org) from [Nodesource's DEB repository](https://deb.nodesource.com). +Install [Node.js](https://nodejs.org) from prebuilt binaries. ## Usage ```json "features": { - "ghcr.io/CargoSense/devcontainer-features/node:3": {} + "ghcr.io/CargoSense/devcontainer-features/node:4": {} } ``` @@ -14,8 +14,8 @@ Install [Node.js](https://nodejs.org) from [Nodesource's DEB repository](https:/ | Option ID | Description | Type | Default Value | |:----------|:--------------------------------|:-------|:--------------| -| `version` | The Node.js version to install. | string | `automatic` | +| `version` | The Node.js version to install. | string | `latest` | ## OS Support -This Feature should work on recent versions of Debian/Ubuntu and Linux distributions using the [apt](https://wiki.debian.org/AptCLI) management tool. +This Feature should work on recent versions of Debian/Ubuntu and Linux distributions using the [apt](https://wiki.debian.org/AptCLI) management tool and on architectures for which Node.js provides prebuilt binaries. diff --git a/src/node/devcontainer-feature.json b/src/node/devcontainer-feature.json index cc22b1a..d30e8e6 100644 --- a/src/node/devcontainer-feature.json +++ b/src/node/devcontainer-feature.json @@ -1,19 +1,26 @@ { "name": "Node.js", "id": "node", - "version": "3.0.0", - "description": "Install Node.js from Nodesource's DEB repository.", + "version": "4.0.0", + "description": "Install Node.js from prebuilt binaries.", "documentationURL": "https://github.com/CargoSense/devcontainer-features/tree/main/src/node", "licenseURL": "https://github.com/CargoSense/devcontainer-features/blob/main/LICENSE", "options": { "version": { "type": "string", - "default": "automatic", + "default": "latest", "description": "The Node.js version to install." } }, "containerEnv": { - "NPM_CONFIG_CACHE": "/usr/local/npm" + "NPM_CONFIG_CACHE": "/usr/local/npm", + "NODE_HOME": "/usr/local/share/node", + "PATH": "/usr/local/share/node/bin:${PATH}" + }, + "dependsOn": { + "ghcr.io/CargoSense/devcontainer-features/apt-packages:1": { + "packages": "libatomic1" + } }, "installsAfter": [ "ghcr.io/devcontainers/features/common-utils" diff --git a/src/node/install.sh b/src/node/install.sh index 99b2acc..fb95796 100755 --- a/src/node/install.sh +++ b/src/node/install.sh @@ -2,18 +2,31 @@ set -e -NODE_VERSION="${VERSION:-"automatic"}" -NODE_MAJOR_VERSION="24" - -pkg="nodejs" - -if [[ "${NODE_VERSION}" != "automatic" ]]; then - NODE_MAJOR_VERSION="$(echo "${NODE_VERSION}" | cut -d. -f1)" - pkg="${pkg}=${NODE_VERSION}-1nodesource1" +NODE_VERSION="${VERSION:-"latest"}" + +USERNAME="${USERNAME:-"${_REMOTE_USER:-"automatic"}"}" + +# Determine the appropriate non-root user. +if [[ "${USERNAME}" = "automatic" ]]; then + USERNAME="" + POSSIBLE_USERS=("vscode" "node" "$(getent passwd 1000 | cut -d: -f1)") + + for CURRENT_USER in "${POSSIBLE_USERS[@]}"; do + if id -u "${CURRENT_USER}" >/dev/null 2>&1; then + USERNAME="${CURRENT_USER}" + break + fi + done + + if [[ "${USERNAME}" = "" ]]; then + USERNAME="root" + fi +elif ! id -u "${USERNAME}" >/dev/null 2>&1; then + USERNAME="root" fi curl_installed="" -gpg_installed="" +xz_installed="" if ! type curl >/dev/null 2>&1; then apt update --yes @@ -22,49 +35,56 @@ if ! type curl >/dev/null 2>&1; then curl_installed="true" fi -if ! type gpg >/dev/null 2>&1; then +if ! type xz >/dev/null 2>&1; then apt update --yes - apt install --yes gnupg + apt install --yes xz-utils - gpg_installed="true" + xz_installed="true" fi -apt_sources_snippet="$(cat << EOF -Types: deb -URIs: https://deb.nodesource.com/node_${NODE_MAJOR_VERSION}.x -Suites: nodistro -Components: main -Signed-By: /etc/apt/keyrings/nodesource.gpg -EOF -)" - -install -dm 755 /etc/apt/keyrings -curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg -echo "${apt_sources_snippet}" | tee /etc/apt/sources.list.d/nodesource.sources +# Normalize architecture +arch="$(dpkg --print-architecture)" +if [[ "${arch}" = "amd64" ]] || [[ "${arch}" = "x86_64" ]] || [[ "${arch}" = "i386" ]]; then + arch="x64" +fi -if [[ -n "${curl_installed}" ]]; then - apt purge curl --autoremove --yes - rm -rf /var/lib/apt/lists/* +# Normalize Node.js version string +if [[ "${NODE_VERSION}" != "latest" ]] && [[ "${NODE_VERSION}" != "v"* ]]; then + NODE_VERSION="v${NODE_VERSION}" fi -if [[ -n "${gpg_installed}" ]]; then - apt purge gnupg --autoremove --yes - rm -rf /var/lib/apt/lists/* +# Configure "node" group +if ! grep -e "^node:" /etc/group >/dev/null 2>&1; then + groupadd --system node fi +usermod --append --groups node "${USERNAME}" + +# Install Node.js +umask 0002 +mkdir -p "${NODE_HOME:?}" +curl -fsSL "https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}-linux-${arch}.tar.xz" | tar xf - -C "${NODE_HOME}" -J --strip-components 1 -apt update --yes -apt install --yes "${pkg}" -rm -rf /var/lib/apt/lists/* +chown -R "${USERNAME}:node" "${NODE_HOME}" +chmod g+rws "${NODE_HOME}" -node_rc_snippet="$(cat << EOF -export NODE_VERSION="$(node -v | cut -c2-)" -EOF -)" +# Configure shell +rc_snippet="export NODE_VERSION=\"$(node -v | cut -c2-)\"" -if [[ "$(cat /etc/bash.bashrc)" != *"${node_rc_snippet}"* ]]; then - echo "${node_rc_snippet}" >> /etc/bash.bashrc +if [[ -f /etc/bash.bashrc ]] && ! grep -q "${rc_snippet}" /etc/bash.bashrc; then + echo "${rc_snippet}" >>/etc/bash.bashrc fi -if [[ -f "/etc/zsh/zshrc" ]] && [[ "$(cat /etc/zsh/zshrc)" != *"${node_rc_snippet}"* ]]; then - echo "${node_rc_snippet}" >> /etc/zsh/zshrc +if [[ -f /etc/zsh/zshrc ]] && ! grep -q "${rc_snippet}" /etc/zsh/zshrc; then + echo "${rc_snippet}" >>/etc/zsh/zshrc +fi + +# Cleanup +if [[ -n "${curl_installed}" ]]; then + apt purge curl --autoremove --yes + rm -rf /var/lib/apt/lists/* +fi + +if [[ -n "${xz_installed}" ]]; then + apt purge xz-utils --autoremove --yes + rm -rf /var/lib/apt/lists/* fi From ed30f8fd5c36b38b19415a6f6a9b6cc814711eed Mon Sep 17 00:00:00 2001 From: Jason Garber Date: Mon, 30 Mar 2026 09:32:13 -0400 Subject: [PATCH 2/3] Latest Node.js is v25 --- test/node/test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/node/test.sh b/test/node/test.sh index 06d6280..68528e4 100755 --- a/test/node/test.sh +++ b/test/node/test.sh @@ -7,7 +7,7 @@ set -e source dev-container-features-test-lib # Feature-specific tests -check "version" bash -c "node --version | grep -E 'v24\..+'" +check "version" bash -c "node --version | grep -E 'v25\..+'" check "which node" bash -c "which node | grep /usr/bin/node" # Report result From 577b51cdd1c677500e16ea4ecb320a0fe97cc786 Mon Sep 17 00:00:00 2001 From: Jason Garber Date: Mon, 30 Mar 2026 13:39:05 +0000 Subject: [PATCH 3/3] Fix path to Node.js executable in test --- test/node/test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/node/test.sh b/test/node/test.sh index 68528e4..95d5e83 100755 --- a/test/node/test.sh +++ b/test/node/test.sh @@ -8,7 +8,7 @@ source dev-container-features-test-lib # Feature-specific tests check "version" bash -c "node --version | grep -E 'v25\..+'" -check "which node" bash -c "which node | grep /usr/bin/node" +check "which node" bash -c "which node | grep /usr/local/share/node/bin/node" # Report result reportResults