Skip to content

Commit 4f0d020

Browse files
jdputschadiJeff Putsch
andauthored
Add RHEL support to node feature. (#823)
* node feature working for RHEL and family, passing all tests * RHEL support added * fix linting error by removing debugging code. * incorporate PR feedback * address PR feedback --------- Co-authored-by: Jeff Putsch <jputsch@analog.com>
1 parent 19d1edc commit 4f0d020

20 files changed

Lines changed: 570 additions & 68 deletions

src/node/NOTES.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ Alternatively, you can start up an interactive shell which will in turn source `
2020

2121
## OS Support
2222

23-
This Feature should work on recent versions of Debian/Ubuntu-based distributions with the `apt` package manager installed.
23+
Debian/Ubuntu, RedHat Enterprise Linux, Fedora, Alma, and Rocky Linux distributions with the `apt`, `yum`, `dnf`, or `microdnf` package manager installed.
24+
25+
**Note**: RedHat 7 Family (RedHat, CentOS, etc.) must use Node versions less than 18 due to its system libraries and long-term support (LTS) policies.
2426

2527
`bash` is required to execute the `install.sh` script.

src/node/devcontainer-feature.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"id": "node",
3-
"version": "1.3.1",
3+
"version": "1.4.0",
44
"name": "Node.js (via nvm), yarn and pnpm",
55
"documentationURL": "https://github.com/devcontainers/features/tree/main/src/node",
66
"description": "Installs Node.js, nvm, yarn, pnpm, and needed dependencies.",
@@ -53,4 +53,4 @@
5353
"installsAfter": [
5454
"ghcr.io/devcontainers/features/common-utils"
5555
]
56-
}
56+
}

src/node/install.sh

Lines changed: 213 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -21,61 +21,136 @@ UPDATE_RC="${UPDATE_RC:-"true"}"
2121

2222
set -e
2323

24-
# Clean up
25-
rm -rf /var/lib/apt/lists/*
26-
2724
if [ "$(id -u)" -ne 0 ]; then
2825
echo -e 'Script must be run as root. Use sudo, su, or add "USER root" to your Dockerfile before running this script.'
2926
exit 1
3027
fi
3128

29+
# Bring in ID, ID_LIKE, VERSION_ID, VERSION_CODENAME
30+
. /etc/os-release
31+
# Get an adjusted ID independent of distro variants
32+
MAJOR_VERSION_ID=$(echo ${VERSION_ID} | cut -d . -f 1)
33+
if [ "${ID}" = "debian" ] || [ "${ID_LIKE}" = "debian" ]; then
34+
ADJUSTED_ID="debian"
35+
elif [[ "${ID}" = "rhel" || "${ID}" = "fedora" || "${ID}" = "mariner" || "${ID_LIKE}" = *"rhel"* || "${ID_LIKE}" = *"fedora"* || "${ID_LIKE}" = *"mariner"* ]]; then
36+
ADJUSTED_ID="rhel"
37+
if [[ "${ID}" = "rhel" ]] || [[ "${ID}" = *"alma"* ]] || [[ "${ID}" = *"rocky"* ]]; then
38+
VERSION_CODENAME="rhel${MAJOR_VERSION_ID}"
39+
else
40+
VERSION_CODENAME="${ID}${MAJOR_VERSION_ID}"
41+
fi
42+
else
43+
echo "Linux distro ${ID} not supported."
44+
exit 1
45+
fi
46+
47+
# Setup INSTALL_CMD & PKG_MGR_CMD
48+
if type apt-get > /dev/null 2>&1; then
49+
PKG_MGR_CMD=apt-get
50+
INSTALL_CMD="${PKG_MGR_CMD} -y install --no-install-recommends"
51+
elif type microdnf > /dev/null 2>&1; then
52+
PKG_MGR_CMD=microdnf
53+
INSTALL_CMD="${PKG_MGR_CMD} -y install --refresh --best --nodocs --noplugins --setopt=install_weak_deps=0"
54+
elif type dnf > /dev/null 2>&1; then
55+
PKG_MGR_CMD=dnf
56+
INSTALL_CMD="${PKG_MGR_CMD} -y install"
57+
else
58+
PKG_MGR_CMD=yum
59+
INSTALL_CMD="${PKG_MGR_CMD} -y install"
60+
fi
61+
62+
# Clean up
63+
clean_up() {
64+
case ${ADJUSTED_ID} in
65+
debian)
66+
rm -rf /var/lib/apt/lists/*
67+
;;
68+
rhel)
69+
rm -rf /var/cache/dnf/* /var/cache/yum/*
70+
rm -f /etc/yum.repos.d/yarn.repo
71+
;;
72+
esac
73+
}
74+
clean_up
75+
3276
# Ensure that login shells get the correct path if the user updated the PATH using ENV.
3377
rm -f /etc/profile.d/00-restore-env.sh
3478
echo "export PATH=${PATH//$(sh -lc 'echo $PATH')/\$PATH}" > /etc/profile.d/00-restore-env.sh
3579
chmod +x /etc/profile.d/00-restore-env.sh
3680

37-
# Determine the appropriate non-root user
38-
if [ "${USERNAME}" = "auto" ] || [ "${USERNAME}" = "automatic" ]; then
39-
USERNAME=""
40-
POSSIBLE_USERS=("vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd)")
41-
for CURRENT_USER in "${POSSIBLE_USERS[@]}"; do
42-
if id -u ${CURRENT_USER} > /dev/null 2>&1; then
43-
USERNAME=${CURRENT_USER}
44-
break
45-
fi
46-
done
47-
if [ "${USERNAME}" = "" ]; then
48-
USERNAME=root
49-
fi
50-
elif [ "${USERNAME}" = "none" ] || ! id -u ${USERNAME} > /dev/null 2>&1; then
51-
USERNAME=root
52-
fi
53-
5481
updaterc() {
82+
local _bashrc
83+
local _zshrc
5584
if [ "${UPDATE_RC}" = "true" ]; then
56-
echo "Updating /etc/bash.bashrc and /etc/zsh/zshrc..."
57-
if [[ "$(cat /etc/bash.bashrc)" != *"$1"* ]]; then
58-
echo -e "$1" >> /etc/bash.bashrc
85+
case $ADJUSTED_ID in
86+
debian)
87+
_bashrc=/etc/bash.bashrc
88+
_zshrc=/etc/zsh/zshrc
89+
;;
90+
rhel)
91+
_bashrc=/etc/bashrc
92+
_zshrc=/etc/zshrc
93+
;;
94+
esac
95+
echo "Updating ${_bashrc} and ${_zshrc}..."
96+
if [[ "$(cat ${_bashrc})" != *"$1"* ]]; then
97+
echo -e "$1" >> "${_bashrc}"
5998
fi
60-
if [ -f "/etc/zsh/zshrc" ] && [[ "$(cat /etc/zsh/zshrc)" != *"$1"* ]]; then
61-
echo -e "$1" >> /etc/zsh/zshrc
99+
if [ -f "${_zshrc}" ] && [[ "$(cat ${_zshrc})" != *"$1"* ]]; then
100+
echo -e "$1" >> "${_zshrc}"
62101
fi
63102
fi
64103
}
65104

66-
apt_get_update() {
67-
if [ "$(find /var/lib/apt/lists/* | wc -l)" = "0" ]; then
68-
echo "Running apt-get update..."
69-
apt-get update -y
70-
fi
105+
pkg_mgr_update() {
106+
case $ADJUSTED_ID in
107+
debian)
108+
if [ "$(find /var/lib/apt/lists/* 2>/dev/null | wc -l)" = "0" ]; then
109+
echo "Running apt-get update..."
110+
${PKG_MGR_CMD} update -y
111+
fi
112+
;;
113+
rhel)
114+
if [ ${PKG_MGR_CMD} = "microdnf" ]; then
115+
if [ "$(ls /var/cache/yum/* 2>/dev/null | wc -l)" = 0 ]; then
116+
echo "Running ${PKG_MGR_CMD} makecache ..."
117+
${PKG_MGR_CMD} makecache
118+
fi
119+
else
120+
if [ "$(ls /var/cache/${PKG_MGR_CMD}/* 2>/dev/null | wc -l)" = 0 ]; then
121+
echo "Running ${PKG_MGR_CMD} check-update ..."
122+
set +e
123+
stderr_messages=$(${PKG_MGR_CMD} -q check-update 2>&1)
124+
rc=$?
125+
# centos 7 sometimes returns a status of 100 when it apears to work.
126+
if [ $rc != 0 ] && [ $rc != 100 ]; then
127+
echo "(Error) ${PKG_MGR_CMD} check-update produced the following error message(s):"
128+
echo "${stderr_messages}"
129+
exit 1
130+
fi
131+
set -e
132+
fi
133+
fi
134+
;;
135+
esac
71136
}
72137

73138
# Checks if packages are installed and installs them if not
74139
check_packages() {
75-
if ! dpkg -s "$@" > /dev/null 2>&1; then
76-
apt_get_update
77-
apt-get -y install --no-install-recommends "$@"
78-
fi
140+
case ${ADJUSTED_ID} in
141+
debian)
142+
if ! dpkg -s "$@" > /dev/null 2>&1; then
143+
pkg_mgr_update
144+
${INSTALL_CMD} "$@"
145+
fi
146+
;;
147+
rhel)
148+
if ! rpm -q "$@" > /dev/null 2>&1; then
149+
pkg_mgr_update
150+
${INSTALL_CMD} "$@"
151+
fi
152+
;;
153+
esac
79154
}
80155

81156
# Figure out correct version of a three part version number is not passed
@@ -112,35 +187,95 @@ find_version_from_git_tags() {
112187
echo "${variable_name}=${!variable_name}"
113188
}
114189

190+
install_yarn() {
191+
if [ "${ADJUSTED_ID}" = "debian" ]; then
192+
# for backward compatiblity with existing devcontainer features, install yarn
193+
# via apt-get on Debian systems
194+
if ! type yarn >/dev/null 2>&1; then
195+
# Import key safely (new method rather than deprecated apt-key approach) and install
196+
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | gpg --dearmor > /usr/share/keyrings/yarn-archive-keyring.gpg
197+
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/yarn-archive-keyring.gpg] https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list
198+
apt-get update
199+
apt-get -y install --no-install-recommends yarn
200+
else
201+
echo "Yarn is already installed."
202+
fi
203+
else
204+
local _ver=${1:-node}
205+
# on non-debian systems, prefer corepack, fallback to npm based installation of yarn...
206+
# Try to leverage corepack if possible
207+
# From https://yarnpkg.com:
208+
# The preferred way to manage Yarn is by-project and through Corepack, a tool
209+
# shipped by default with Node.js. Modern releases of Yarn aren't meant to be
210+
# installed globally, or from npm.
211+
if ! bash -c ". '${NVM_DIR}/nvm.sh' && nvm use ${_ver} && type yarn >/dev/null 2>&1"; then
212+
if bash -c ". '${NVM_DIR}/nvm.sh' && nvm use ${_ver} && type corepack >/dev/null 2>&1"; then
213+
su ${USERNAME} -c "umask 0002 && . '${NVM_DIR}/nvm.sh' && nvm use ${_ver} && corepack enable"
214+
fi
215+
if ! bash -c ". '${NVM_DIR}/nvm.sh' && nvm use ${_ver} && type yarn >/dev/null 2>&1"; then
216+
# Yum/DNF want to install nodejs dependencies, we'll use NPM to install yarn
217+
su ${USERNAME} -c "umask 0002 && . '${NVM_DIR}/nvm.sh' && nvm use ${_ver} && npm install --global yarn"
218+
fi
219+
else
220+
echo "Yarn already installed."
221+
fi
222+
fi
223+
}
224+
225+
# Mariner does not have awk installed by default, this can cause
226+
# problems is username is auto* and later when we try to install
227+
# node via npm.
228+
if ! type awk >/dev/null 2>&1; then
229+
check_packages awk
230+
fi
231+
232+
# Determine the appropriate non-root user
233+
if [ "${USERNAME}" = "auto" ] || [ "${USERNAME}" = "automatic" ]; then
234+
USERNAME=""
235+
POSSIBLE_USERS=("vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd)")
236+
for CURRENT_USER in "${POSSIBLE_USERS[@]}"; do
237+
if id -u ${CURRENT_USER} > /dev/null 2>&1; then
238+
USERNAME=${CURRENT_USER}
239+
break
240+
fi
241+
done
242+
if [ "${USERNAME}" = "" ]; then
243+
USERNAME=root
244+
fi
245+
elif [ "${USERNAME}" = "none" ] || ! id -u ${USERNAME} > /dev/null 2>&1; then
246+
USERNAME=root
247+
fi
248+
115249
# Ensure apt is in non-interactive to avoid prompts
116250
export DEBIAN_FRONTEND=noninteractive
117251

118-
. /etc/os-release
119-
if [[ "bionic" = *"${VERSION_CODENAME}"* ]]; then
120-
if [[ "${NODE_VERSION}" =~ "18" ]] || [[ "${NODE_VERSION}" = "lts" ]]; then
121-
echo "(!) Unsupported distribution version '${VERSION_CODENAME}' for Node 18. Details: https://github.com/nodejs/node/issues/42351#issuecomment-1068424442"
252+
if ( [ -n "${VERSION_CODENAME}" ] && [[ "bionic" = *"${VERSION_CODENAME}"* ]] ) || [[ "rhel7" = *"${ADJUSTED_ID}${MAJOR_VERSION_ID}"* ]]; then
253+
node_major_version=$(echo "${NODE_VERSION}" | cut -d . -f 1)
254+
if [[ "${node_major_version}" -ge 18 ]] || [[ "${NODE_VERSION}" = "lts" ]] || [[ "${NODE_VERSION}" = "latest" ]]; then
255+
echo "(!) Unsupported distribution version '${VERSION_CODENAME}' for Node >= 18. Details: https://github.com/nodejs/node/issues/42351#issuecomment-1068424442"
122256
exit 1
123257
fi
124258
fi
125259

126260
# Install dependencies
127-
check_packages apt-transport-https curl ca-certificates tar gnupg2 dirmngr
261+
case ${ADJUSTED_ID} in
262+
debian)
263+
check_packages apt-transport-https curl ca-certificates tar gnupg2 dirmngr
264+
;;
265+
rhel)
266+
check_packages ca-certificates tar gnupg2 which findutils util-linux tar
267+
# minimal RHEL installs may not include curl, or includes curl-minimal instead.
268+
# Install curl if the "curl" command is not present.
269+
if ! type curl > /dev/null 2>&1; then
270+
check_packages curl
271+
fi
272+
;;
273+
esac
128274

129275
if ! type git > /dev/null 2>&1; then
130276
check_packages git
131277
fi
132278

133-
# Install yarn
134-
if type yarn > /dev/null 2>&1; then
135-
echo "Yarn already installed."
136-
else
137-
# Import key safely (new method rather than deprecated apt-key approach) and install
138-
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | gpg --dearmor > /usr/share/keyrings/yarn-archive-keyring.gpg
139-
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/yarn-archive-keyring.gpg] https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list
140-
apt-get update
141-
apt-get -y install --no-install-recommends yarn
142-
fi
143-
144279
# Adjust node version if required
145280
if [ "${NODE_VERSION}" = "none" ]; then
146281
export NODE_VERSION=
@@ -159,6 +294,7 @@ umask 0002
159294
# Do not update profile - we'll do this manually
160295
export PROFILE=/dev/null
161296
curl -so- "https://raw.githubusercontent.com/nvm-sh/nvm/v${NVM_VERSION}/install.sh" | bash
297+
162298
source "${NVM_DIR}/nvm.sh"
163299
if [ "${NODE_VERSION}" != "" ]; then
164300
nvm alias default "${NODE_VERSION}"
@@ -204,6 +340,9 @@ else
204340
fi
205341
fi
206342

343+
# Possibly install yarn (puts yarn in per-Node install on RHEL, uses system yarn on Debian)
344+
install_yarn
345+
207346
# Additional node versions to be installed but not be set as
208347
# default we can assume the nvm is the group owner of the nvm
209348
# directory and the sticky bit on directories so any installed
@@ -214,6 +353,8 @@ if [ ! -z "${ADDITIONAL_VERSIONS}" ]; then
214353
read -a additional_versions <<< "$ADDITIONAL_VERSIONS"
215354
for ver in "${additional_versions[@]}"; do
216355
su ${USERNAME} -c "umask 0002 && . '$NVM_DIR/nvm.sh' && nvm install '${ver}'"
356+
# possibly install yarn (puts yarn in per-Node install on RHEL, uses system yarn on Debian)
357+
install_yarn "${ver}"
217358
done
218359

219360
# Ensure $NODE_VERSION is on the $PATH
@@ -224,14 +365,17 @@ if [ ! -z "${ADDITIONAL_VERSIONS}" ]; then
224365
fi
225366

226367
# Install pnpm
227-
if type pnpm > /dev/null 2>&1; then
368+
if bash -c ". '${NVM_DIR}/nvm.sh' && type pnpm >/dev/null 2>&1"; then
228369
echo "pnpm already installed."
229370
else
230-
if type npm > /dev/null 2>&1; then
231-
[ ! -z "$http_proxy" ] && npm set proxy="$http_proxy"
232-
[ ! -z "$https_proxy" ] && npm set https-proxy="$https_proxy"
233-
[ ! -z "$no_proxy" ] && npm set noproxy="$no_proxy"
234-
npm install -g pnpm
371+
if bash -c ". '${NVM_DIR}/nvm.sh' && type npm >/dev/null 2>&1"; then
372+
(
373+
. "${NVM_DIR}/nvm.sh"
374+
[ ! -z "$http_proxy" ] && npm set proxy="$http_proxy"
375+
[ ! -z "$https_proxy" ] && npm set https-proxy="$https_proxy"
376+
[ ! -z "$no_proxy" ] && npm set noproxy="$no_proxy"
377+
npm install -g pnpm
378+
)
235379
else
236380
echo "Skip installing pnpm because npm is missing"
237381
fi
@@ -248,21 +392,29 @@ if [ "${INSTALL_TOOLS_FOR_NODE_GYP}" = "true" ]; then
248392
to_install="${to_install} gcc"
249393
fi
250394
if ! type g++ > /dev/null 2>&1; then
251-
to_install="${to_install} g++"
395+
if [ ${ADJUSTED_ID} = "debian" ]; then
396+
to_install="${to_install} g++"
397+
elif [ ${ADJUSTED_ID} = "rhel" ]; then
398+
to_install="${to_install} gcc-c++"
399+
fi
252400
fi
253401
if ! type python3 > /dev/null 2>&1; then
254-
to_install="${to_install} python3-minimal"
402+
if [ ${ADJUSTED_ID} = "debian" ]; then
403+
to_install="${to_install} python3-minimal"
404+
elif [ ${ADJUSTED_ID} = "rhel" ]; then
405+
to_install="${to_install} python3"
406+
fi
255407
fi
256408
if [ ! -z "${to_install}" ]; then
257-
apt_get_update
258-
apt-get -y install ${to_install}
409+
pkg_mgr_update
410+
check_packages ${to_install}
259411
fi
260412
fi
261413

262414

263415
# Clean up
264416
su ${USERNAME} -c "umask 0002 && . '$NVM_DIR/nvm.sh' && nvm clear-cache"
265-
rm -rf /var/lib/apt/lists/*
417+
clean_up
266418

267419
# Ensure privs are correct for installed node versions. Unfortunately the
268420
# way nvm installs node versions pulls privs from the tar which does not

0 commit comments

Comments
 (0)