diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 708661052d..5b8c8fe3dc 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -331,115 +331,6 @@ jobs: AWS_ENDPOINT: ${{ secrets.AWS_ENDPOINT }} AWS_BUCKET: bun - macos-sfx: - name: Generate macOS SFX Archives - runs-on: macos-latest - needs: sign - if: ${{ github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.use-sfx == 'true') }} - permissions: - contents: write - strategy: - matrix: - include: - - arch: aarch64 - display: arm64 - - arch: x64 - display: x64 - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Setup Environment - run: | - echo "Creating working directory..." - mkdir -p sfx-build - - name: Download Bun Release - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - cd sfx-build - - # Download the zip file from GitHub releases - echo "Downloading bun-darwin-${{ matrix.arch }}.zip..." - - if [[ "${{ env.BUN_VERSION }}" == "canary" ]]; then - DOWNLOAD_URL="https://github.com/oven-sh/bun/releases/download/canary/bun-darwin-${{ matrix.arch }}.zip" - else - DOWNLOAD_URL="https://github.com/oven-sh/bun/releases/download/${{ env.BUN_VERSION }}/bun-darwin-${{ matrix.arch }}.zip" - fi - - curl -L -o "bun-darwin-${{ matrix.arch }}.zip" "$DOWNLOAD_URL" - - # Extract the zip file - unzip -q "bun-darwin-${{ matrix.arch }}.zip" - - # Find the bun executable - BUN_EXEC=$(find . -name "bun" -type f | head -n 1) - if [[ -z "$BUN_EXEC" ]]; then - echo "Error: Could not find bun executable in the archive" - exit 1 - fi - - # Move it to a known location - mv "$BUN_EXEC" "./bun" - chmod +x ./bun - - # Clean up - rm -rf bun-darwin-${{ matrix.arch }} - - name: Create Self-Extracting Archive - run: | - cd sfx-build - - # Create the self-extracting script header - cat > extract-header.sh << 'SCRIPT_EOF' - #!/bin/bash - set -euo pipefail - DEFAULT_INSTALL_DIR="$HOME/.bun/bin" - INSTALL_DIR="${BUN_INSTALL_DIR:-$DEFAULT_INSTALL_DIR}" - ARCH="$(uname -m)" - EXPECTED_ARCH="ARCH_PLACEHOLDER" - if [[ "$ARCH" == "x86_64" ]]; then - ARCH="x64" - elif [[ "$ARCH" == "arm64" ]]; then - ARCH="arm64" - fi - if [[ "$ARCH" != "$EXPECTED_ARCH" ]]; then - echo "Warning: This installer is for $EXPECTED_ARCH but you're running on $ARCH" - fi - mkdir -p "$INSTALL_DIR" - PAYLOAD_LINE=$(awk '/^__PAYLOAD_BEGINS__/ { print NR + 1; exit 0; }' "$0") - echo "Extracting Bun to $INSTALL_DIR..." - tail -n +"$PAYLOAD_LINE" "$0" | base64 -D | tar -xzf - -C "$INSTALL_DIR" - chmod +x "$INSTALL_DIR/bun" - echo "Bun has been installed to $INSTALL_DIR/bun" - echo "Run 'export PATH=\"\$PATH:$INSTALL_DIR\"' to add it to your PATH" - exit 0 - __PAYLOAD_BEGINS__ - SCRIPT_EOF - - # Replace the architecture placeholder - sed -i '' "s/ARCH_PLACEHOLDER/${{ matrix.display }}/g" extract-header.sh - - # Create the SFX archive - tar -czf bun.tar.gz bun - base64 < bun.tar.gz > bun.tar.gz.b64 - cat extract-header.sh bun.tar.gz.b64 > "bun-darwin-${{ matrix.arch }}-sfx.sh" - chmod +x "bun-darwin-${{ matrix.arch }}-sfx.sh" - - # Generate checksum - shasum -a 256 "bun-darwin-${{ matrix.arch }}-sfx.sh" > "bun-darwin-${{ matrix.arch }}-sfx.sh.sha256" - - name: Upload to Release - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - cd sfx-build - - # Upload the SFX to the release - gh release upload "${{ env.BUN_VERSION }}" \ - "bun-darwin-${{ matrix.arch }}-sfx.sh" \ - "bun-darwin-${{ matrix.arch }}-sfx.sh.sha256" \ - --clobber \ - --repo "${{ github.repository }}" - linux-sfx: name: Generate Linux SFX Archives runs-on: ubuntu-latest diff --git a/.github/workflows/sfx-linux.yml b/.github/workflows/sfx-linux.yml index f75b8b2a99..5f00177d78 100644 --- a/.github/workflows/sfx-linux.yml +++ b/.github/workflows/sfx-linux.yml @@ -32,20 +32,47 @@ jobs: - arch: x64 zip_name: bun-linux-x64 display: x64 + gcc_arch: x86-64 - arch: x64 zip_name: bun-linux-x64-baseline display: x64-baseline baseline: true + gcc_arch: x86-64 - arch: aarch64 zip_name: bun-linux-aarch64 display: aarch64 + gcc_arch: aarch64 + - arch: x64 + zip_name: bun-linux-x64-musl + display: x64-musl + musl: true + gcc_arch: x86-64 + - arch: x64 + zip_name: bun-linux-x64-musl-baseline + display: x64-musl-baseline + baseline: true + musl: true + gcc_arch: x86-64 + - arch: aarch64 + zip_name: bun-linux-aarch64-musl + display: aarch64-musl + musl: true + gcc_arch: aarch64 steps: - name: Checkout uses: actions/checkout@v4 - - name: Setup Environment + - name: Setup Build Environment run: | - echo "Creating working directory..." + echo "Installing build dependencies..." + sudo apt-get update + sudo apt-get install -y gcc make xxd upx-ucl + + # Install cross-compilation toolchain for aarch64 + if [[ "${{ matrix.arch }}" == "aarch64" ]]; then + sudo apt-get install -y gcc-aarch64-linux-gnu + fi + mkdir -p sfx-build cd sfx-build @@ -55,7 +82,6 @@ jobs: run: | cd sfx-build - # Download the zip file from GitHub releases echo "Downloading ${{ matrix.zip_name }}.zip..." if [[ "${{ env.BUN_VERSION }}" == "canary" ]]; then @@ -66,7 +92,7 @@ jobs: curl -L -o "${{ matrix.zip_name }}.zip" "$DOWNLOAD_URL" - # Extract the zip file using unzip (available on GitHub runners) + # Extract the zip file unzip -q "${{ matrix.zip_name }}.zip" # Find the bun executable @@ -80,189 +106,292 @@ jobs: mv "$BUN_EXEC" "./bun" chmod +x ./bun + # Compress the binary + echo "Compressing bun binary..." + gzip -9 -k ./bun + # Clean up rm -rf bun-linux-*/ ${{ matrix.zip_name }}.zip - - name: Create SFX Script + - name: Create SFX Source Code run: | cd sfx-build - # Create the self-extracting script - cat > extract-header.sh << 'SCRIPT_EOF' - #!/bin/sh - # Bun Self-Extracting Archive for Linux - # This is a self-extracting archive. Run it to install Bun. - # No external dependencies required (no unzip needed!) + # Create the self-extracting executable source + cat > sfx.c << 'EOF' + #include + #include + #include + #include + #include + #include + #include + #include - set -e + // Terminal colors + #define COLOR_RED "\033[0;31m" + #define COLOR_GREEN "\033[0;32m" + #define COLOR_YELLOW "\033[1;33m" + #define COLOR_RESET "\033[0m" - # Default installation directory - DEFAULT_INSTALL_DIR="$HOME/.bun/bin" - INSTALL_DIR="${BUN_INSTALL_DIR:-$DEFAULT_INSTALL_DIR}" + // Embedded compressed binary data + static const unsigned char compressed_data[] = { + #include "bun_data.h" + }; + static const size_t compressed_size = sizeof(compressed_data); - # ANSI color codes (only if terminal supports it) - if [ -t 1 ] && [ -n "${TERM-}" ] && [ "${TERM-}" != "dumb" ]; then - RED='\033[0;31m' - GREEN='\033[0;32m' - YELLOW='\033[1;33m' - NC='\033[0m' - else - RED='' - GREEN='' - YELLOW='' - NC='' - fi + // Installation configuration + #ifndef DEFAULT_INSTALL_DIR + #define DEFAULT_INSTALL_DIR "/.bun/bin" + #endif - error() { - printf "${RED}error: %s${NC}\n" "$1" >&2 - exit 1 + #ifndef BUN_ARCH + #define BUN_ARCH "unknown" + #endif + + void print_error(const char* msg) { + if (isatty(STDERR_FILENO)) { + fprintf(stderr, COLOR_RED "error: " COLOR_RESET "%s\n", msg); + } else { + fprintf(stderr, "error: %s\n", msg); + } } - info() { - printf "${GREEN}%s${NC}\n" "$1" + void print_success(const char* msg) { + if (isatty(STDOUT_FILENO)) { + printf(COLOR_GREEN "%s" COLOR_RESET "\n", msg); + } else { + printf("%s\n", msg); + } } - warn() { - printf "${YELLOW}warning: %s${NC}\n" "$1" + void print_info(const char* msg) { + printf("%s\n", msg); } - # Check if running on Linux - if [ "$(uname -s)" != "Linux" ]; then - error "This installer is for Linux only. Please download the correct version for your platform." - fi + int ensure_directory(const char* path) { + struct stat st = {0}; + if (stat(path, &st) == -1) { + if (mkdir(path, 0755) == -1 && errno != EEXIST) { + return -1; + } + } + return 0; + } - # Check architecture - ARCH="$(uname -m)" - EXPECTED_ARCH="ARCH_PLACEHOLDER" - EXPECTED_DISPLAY="DISPLAY_PLACEHOLDER" + int create_directory_tree(const char* path) { + char tmp[1024]; + char *p = NULL; + size_t len; + + snprintf(tmp, sizeof(tmp), "%s", path); + len = strlen(tmp); + if (tmp[len - 1] == '/') + tmp[len - 1] = 0; + + for (p = tmp + 1; *p; p++) { + if (*p == '/') { + *p = 0; + if (ensure_directory(tmp) == -1) { + return -1; + } + *p = '/'; + } + } + + if (ensure_directory(tmp) == -1) { + return -1; + } + + return 0; + } - case "$ARCH" in - x86_64) - ARCH_NORM="x64" - ;; - aarch64|arm64) - ARCH_NORM="aarch64" - ;; - *) - ARCH_NORM="$ARCH" - ;; - esac + int decompress_to_file(const char* output_path) { + FILE* outfile = fopen(output_path, "wb"); + if (!outfile) { + return -1; + } + + // Decompress using zlib + z_stream stream = {0}; + stream.next_in = (Bytef*)compressed_data; + stream.avail_in = compressed_size; + + if (inflateInit2(&stream, 16 + MAX_WBITS) != Z_OK) { + fclose(outfile); + return -1; + } + + unsigned char buffer[8192]; + int ret; + + do { + stream.next_out = buffer; + stream.avail_out = sizeof(buffer); + + ret = inflate(&stream, Z_NO_FLUSH); + if (ret == Z_STREAM_ERROR) { + inflateEnd(&stream); + fclose(outfile); + return -1; + } + + size_t have = sizeof(buffer) - stream.avail_out; + if (fwrite(buffer, 1, have, outfile) != have) { + inflateEnd(&stream); + fclose(outfile); + return -1; + } + } while (ret != Z_STREAM_END); + + inflateEnd(&stream); + fclose(outfile); + + // Make executable + if (chmod(output_path, 0755) == -1) { + return -1; + } + + return 0; + } - # Check if architecture matches (allow x64 baseline on x64) - ARCH_MISMATCH=0 - case "$EXPECTED_ARCH" in - x64) - if [ "$ARCH_NORM" != "x64" ]; then - ARCH_MISMATCH=1 - fi - ;; - aarch64) - if [ "$ARCH_NORM" != "aarch64" ]; then - ARCH_MISMATCH=1 - fi - ;; - esac + int main(int argc, char* argv[]) { + printf("Bun Self-Extracting Archive for Linux %s\n", BUN_ARCH); + printf("=====================================\n\n"); + + // Determine installation directory + const char* install_dir_env = getenv("BUN_INSTALL_DIR"); + char install_dir[1024]; + + if (install_dir_env && install_dir_env[0] != '\0') { + snprintf(install_dir, sizeof(install_dir), "%s", install_dir_env); + } else { + const char* home = getenv("HOME"); + if (!home) { + print_error("Could not determine home directory"); + return 1; + } + snprintf(install_dir, sizeof(install_dir), "%s%s", home, DEFAULT_INSTALL_DIR); + } + + printf("Installation directory: %s\n", install_dir); + + // Create installation directory + if (create_directory_tree(install_dir) == -1) { + char error_msg[1024]; + snprintf(error_msg, sizeof(error_msg), "Failed to create directory: %s", install_dir); + print_error(error_msg); + return 1; + } + + // Extract bun executable + char bun_path[1024]; + snprintf(bun_path, sizeof(bun_path), "%s/bun", install_dir); + + printf("Extracting bun...\n"); + if (decompress_to_file(bun_path) == -1) { + print_error("Failed to extract bun executable"); + return 1; + } + + // Verify installation + char version_cmd[1024]; + snprintf(version_cmd, sizeof(version_cmd), "%s --version 2>/dev/null", bun_path); + FILE* fp = popen(version_cmd, "r"); + if (fp) { + char version[128]; + if (fgets(version, sizeof(version), fp) != NULL) { + // Remove newline + size_t len = strlen(version); + if (len > 0 && version[len-1] == '\n') { + version[len-1] = '\0'; + } + char success_msg[256]; + snprintf(success_msg, sizeof(success_msg), "✓ Bun %s has been installed to %s", version, bun_path); + print_success(success_msg); + } + pclose(fp); + } + + // Check if in PATH + const char* path_env = getenv("PATH"); + if (path_env && !strstr(path_env, install_dir)) { + printf("\n"); + print_info("To get started, add Bun to your PATH:"); + printf(" export PATH=\"$PATH:%s\"\n", install_dir); + printf("\n"); + print_info("To make this permanent, add it to your shell configuration file:"); + + const char* shell = getenv("SHELL"); + if (shell) { + if (strstr(shell, "bash")) { + printf(" echo 'export PATH=\"$PATH:%s\"' >> ~/.bashrc\n", install_dir); + } else if (strstr(shell, "zsh")) { + printf(" echo 'export PATH=\"$PATH:%s\"' >> ~/.zshrc\n", install_dir); + } else if (strstr(shell, "fish")) { + printf(" echo 'set -gx PATH $PATH %s' >> ~/.config/fish/config.fish\n", install_dir); + } else { + printf(" echo 'export PATH=\"$PATH:%s\"' >> ~/.profile\n", install_dir); + } + } + } + + return 0; + } + EOF - if [ "$ARCH_MISMATCH" = "1" ]; then - warn "This installer is for $EXPECTED_DISPLAY but you're running on $ARCH" - if [ -t 0 ]; then - printf "Continue anyway? [y/N] " - read -r REPLY - case "$REPLY" in - [Yy]*) - ;; - *) - exit 1 - ;; - esac - else - error "Architecture mismatch and no TTY to confirm" - fi - fi + # Convert the compressed binary to a C header file + echo "Converting binary to C array..." + xxd -i bun.gz | sed 's/unsigned char bun_gz\[\]//' | sed 's/unsigned int bun_gz_len = .*//' > bun_data.h - # Create installation directory - mkdir -p "$INSTALL_DIR" || error "Failed to create installation directory: $INSTALL_DIR" - - # Find where the payload starts - ARCHIVE_MARKER="__ARCHIVE_BELOW__" - ARCHIVE_LINE=$(awk "/$ARCHIVE_MARKER/ { print NR + 1; exit }" "$0") - - if [ -z "$ARCHIVE_LINE" ]; then - error "Could not find archive marker" - fi - - # Extract the payload - info "Extracting Bun to $INSTALL_DIR..." - - # Use tail to skip the script part, then decode and extract - # We use base64 to ensure binary safety across all systems - if ! tail -n +"$ARCHIVE_LINE" "$0" | base64 -d | tar -xzf - -C "$INSTALL_DIR" 2>/dev/null; then - error "Failed to extract Bun. The archive may be corrupted." - fi - - # Make it executable - chmod +x "$INSTALL_DIR/bun" || error "Failed to make bun executable" - - # Verify installation - if [ -x "$INSTALL_DIR/bun" ]; then - BUN_VERSION=$("$INSTALL_DIR/bun" --version 2>/dev/null || echo "unknown") - info "✓ Bun $BUN_VERSION has been installed to $INSTALL_DIR/bun" - else - error "Installation verification failed" - fi - - # Check if bun is in PATH - if ! echo "$PATH" | grep -q "$INSTALL_DIR"; then - info "" - info "To get started, add Bun to your PATH:" - info " export PATH=\"\$PATH:$INSTALL_DIR\"" - info "" - info "To make this permanent, add it to your shell configuration file:" - case "${SHELL##*/}" in - bash) - info " echo 'export PATH=\"\$PATH:$INSTALL_DIR\"' >> ~/.bashrc" - ;; - zsh) - info " echo 'export PATH=\"\$PATH:$INSTALL_DIR\"' >> ~/.zshrc" - ;; - fish) - info " echo 'set -gx PATH \$PATH $INSTALL_DIR' >> ~/.config/fish/config.fish" - ;; - *) - info " echo 'export PATH=\"\$PATH:$INSTALL_DIR\"' >> ~/.profile" - ;; - esac - fi - - exit 0 - # DO NOT REMOVE THE FOLLOWING LINE - __ARCHIVE_BELOW__ - SCRIPT_EOF - - # Replace placeholders - sed -i "s/ARCH_PLACEHOLDER/${{ matrix.arch }}/g" extract-header.sh - sed -i "s/DISPLAY_PLACEHOLDER/${{ matrix.display }}/g" extract-header.sh - - - name: Create Self-Extracting Archive + - name: Build Self-Extracting Executable run: | cd sfx-build - # Create a tar.gz of the bun executable - tar -czf bun.tar.gz bun + # Select compiler based on architecture and libc + if [[ "${{ matrix.musl }}" == "true" ]]; then + # For musl builds, we need musl-gcc + sudo apt-get install -y musl-tools + if [[ "${{ matrix.arch }}" == "aarch64" ]]; then + # Cross-compile for aarch64 musl - this is more complex + # For now, skip cross-compilation for musl + echo "Warning: Cross-compilation for aarch64-musl not yet supported" + exit 0 + else + CC=musl-gcc + fi + elif [[ "${{ matrix.arch }}" == "aarch64" ]]; then + CC=aarch64-linux-gnu-gcc + else + CC=gcc + fi - # Encode it as base64 (for binary safety) - base64 < bun.tar.gz > bun.tar.gz.b64 + # Compile the self-extracting executable + echo "Building self-extracting executable..." + $CC -static -O3 -s \ + -DBUN_ARCH="\"${{ matrix.display }}\"" \ + sfx.c -o "${{ matrix.zip_name }}" \ + -lz - # Combine header and payload - cat extract-header.sh bun.tar.gz.b64 > "${{ matrix.zip_name }}-sfx.sh" + # Compress the executable with UPX for smaller size + echo "Compressing executable with UPX..." + upx --best --lzma "${{ matrix.zip_name }}" || true # Make it executable - chmod +x "${{ matrix.zip_name }}-sfx.sh" + chmod +x "${{ matrix.zip_name }}" + + # Show final size + echo "Final executable size:" + ls -lh "${{ matrix.zip_name }}" + + - name: Test Self-Extracting Archive + if: ${{ matrix.arch != 'aarch64' && matrix.musl != 'true' }} # Can't test aarch64 or musl on x64 runner + run: | + cd sfx-build - # Test the SFX echo "Testing self-extraction..." TEST_DIR="$(mktemp -d)" - BUN_INSTALL_DIR="$TEST_DIR" ./${{ matrix.zip_name }}-sfx.sh + BUN_INSTALL_DIR="$TEST_DIR" ./${{ matrix.zip_name }} if [ -x "$TEST_DIR/bun" ]; then echo "✓ Self-extraction test passed" @@ -273,64 +402,23 @@ jobs: exit 1 fi - - name: Create Quick Install Script - run: | - cd sfx-build - - # Also create a simple download-and-run script - cat > "${{ matrix.zip_name }}-install.sh" << 'INSTALLER_EOF' - #!/bin/sh - # Quick installer script for Bun on Linux - - set -e - - VERSION="VERSION_PLACEHOLDER" - ARCHIVE_NAME="ARCHIVE_PLACEHOLDER" - - echo "Downloading Bun $VERSION for Linux..." - - if [ "$VERSION" = "canary" ]; then - URL="https://github.com/oven-sh/bun/releases/download/canary/${ARCHIVE_NAME}-sfx.sh" - else - URL="https://github.com/oven-sh/bun/releases/download/$VERSION/${ARCHIVE_NAME}-sfx.sh" - fi - - if command -v curl >/dev/null 2>&1; then - curl -fsSL "$URL" | sh - elif command -v wget >/dev/null 2>&1; then - wget -qO- "$URL" | sh - else - echo "Error: Neither curl nor wget found. Please install one of them." - exit 1 - fi - INSTALLER_EOF - - # Replace placeholders - sed -i "s/VERSION_PLACEHOLDER/${{ env.BUN_VERSION }}/g" "${{ matrix.zip_name }}-install.sh" - sed -i "s/ARCHIVE_PLACEHOLDER/${{ matrix.zip_name }}/g" "${{ matrix.zip_name }}-install.sh" - - chmod +x "${{ matrix.zip_name }}-install.sh" - - name: Generate Checksums run: | cd sfx-build # Generate SHA256 checksums - sha256sum "${{ matrix.zip_name }}-sfx.sh" > "${{ matrix.zip_name }}-sfx.sh.sha256" - sha256sum "${{ matrix.zip_name }}-install.sh" > "${{ matrix.zip_name }}-install.sh.sha256" + sha256sum "${{ matrix.zip_name }}" > "${{ matrix.zip_name }}.sha256" echo "Generated files:" - ls -la *.sh *.sha256 + ls -la ${{ matrix.zip_name }}* - name: Upload Artifacts uses: actions/upload-artifact@v4 with: name: ${{ matrix.zip_name }}-sfx path: | - sfx-build/${{ matrix.zip_name }}-sfx.sh - sfx-build/${{ matrix.zip_name }}-sfx.sh.sha256 - sfx-build/${{ matrix.zip_name }}-install.sh - sfx-build/${{ matrix.zip_name }}-install.sh.sha256 + sfx-build/${{ matrix.zip_name }} + sfx-build/${{ matrix.zip_name }}.sha256 - name: Upload to Release if: ${{ (github.event_name == 'release' || github.event.inputs.upload-to-release == 'true') && env.BUN_VERSION != 'canary' }} @@ -339,12 +427,10 @@ jobs: run: | cd sfx-build - # Upload the SFX and installer scripts to the release + # Upload the SFX to the release gh release upload "${{ env.BUN_VERSION }}" \ - "${{ matrix.zip_name }}-sfx.sh" \ - "${{ matrix.zip_name }}-sfx.sh.sha256" \ - "${{ matrix.zip_name }}-install.sh" \ - "${{ matrix.zip_name }}-install.sh.sha256" \ + "${{ matrix.zip_name }}" \ + "${{ matrix.zip_name }}.sha256" \ --clobber \ --repo "${{ github.repository }}" diff --git a/.github/workflows/sfx-macos.yml b/.github/workflows/sfx-macos.yml deleted file mode 100644 index 861def666c..0000000000 --- a/.github/workflows/sfx-macos.yml +++ /dev/null @@ -1,379 +0,0 @@ -name: Generate macOS SFX Archives -concurrency: sfx-macos - -on: - release: - types: - - published - workflow_dispatch: - inputs: - tag: - description: 'Release tag (e.g., "bun-v1.0.2", "canary")' - required: true - default: "canary" - upload-to-release: - description: "Upload SFX files to the release?" - type: boolean - default: false - -env: - BUN_VERSION: ${{ github.event.inputs.tag || github.event.release.tag_name || 'canary' }} - -jobs: - create-macos-sfx: - name: Create macOS Self-Extracting Archives - runs-on: macos-latest - if: ${{ github.repository_owner == 'oven-sh' }} - permissions: - contents: write - strategy: - matrix: - include: - - arch: aarch64 - display: arm64 - - arch: x64 - display: x64 - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup Environment - run: | - echo "Creating working directory..." - mkdir -p sfx-build - cd sfx-build - - - name: Download Bun Release - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - cd sfx-build - - # Download the zip file from GitHub releases - echo "Downloading bun-darwin-${{ matrix.arch }}.zip..." - - if [[ "${{ env.BUN_VERSION }}" == "canary" ]]; then - DOWNLOAD_URL="https://github.com/oven-sh/bun/releases/download/canary/bun-darwin-${{ matrix.arch }}.zip" - else - DOWNLOAD_URL="https://github.com/oven-sh/bun/releases/download/${{ env.BUN_VERSION }}/bun-darwin-${{ matrix.arch }}.zip" - fi - - curl -L -o "bun-darwin-${{ matrix.arch }}.zip" "$DOWNLOAD_URL" - - # Extract the zip file - unzip -q "bun-darwin-${{ matrix.arch }}.zip" - - # Find the bun executable - BUN_EXEC=$(find . -name "bun" -type f | head -n 1) - if [[ -z "$BUN_EXEC" ]]; then - echo "Error: Could not find bun executable in the archive" - exit 1 - fi - - # Move it to a known location - mv "$BUN_EXEC" "./bun" - chmod +x ./bun - - # Clean up - rm -rf bun-darwin-${{ matrix.arch }} - - - name: Create SFX Script - run: | - cd sfx-build - - # Create the self-extracting script - cat > extract-header.sh << 'SCRIPT_EOF' - #!/bin/bash - # Bun Self-Extracting Archive for macOS - # This is a self-extracting archive. Run it to install Bun. - - set -euo pipefail - - # Default installation directory - DEFAULT_INSTALL_DIR="$HOME/.bun/bin" - INSTALL_DIR="${BUN_INSTALL_DIR:-$DEFAULT_INSTALL_DIR}" - - # Colors for output - RED='\033[0;31m' - GREEN='\033[0;32m' - YELLOW='\033[1;33m' - NC='\033[0m' # No Color - - # Detect if stdout is a terminal - if [[ -t 1 ]]; then - INTERACTIVE=1 - else - INTERACTIVE=0 - fi - - print_color() { - if [[ $INTERACTIVE -eq 1 ]]; then - printf "%b%s%b\n" "$1" "$2" "$NC" - else - echo "$2" - fi - } - - error() { - print_color "$RED" "error: $1" >&2 - exit 1 - } - - info() { - print_color "$GREEN" "$1" - } - - warn() { - print_color "$YELLOW" "warning: $1" - } - - # Check if running on macOS - if [[ "$(uname -s)" != "Darwin" ]]; then - error "This installer is for macOS only. Please download the correct version for your platform." - fi - - # Check architecture - ARCH="$(uname -m)" - EXPECTED_ARCH="ARCH_PLACEHOLDER" - - if [[ "$ARCH" == "x86_64" ]]; then - ARCH="x64" - elif [[ "$ARCH" == "arm64" ]]; then - ARCH="arm64" - fi - - if [[ "$ARCH" != "$EXPECTED_ARCH" ]]; then - warn "This installer is for $EXPECTED_ARCH but you're running on $ARCH" - if [[ $INTERACTIVE -eq 1 ]]; then - read -p "Continue anyway? [y/N] " -n 1 -r - echo - if [[ ! $REPLY =~ ^[Yy]$ ]]; then - exit 1 - fi - fi - fi - - # Create installation directory - mkdir -p "$INSTALL_DIR" || error "Failed to create installation directory: $INSTALL_DIR" - - # Find the payload offset - PAYLOAD_LINE=$(awk '/^__PAYLOAD_BEGINS__/ { print NR + 1; exit 0; }' "$0") - if [[ -z "$PAYLOAD_LINE" ]]; then - error "Could not find payload marker" - fi - - # Extract and decompress the payload - info "Extracting Bun to $INSTALL_DIR..." - tail -n +"$PAYLOAD_LINE" "$0" | base64 -D | tar -xzf - -C "$INSTALL_DIR" || error "Failed to extract Bun" - - # Make it executable - chmod +x "$INSTALL_DIR/bun" || error "Failed to make bun executable" - - # Verify installation - if [[ -x "$INSTALL_DIR/bun" ]]; then - BUN_VERSION="$("$INSTALL_DIR/bun" --version 2>/dev/null || echo "unknown")" - info "✓ Bun $BUN_VERSION has been installed to $INSTALL_DIR/bun" - else - error "Installation verification failed" - fi - - # Add to PATH if needed - if [[ ":$PATH:" != *":$INSTALL_DIR:"* ]]; then - info "" - info "To use Bun, add this to your shell configuration file (.bashrc, .zshrc, etc.):" - info " export PATH=\"\$PATH:$INSTALL_DIR\"" - info "" - info "Or run this command to use Bun in the current session:" - info " export PATH=\"\$PATH:$INSTALL_DIR\"" - fi - - exit 0 - __PAYLOAD_BEGINS__ - SCRIPT_EOF - - # Replace the architecture placeholder - sed -i '' "s/ARCH_PLACEHOLDER/${{ matrix.display }}/g" extract-header.sh - - - name: Create Self-Extracting Archive - run: | - cd sfx-build - - # Create a tar.gz of the bun executable - tar -czf bun.tar.gz bun - - # Encode it as base64 - base64 < bun.tar.gz > bun.tar.gz.b64 - - # Combine header and payload - cat extract-header.sh bun.tar.gz.b64 > "bun-darwin-${{ matrix.arch }}-sfx.sh" - - # Make it executable - chmod +x "bun-darwin-${{ matrix.arch }}-sfx.sh" - - # Test the SFX - echo "Testing self-extraction..." - TEST_DIR="$(mktemp -d)" - BUN_INSTALL_DIR="$TEST_DIR" ./bun-darwin-${{ matrix.arch }}-sfx.sh - - if [[ -x "$TEST_DIR/bun" ]]; then - echo "✓ Self-extraction test passed" - "$TEST_DIR/bun" --version - rm -rf "$TEST_DIR" - else - echo "✗ Self-extraction test failed" - exit 1 - fi - - - name: Create Download Script - run: | - cd sfx-build - - # Create installer script - cat > "bun-darwin-${{ matrix.arch }}-installer.sh" << 'INSTALLER_EOF' - #!/bin/bash - # Quick installer script for Bun on macOS - - set -euo pipefail - - ARCH="ARCH_PLACEHOLDER" - VERSION="VERSION_PLACEHOLDER" - - if [[ "$ARCH" == "arm64" ]]; then - SFX_ARCH="aarch64" - else - SFX_ARCH="x64" - fi - - echo "Downloading Bun $VERSION for macOS $ARCH..." - - if [[ "$VERSION" == "canary" ]]; then - URL="https://github.com/oven-sh/bun/releases/download/canary/bun-darwin-${SFX_ARCH}-sfx.sh" - else - URL="https://github.com/oven-sh/bun/releases/download/$VERSION/bun-darwin-${SFX_ARCH}-sfx.sh" - fi - - curl -fsSL "$URL" | bash - INSTALLER_EOF - - # Replace placeholders - sed -i '' "s/ARCH_PLACEHOLDER/${{ matrix.display }}/g" "bun-darwin-${{ matrix.arch }}-installer.sh" - sed -i '' "s/VERSION_PLACEHOLDER/${{ env.BUN_VERSION }}/g" "bun-darwin-${{ matrix.arch }}-installer.sh" - - chmod +x "bun-darwin-${{ matrix.arch }}-installer.sh" - - - name: Generate Checksums - run: | - cd sfx-build - - # Generate SHA256 checksums - shasum -a 256 "bun-darwin-${{ matrix.arch }}-sfx.sh" > "bun-darwin-${{ matrix.arch }}-sfx.sh.sha256" - shasum -a 256 "bun-darwin-${{ matrix.arch }}-installer.sh" > "bun-darwin-${{ matrix.arch }}-installer.sh.sha256" - - echo "Generated files:" - ls -la *.sh *.sha256 - - - name: Upload Artifacts - uses: actions/upload-artifact@v4 - with: - name: bun-darwin-${{ matrix.arch }}-sfx - path: | - sfx-build/bun-darwin-${{ matrix.arch }}-sfx.sh - sfx-build/bun-darwin-${{ matrix.arch }}-sfx.sh.sha256 - sfx-build/bun-darwin-${{ matrix.arch }}-installer.sh - sfx-build/bun-darwin-${{ matrix.arch }}-installer.sh.sha256 - - - name: Upload to Release - if: ${{ (github.event_name == 'release' || github.event.inputs.upload-to-release == 'true') && env.BUN_VERSION != 'canary' }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - cd sfx-build - - # Upload the SFX and installer scripts to the release - gh release upload "${{ env.BUN_VERSION }}" \ - "bun-darwin-${{ matrix.arch }}-sfx.sh" \ - "bun-darwin-${{ matrix.arch }}-sfx.sh.sha256" \ - "bun-darwin-${{ matrix.arch }}-installer.sh" \ - "bun-darwin-${{ matrix.arch }}-installer.sh.sha256" \ - --clobber \ - --repo "${{ github.repository }}" - - create-universal-installer: - name: Create Universal macOS Installer - runs-on: macos-latest - needs: create-macos-sfx - if: ${{ github.repository_owner == 'oven-sh' }} - permissions: - contents: write - steps: - - name: Download SFX Artifacts - uses: actions/download-artifact@v4 - with: - pattern: bun-darwin-*-sfx - merge-multiple: true - path: sfx-files - - - name: Create Universal Installer - run: | - cd sfx-files - - # Create universal installer script - cat > bun-darwin-universal-installer.sh << 'UNIVERSAL_EOF' - #!/bin/bash - # Universal Bun installer for macOS (detects architecture automatically) - - set -euo pipefail - - VERSION="VERSION_PLACEHOLDER" - - # Detect architecture - ARCH="$(uname -m)" - if [[ "$ARCH" == "x86_64" ]]; then - SFX_ARCH="x64" - elif [[ "$ARCH" == "arm64" ]]; then - SFX_ARCH="aarch64" - else - echo "Error: Unsupported architecture: $ARCH" - exit 1 - fi - - echo "Detected architecture: $ARCH" - echo "Downloading Bun $VERSION for macOS..." - - if [[ "$VERSION" == "canary" ]]; then - URL="https://github.com/oven-sh/bun/releases/download/canary/bun-darwin-${SFX_ARCH}-sfx.sh" - else - URL="https://github.com/oven-sh/bun/releases/download/$VERSION/bun-darwin-${SFX_ARCH}-sfx.sh" - fi - - curl -fsSL "$URL" | bash - UNIVERSAL_EOF - - # Replace version placeholder - sed -i '' "s/VERSION_PLACEHOLDER/${{ env.BUN_VERSION }}/g" bun-darwin-universal-installer.sh - - chmod +x bun-darwin-universal-installer.sh - - # Generate checksum - shasum -a 256 bun-darwin-universal-installer.sh > bun-darwin-universal-installer.sh.sha256 - - - name: Upload Universal Installer - uses: actions/upload-artifact@v4 - with: - name: bun-darwin-universal-sfx - path: | - sfx-files/bun-darwin-universal-installer.sh - sfx-files/bun-darwin-universal-installer.sh.sha256 - - - name: Upload to Release - if: ${{ (github.event_name == 'release' || github.event.inputs.upload-to-release == 'true') && env.BUN_VERSION != 'canary' }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - cd sfx-files - - gh release upload "${{ env.BUN_VERSION }}" \ - "bun-darwin-universal-installer.sh" \ - "bun-darwin-universal-installer.sh.sha256" \ - --clobber \ - --repo "${{ github.repository }}" diff --git a/docs/install/sfx.md b/docs/install/sfx.md index 82638ae21c..d846e8947b 100644 --- a/docs/install/sfx.md +++ b/docs/install/sfx.md @@ -1,52 +1,35 @@ -# Self-Extracting Archives (SFX) for Bun +# Self-Extracting Executables for Bun -Self-extracting archives provide a convenient way to install Bun without requiring external tools like `unzip`. These are shell scripts that contain the Bun binary embedded within them. +Bun provides true self-extracting executables for Linux that require **zero dependencies** - not even a shell. These are native binaries that contain the Bun executable embedded within them. ## Linux -### Quick Install +### Download and Run -For automatic architecture detection: - -```bash -curl -fsSL https://github.com/oven-sh/bun/releases/latest/download/bun-linux-install.sh | sh -``` - -### Manual Installation - -Choose the appropriate version for your system: +Download the appropriate executable for your system and run it directly: **x64 (with AVX2 support):** ```bash -curl -fsSL https://github.com/oven-sh/bun/releases/latest/download/bun-linux-x64-sfx.sh | sh +curl -LO https://github.com/oven-sh/bun/releases/latest/download/bun-linux-x64 +chmod +x bun-linux-x64 +./bun-linux-x64 ``` **x64 (baseline - no AVX2):** ```bash -curl -fsSL https://github.com/oven-sh/bun/releases/latest/download/bun-linux-x64-baseline-sfx.sh | sh +curl -LO https://github.com/oven-sh/bun/releases/latest/download/bun-linux-x64-baseline +chmod +x bun-linux-x64-baseline +./bun-linux-x64-baseline ``` **ARM64/AArch64:** ```bash -curl -fsSL https://github.com/oven-sh/bun/releases/latest/download/bun-linux-aarch64-sfx.sh | sh -``` - -### Download and Run Later - -You can also download the SFX file and run it later: - -```bash -# Download -curl -LO https://github.com/oven-sh/bun/releases/latest/download/bun-linux-x64-sfx.sh - -# Make executable -chmod +x bun-linux-x64-sfx.sh - -# Run -./bun-linux-x64-sfx.sh +curl -LO https://github.com/oven-sh/bun/releases/latest/download/bun-linux-aarch64 +chmod +x bun-linux-aarch64 +./bun-linux-aarch64 ``` ### Custom Installation Directory @@ -54,40 +37,33 @@ chmod +x bun-linux-x64-sfx.sh By default, Bun installs to `$HOME/.bun/bin`. You can override this: ```bash -BUN_INSTALL_DIR=/usr/local/bin ./bun-linux-x64-sfx.sh +BUN_INSTALL_DIR=/usr/local/bin ./bun-linux-x64 ``` ## How It Works -The self-extracting archives are shell scripts with these components: +The self-extracting executables are true native binaries: -1. **Shell Script Header**: A POSIX-compliant shell script that: +1. **Native Binary**: Written in C and compiled to a static executable +2. **Embedded Bun**: The Bun executable is compressed and embedded directly in the binary +3. **Zero Dependencies**: Statically linked - requires absolutely nothing to run +4. **Small Size**: Uses UPX compression to minimize file size - - Checks system compatibility - - Creates the installation directory - - Extracts the embedded binary - - Sets proper permissions - - Provides PATH setup instructions +When you run the executable, it: -2. **Embedded Binary**: The Bun executable is: - - - Compressed with gzip - - Encoded in base64 for text safety - - Appended to the shell script - -3. **No External Dependencies**: Only uses standard POSIX tools: - - `sh` (POSIX shell) - - `tar`, `gzip` (for extraction) - - `base64` (for decoding) - - `awk`, `tail` (for payload extraction) +1. Creates the installation directory +2. Decompresses the embedded Bun binary +3. Writes it to disk with proper permissions +4. Verifies the installation +5. Provides PATH setup instructions ## Advantages -- **No `unzip` Required**: Works on minimal Linux systems -- **Single File**: Everything in one downloadable script +- **Zero Dependencies**: No shell, no tar, no gzip, no base64 - nothing required +- **Single File**: One self-contained executable - **Secure**: Checksums provided for verification -- **Portable**: POSIX-compliant, works on most Linux distributions -- **Architecture Detection**: Universal installer automatically selects the right binary +- **Universal**: Works on any Linux system with the matching architecture +- **Fast**: Native code extraction is faster than shell scripts ## Verification @@ -95,8 +71,18 @@ Always verify downloads using the provided checksums: ```bash # Download checksum -curl -LO https://github.com/oven-sh/bun/releases/latest/download/bun-linux-x64-sfx.sh.sha256 +curl -LO https://github.com/oven-sh/bun/releases/latest/download/bun-linux-x64.sha256 # Verify -sha256sum -c bun-linux-x64-sfx.sh.sha256 +sha256sum -c bun-linux-x64.sha256 ``` + +## Technical Details + +The self-extracting executables are built using: + +- **Language**: C +- **Compression**: zlib (gzip compatible) +- **Executable Compression**: UPX with LZMA +- **Linking**: Static (no shared library dependencies) +- **Cross-compilation**: Supports x64 and aarch64 targets diff --git a/src/cli/install.sh b/src/cli/install.sh index e4e1fa430e..aaddd08e05 100644 --- a/src/cli/install.sh +++ b/src/cli/install.sh @@ -56,14 +56,9 @@ success() { # Check for unzip and offer SFX alternative if not available use_sfx=0 if ! command -v unzip >/dev/null 2>&1; then - echo -e "${Dim}unzip is not available. Using self-extracting archive instead.${Color_Off}" + echo -e "${Dim}unzip is not available. Using self-extracting executable instead.${Color_Off}" use_sfx=1 - - # Check for required tools for SFX - for tool in sh tar gzip base64 awk tail; do - command -v "$tool" >/dev/null 2>&1 || - error "$tool is required for self-extracting archive installation" - done + # No tool checks needed - SFX executables have zero dependencies! fi if [[ $# -gt 2 ]]; then @@ -161,21 +156,34 @@ if [[ $use_sfx -eq 1 ]]; then linux-x64-musl) sfx_name="bun-linux-x64-musl" ;; linux-x64-musl-baseline) sfx_name="bun-linux-x64-musl-baseline" ;; linux-aarch64-musl) sfx_name="bun-linux-aarch64-musl" ;; - *) error "Self-extracting archives are not available for $target. Please install unzip." ;; + *) error "Self-extracting executables are not available for $target. Please install unzip." ;; esac if [[ $# = 0 ]]; then - sfx_uri=$github_repo/releases/latest/download/$sfx_name-sfx.sh + sfx_uri=$github_repo/releases/latest/download/$sfx_name else - sfx_uri=$github_repo/releases/download/$1/$sfx_name-sfx.sh + sfx_uri=$github_repo/releases/download/$1/$sfx_name fi - info "Downloading self-extracting archive..." + info "Downloading self-extracting executable..." + info "This is a zero-dependency native executable." - # Download and run the SFX with custom install directory + # Download the SFX executable + sfx_temp="$exe.sfx.tmp" + curl --fail --location --progress-bar --output "$sfx_temp" "$sfx_uri" || + error "Failed to download self-extracting executable from \"$sfx_uri\"" + + # Make it executable + chmod +x "$sfx_temp" || + error "Failed to set permissions on self-extracting executable" + + # Run it with custom install directory export BUN_INSTALL_DIR="$bin_dir" - curl --fail --location --progress-bar "$sfx_uri" | sh || - error "Failed to download and run self-extracting archive from \"$sfx_uri\"" + "$sfx_temp" || + error "Failed to run self-extracting executable" + + # Clean up + rm -f "$sfx_temp" # The SFX installs directly to bin_dir, so we're done with extraction exe=$bin_dir/bun