Skip to content

Commit 634062d

Browse files
feat: add bun features and bun-package helper
1 parent 611d590 commit 634062d

34 files changed

Lines changed: 1123 additions & 0 deletions

src/bun-curl/NOTES.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Additional Notes
2+
3+
- Website: [https://bun.com/](https://bun.com/)
4+
- GitHub: [https://github.com/oven-sh/bun](https://github.com/oven-sh/bun)
5+
6+
## Install Method
7+
8+
This feature installs Bun via the official curl installer, with `BUN_INSTALL=/usr/local` for system-wide binaries. Version pinning is supported by passing `bun-v<version>` to the install script when `version` is set.
9+
10+
## bunx shim
11+
12+
Provides a small shim at `/usr/local/bin/bunx` that forwards to `bun x` for consistency.
13+
14+
## Usage Examples
15+
16+
Default (latest):
17+
18+
```json
19+
"features": {
20+
"ghcr.io/devcontainers-extra/features/bun-curl:1": {}
21+
}
22+
```
23+
24+
Pin a specific version:
25+
26+
```json
27+
"features": {
28+
"ghcr.io/devcontainers-extra/features/bun-curl:1": {
29+
"version": "1.2.21"
30+
}
31+
}
32+
```
33+
34+
## Support
35+
36+
- Distros: Debian/Ubuntu and Alpine
37+
- Architectures: x86_64 and arm64
38+
39+
## Tests
40+
41+
This feature is tested against:
42+
43+
- Ubuntu (latest)
44+
- Debian (latest)
45+
- Alpine (latest)
46+
- Debian with a pinned version (e.g., 1.2.21)
47+
48+
## Considerations / Future Enhancements
49+
50+
- If the curl installer behavior changes, we can add validations or retries.
51+
- Coexistence with Node.js: installs only `bun` and a `bunx` shim.
52+
- PATH/profile: installation to `/usr/local/bin` avoids profile changes.

src/bun-curl/README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Bun (via curl installer) (bun-curl)
2+
3+
Installs the Bun JavaScript runtime and package manager via the official curl installer.
4+
5+
## Example Usage
6+
7+
```json
8+
"features": {
9+
"ghcr.io/devcontainers-extra/features/bun-curl:1": {}
10+
}
11+
```
12+
13+
## Options
14+
15+
| Options Id | Description | Type | Default Value |
16+
|-----|-----|-----|-----|
17+
| version | Select the version to install. | string | latest |
18+
19+
---
20+
21+
_Note: This file was auto-generated from the [devcontainer-feature.json](devcontainer-feature.json). Add additional notes to a `NOTES.md`._
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"id": "bun-curl",
3+
"version": "1.0.0",
4+
"name": "Bun (via curl installer)",
5+
"documentationURL": "http://github.com/devcontainers-extra/features/tree/main/src/bun-curl",
6+
"description": "Installs the Bun JavaScript runtime and package manager via the official curl installer.",
7+
"options": {
8+
"version": {
9+
"default": "latest",
10+
"description": "Select the version to install.",
11+
"proposals": [
12+
"latest"
13+
],
14+
"type": "string"
15+
}
16+
},
17+
"installsAfter": []
18+
}

src/bun-curl/install.sh

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
#!/usr/bin/env bash
2+
3+
set -e
4+
5+
source ./library_scripts.sh
6+
7+
# Install prerequisites transiently (only if missing)
8+
tempdir=$(mktemp -d)
9+
cleanup_pkgs=""
10+
apt_installed=""
11+
apk_installed=""
12+
13+
if [ -x "/usr/bin/apt-get" ]; then
14+
cp -p -R /var/lib/apt/lists $tempdir
15+
to_install=()
16+
for pkg in curl ca-certificates tar unzip; do
17+
if ! dpkg -s "$pkg" >/dev/null 2>&1; then
18+
to_install+=("$pkg")
19+
fi
20+
done
21+
if [ ${#to_install[@]} -gt 0 ]; then
22+
apt-get update -y
23+
apt-get -y install --no-install-recommends "${to_install[@]}"
24+
apt_installed="${to_install[*]}"
25+
fi
26+
cleanup_pkgs="apt"
27+
elif [ -x "/sbin/apk" ]; then
28+
cp -p -R /var/cache/apk $tempdir
29+
to_install=()
30+
for pkg in curl ca-certificates tar unzip; do
31+
if ! apk info -e "$pkg" >/dev/null 2>&1; then
32+
to_install+=("$pkg")
33+
fi
34+
done
35+
if [ ${#to_install[@]} -gt 0 ]; then
36+
apk add --no-cache "${to_install[@]}"
37+
apk_installed="${to_install[*]}"
38+
fi
39+
cleanup_pkgs="apk"
40+
fi
41+
42+
# Use Bun's official installer; install system-wide to /usr/local
43+
export BUN_INSTALL="/usr/local"
44+
45+
version_arg=""
46+
if [ "${VERSION:-latest}" != "latest" ] && [ -n "${VERSION}" ]; then
47+
version_arg="bun-v${VERSION}"
48+
fi
49+
50+
curl -fsSL https://bun.sh/install | bash -s -- ${version_arg}
51+
52+
# Provide a convenient bunx shim
53+
if [ -x "/usr/local/bin/bun" ] && ! [ -x "/usr/local/bin/bunx" ]; then
54+
cat >/usr/local/bin/bunx <<'EOF'
55+
#!/usr/bin/env bash
56+
exec bun x "$@"
57+
EOF
58+
chmod +x /usr/local/bin/bunx
59+
fi
60+
61+
# Cleanup prerequisites (only ones we installed)
62+
if [ "$cleanup_pkgs" = "apt" ]; then
63+
if [ -n "$apt_installed" ]; then
64+
apt-get -y purge $apt_installed --auto-remove || true
65+
fi
66+
rm -rf /var/lib/apt/lists/*
67+
rm -r /var/lib/apt/lists && mv $tempdir/lists /var/lib/apt/lists
68+
elif [ "$cleanup_pkgs" = "apk" ]; then
69+
if [ -n "$apk_installed" ]; then
70+
apk del $apk_installed || true
71+
fi
72+
fi
73+
74+
rm -rf "$tempdir"
75+
76+
echo 'Bun (curl) installation completed.'

src/bun-curl/library_scripts.sh

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
#!/usr/bin/env bash
2+
3+
clean_download() {
4+
# The purpose of this function is to download a file with minimal impact on container layer size
5+
# this means if no valid downloader is found (curl or wget) then we install a downloader (currently wget) in a
6+
# temporary manner, and making sure to
7+
# 1. uninstall the downloader at the return of the function
8+
# 2. revert back any changes to the package installer database/cache (for example apt-get lists)
9+
# The above steps will minimize the leftovers being created while installing the downloader
10+
# Supported distros:
11+
# debian/ubuntu/alpine
12+
13+
url=$1
14+
output_location=$2
15+
tempdir=$(mktemp -d)
16+
downloader_installed=""
17+
18+
function _apt_get_install() {
19+
tempdir=$1
20+
21+
# copy current state of apt list - in order to revert back later (minimize contianer layer size)
22+
cp -p -R /var/lib/apt/lists $tempdir
23+
apt-get update -y
24+
apt-get -y install --no-install-recommends wget ca-certificates
25+
}
26+
27+
function _apt_get_cleanup() {
28+
tempdir=$1
29+
30+
echo "removing wget"
31+
apt-get -y purge wget --auto-remove
32+
33+
echo "revert back apt lists"
34+
rm -rf /var/lib/apt/lists/*
35+
rm -r /var/lib/apt/lists && mv $tempdir/lists /var/lib/apt/lists
36+
}
37+
38+
function _apk_install() {
39+
tempdir=$1
40+
# copy current state of apk cache - in order to revert back later (minimize contianer layer size)
41+
cp -p -R /var/cache/apk $tempdir
42+
43+
apk add --no-cache wget
44+
}
45+
46+
function _apk_cleanup() {
47+
tempdir=$1
48+
49+
echo "removing wget"
50+
apk del wget
51+
}
52+
# try to use either wget or curl if one of them already installer
53+
if type curl >/dev/null 2>&1; then
54+
downloader=curl
55+
elif type wget >/dev/null 2>&1; then
56+
downloader=wget
57+
else
58+
downloader=""
59+
fi
60+
61+
# in case none of them is installed, install wget temporarly
62+
if [ -z $downloader ]; then
63+
if [ -x "/usr/bin/apt-get" ]; then
64+
_apt_get_install $tempdir
65+
elif [ -x "/sbin/apk" ]; then
66+
_apk_install $tempdir
67+
else
68+
echo "distro not supported"
69+
exit 1
70+
fi
71+
downloader="wget"
72+
downloader_installed="true"
73+
fi
74+
75+
if [ $downloader = "wget" ]; then
76+
wget -q $url -O $output_location
77+
else
78+
curl -sfL $url -o $output_location
79+
fi
80+
81+
# NOTE: the cleanup procedure was not implemented using `trap X RETURN` only because
82+
# alpine lack bash, and RETURN is not a valid signal under sh shell
83+
if ! [ -z $downloader_installed ]; then
84+
if [ -x "/usr/bin/apt-get" ]; then
85+
_apt_get_cleanup $tempdir
86+
elif [ -x "/sbin/apk" ]; then
87+
_apk_cleanup $tempdir
88+
else
89+
echo "distro not supported"
90+
exit 1
91+
fi
92+
fi
93+
94+
}
95+
96+
ensure_nanolayer() {
97+
# Ensure existance of the nanolayer cli program
98+
local variable_name=$1
99+
100+
local required_version=$2
101+
# normalize version
102+
if ! [[ $required_version == v* ]]; then
103+
required_version=v$required_version
104+
fi
105+
106+
local nanolayer_location=""
107+
108+
# If possible - try to use an already installed nanolayer
109+
if [[ -z "${NANOLAYER_FORCE_CLI_INSTALLATION}" ]]; then
110+
if [[ -z "${NANOLAYER_CLI_LOCATION}" ]]; then
111+
if type nanolayer >/dev/null 2>&1; then
112+
echo "Found a pre-existing nanolayer in PATH"
113+
nanolayer_location=nanolayer
114+
fi
115+
elif [ -f "${NANOLAYER_CLI_LOCATION}" ] && [ -x "${NANOLAYER_CLI_LOCATION}" ]; then
116+
nanolayer_location=${NANOLAYER_CLI_LOCATION}
117+
echo "Found a pre-existing nanolayer which were given in env variable: $nanolayer_location"
118+
fi
119+
120+
# make sure its of the required version
121+
if ! [[ -z "${nanolayer_location}" ]]; then
122+
local current_version
123+
current_version=$($nanolayer_location --version)
124+
if ! [[ $current_version == v* ]]; then
125+
current_version=v$current_version
126+
fi
127+
128+
if ! [ $current_version == $required_version ]; then
129+
echo "skipping usage of pre-existing nanolayer. (required version $required_version does not match existing version $current_version)"
130+
nanolayer_location=""
131+
fi
132+
fi
133+
134+
fi
135+
136+
# If not previuse installation found, download it temporarly and delete at the end of the script
137+
if [[ -z "${nanolayer_location}" ]]; then
138+
139+
if [ "$(uname -sm)" == "Linux x86_64" ] || [ "$(uname -sm)" == "Linux aarch64" ]; then
140+
tmp_dir=$(mktemp -d -t nanolayer-XXXXXXXXXX)
141+
142+
clean_up() {
143+
ARG=$?
144+
rm -rf $tmp_dir
145+
exit $ARG
146+
}
147+
trap clean_up EXIT
148+
149+
if [ -x "/sbin/apk" ]; then
150+
clib_type=musl
151+
else
152+
clib_type=gnu
153+
fi
154+
155+
tar_filename=nanolayer-"$(uname -m)"-unknown-linux-$clib_type.tgz
156+
157+
# clean download will minimize leftover in case a downloaderlike wget or curl need to be installed
158+
clean_download https://github.com/devcontainers-extra/nanolayer/releases/download/$required_version/$tar_filename $tmp_dir/$tar_filename
159+
160+
tar xfzv $tmp_dir/$tar_filename -C "$tmp_dir"
161+
chmod a+x $tmp_dir/nanolayer
162+
nanolayer_location=$tmp_dir/nanolayer
163+
164+
else
165+
echo "No binaries compiled for non-x86-linux architectures yet: $(uname -m)"
166+
exit 1
167+
fi
168+
fi
169+
170+
# Expose outside the resolved location
171+
declare -g ${variable_name}=$nanolayer_location
172+
173+
}

src/bun-gh-releases/NOTES.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# Additional Notes
2+
3+
- Website: [https://bun.com/](https://bun.com/)
4+
- GitHub: [https://github.com/oven-sh/bun](https://github.com/oven-sh/bun)
5+
6+
## Install Method
7+
8+
This feature installs Bun via the repository's `gh-release` helper, which downloads the correct release asset for the current platform and architecture and places the `bun` binary in `/usr/local/bin`. This follows the repo’s best practices for reproducibility and small image layers.
9+
10+
## bunx shim
11+
12+
Bun supports executing binaries with `bun x <pkg>`. Some environments/tools expect a `bunx` executable. To provide a consistent experience, this feature adds a small shim at `/usr/local/bin/bunx` that forwards to `bun x`. If the upstream release reliably includes a `bunx` binary in the future, the shim can be removed without breaking users.
13+
14+
## Usage Examples
15+
16+
Default (latest):
17+
18+
```json
19+
"features": {
20+
"ghcr.io/devcontainers-extra/features/bun-gh-releases:1": {}
21+
}
22+
```
23+
24+
Pin a specific version:
25+
26+
```json
27+
"features": {
28+
"ghcr.io/devcontainers-extra/features/bun-gh-releases:1": {
29+
"version": "1.1.38"
30+
}
31+
}
32+
```
33+
34+
## Support
35+
36+
- Distros: `Debian` / `Ubuntu` and `Alpine`
37+
- Architectures: `x86_64` and `arm64`
38+
39+
## Tests
40+
41+
This feature is tested against:
42+
43+
- Debian (latest)
44+
- Alpine (latest)
45+
- Debian with a pinned version of bun (e.g., bun v1.1.38)
46+
47+
## Considerations / Future Enhancements
48+
49+
- If Bun’s release assets ever require explicit filtering, we can wire an `assetRegex` or pass `additionalFlags` to the `gh-release` helper. The helper already auto-filters by platform/arch, so no extra flags are set currently.
50+
- Coexistence with Node.js: this feature only installs `bun` and a `bunx` shim and does not modify Node.js.
51+
- PATH/profile: installation to `/usr/local/bin` avoids profile changes.

0 commit comments

Comments
 (0)