#!/usr/bin/env bash # vim: ft=bash # # © 2025 Konstantin Gredeskoul # All rights reserved. # MIT License. # # This script uses rbenv/ruby-build to install Ruby on OS-X or Linux. It configures # Ruby build flags in such a way to ensure Ruby is linked with libjemalloc (which # reduces the memory by half) and YJIT enabled. It has been tested on both Linux and # MacOS. # # The script has no dependencies on Bashmatic Library, and can be invoked and # executed as a standalone script. This is why it also exists as a Github Gist: # https://bit.ly/ruby-install-sh # # This allows running it directly from the command line and is the recommended way, # eg to install Ruby 3.4.1 with YJIT and Jemalloc enabled: # # bash -c "$(curl -fsSL https://bit.ly/ruby-install-sh)" -- 3.4.1 # # Or if you are comfortable using BASH alises, you can add this to your ~/.bashrc: # # alias rb-install='bash -c "$(curl -fsSL https://bit.ly/ruby-install-sh)" -- ' # # And then, anytime you need a new Ruby installed, it just can't get any simpler than: # # rb-install 3.4.1 # rb-install 3.3.6 # #────────────────────────────────────────────────────────────────────────────────── # NOTE: On Linux you might need to set either $RBENV_HOME or $RBENV_ROOT variables. # # NOTE: To pass additional flags to "rbenv install " set the variable # $RBENV_INSTALL_FLAGS to the desired flags (default is -s). For example # you may want to keep the sources around in case the build failed, by # setting $RBENV_INSTALL_FLAGS="-k". # # USAGE: # # Argument overrides any other ruby version passing method # install-ruby 3.4.1 # # # if a file .ruby-version exists in the current directory, we install that version # install-ruby # # # or if $RUBY_VERSION is defined # export RUBY_VERSION=3.5.5 # install-ruby #──────────────────────────────────────────────────────────────────────────────────────────────────────── # # This block here work on ZSH and BASH. It attempts to determine # whether you ran "source dev" to get access to the BASH functions # defined therein; OR you ran "./dev" to get the application started # in a subshell. The __run_as_script variable is set to 1 in the case # when the script is ran, not sourced, and zero otherwise. # if [[ -n ${ZSH_EVAL_CONTEXT} && ${ZSH_EVAL_CONTEXT} =~ :file$ ]] || [[ -n $BASH_VERSION && $0 != "${BASH_SOURCE[0]}" ]] ; then export __run_as_script=0 2>/dev/null else export __run_as_script=1 2>/dev/null fi # shellcheck disable=SC2086 export txtblk='\e[0;30m' # Black - Regular export txtred='\e[0;31m' # Red export txtgrn='\e[0;32m' # Green export txtylw='\e[0;33m' # Yellow export txtblu='\e[0;34m' # Blue export txtpur='\e[0;35m' # Purple export txtcyn='\e[0;36m' # Cyan export txtwht='\e[0;37m' # White export errclr='\e[0;101m' # White on red export bold='\e[1m' # Text Reset export clr='\e[0m' # Text Reset export SCRIPT_VERSION="0.2.1" export OS="$(uname -s | tr '[:upper:]' '[:lower:]')" line() { echo "────────────────────────────────────────────────────────────────────────────────────────────────────────"; } now() { date '+%F.%T.%S ' | tr -d '\:\-\.'; } version() { echo -e "${txtylw}v$SCRIPT_VERSION${clr}"; } inf() { echo -e "${txtgrn}${bold}──────────────────────────┤ $(printf '%s' "$*") ${clr}"; } function ts() { echo -n "$(date '+%H:%M:%S') ${txtpur}ruby-install${clr} | "; } function inf_() { echo -n -e "${txtblu}$(ts)${txtcyn}$*${clr}"; } function inf() { inf_ "$@"; echo; } function fnc() { echo -e "${txtblu}$(ts)${txtylw}$(shift)$*${clr}"; } function wrn() { echo -e "${txtylw}$(ts)${txtylw}$*${clr}"; } function err() { echo -e "\n${txtred}$(ts)${txtred}💀 ${errclr}${bold}$*${clr}\n" >&2 } # shellcheck disable=SC2120 function ok() { echo -e "${txtgrn}${bold}${*:-"[ OK ]"} ✅ ${clr}"; } # shellcheck disable=SC2120 function fail() { echo -e "${txtred}${bold}${*:-"FAILED"} ❌ ${clr}"; } function puts() { echo -e "$*${clr}"; } # shellcheck disable=SC2120 status-ok() { local msg="${*:-""}" echo -e "${txtgrn}${bold}[ ✔️ ]${clr}" [[ -n "$msg" ]] && echo -e " (INFO: $*)${clr}" echo } status-err() { echo -e -n "${txtred}${bold}[ ✖️ ] " [[ -n "$*" ]] && echo -e "(ERROR: $*)${clr}" echo } print() { echo -e "$@" } # Execute a command and show the result, and/or the output of the command. function run_command() { inf_ "❯ ${green}$(printf '%-60.60s' "$*") " set +e # Run the command if is-verbose; then echo eval "$*" else eval "$*" >/dev/null 2>&1 fi local code=$? ((__run_as_script)) && set -e if [[ ${code} -eq 0 ]]; then ok else fail fi return ${code} } header() { line print "\ ${txtgrn}Ruby Installer Script | Version $(version)${txtgrn} ${OS^} ($(arch)) ${txtblu} ${txtgrn}DESCRIPTION:${txtblu} A BASH script that automates installation and building of the Ruby Interpreter using rbenv and ruby-bulid, with YJIT enabled, linked with libjemalloc, as well as OpenSSL and libyaml. Supported Operating Systems: Linux (Ubuntu) and MacOS (tested on Sequoia). © 2025 Konstantin Gredeskoul, https://kig.re/, @kigster on Github.${clr} " line } usage() { local executable="ruby-install" print " ${txtgrn}NAME: ${txtylw}install-ruby ${txtgrn}USAGE: ${txtylw}${executable} [ ruby-version ] ${clr} # install Ruby Version pass as argument ${txtylw}${executable} ${clr} # install Ruby Version from .ruby-version file ${txtylw}export RUBY_VERSION=3.4.1 ${txtylw}${executable} ${clr} # Install Ruby using the env variable as a version. ${txtgrn}ONLINE USAGE: ${txtylw}bash -c \"\$(curl -fsSL https://bit.ly/ruby-install-$(echo "${SCRIPT_VERSION}" | sed 's/[.]/-/g'))\" -- 3.4.1${clr} " line exit 0 } declare NOW function parse-args() { NOW="$(now | tr -d '\n ')" export NOW if [[ $* =~ (-h|--help) ]]; then header usage fi } function configure() { fnc "Identifying the Ruby Version to install..." export RUBY_VERSION_ARG="$1" export DEFAULT_RUBY_VERSION="${DEFAULT_RUBY_VERSION:-"3.4.1"}" export RUBY_FILE_VERSION="${RUBY_VERSION:-"$(test -f .ruby-version && cat .ruby-version)"}" export RBENV_INSTALL_FLAGS="$RBENV_INSTALL_FLAGS --skip-existing" if [[ $RUBY_VERSION_ARG =~ ([0-9]+.[0-9]+) ]]; then export RUBY_VERSION=$RUBY_VERSION_ARG fi export RUBY_VERSION="${RUBY_VERSION:-${RUBY_FILE_VERSION:-${DEFAULT_RUBY_VERSION}}}" echo -e "${txtpur}RUBY Version to Install : ${txtcyn}${RUBY_VERSION}" declare SUDO if [[ $OS =~ darwin ]]; then export RUBY_CFLAGS="-Wno-error=implicit-function-declaration" export OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES export PACKAGES="rbenv jemalloc" export SUDO=false export INSTALLER="brew" export INSTALLER_FLAGS=" --quiet " export INSTALL_COMMAND="install" export UNINSTALL_COMMAND="uninstall" else export PACKAGES="rbenv libjemalloc2" export SUDO=true export INSTALLER="apt-get" export INSTALLER_FLAGS=" -yqq --silent " export INSTALL_COMMAND="install" export UNINSTALL_COMMAND="remove" fi export SUDO_PREFIX="" [[ $SUDO == true ]] && export SUDO_PREFIX="sudo " export RBENV_ROOT="${RBENV_ROOT-"${RBENV_HOME:-"${HOME}/.rbenv"}"}" [[ -n $RBENV_ROOT && ! -d $RBENV_ROOT ]] && mkdir -p "$RBENV_ROOT" export OPT_DIR=$(if [[ -d /opt/homebrew ]]; then echo /opt/homebrew; else echo /usr/local; fi) export RUBY_CONFIGURE_OPTS="--with-jemalloc --enable-yjit --with-opt-dir=${OPT_DIR}" export RUBY_YJIT_ENABLE=1 # rustc --version | awk '{print $2}' | tr '.' '0' export MIN_RUSTC_VERSION=108300 } function version-integer() { echo "$1" | awk '{print $2}' | sed 's/[._]/0/g' } function rust-check() { inf "Rust Compiler installation and version check..." if [[ -x "$(command -v rustc)" ]]; then local rust_version rust_version="$(version-integer "$(rustc --version)" )" if [[ $rust_version -lt $MIN_RUSTC_VERSION ]]; then echo "Rust compiler is installed, but the version is older — $(rustc --version)." echo "Removing and reinstalling..." rust-reinstall else echo "Rust compiler is installed and is up to date." fi else echo "Rust compiler is not installed. Installing..." rust-reinstall fi } # OS-independent rustc reinstaller function rust-reinstall() { inf "(Re)-Installing Rust compiler..." [[ -x "$(command -v rustup)" ]] && rustup default stable eval "${SUDO_PREFIX} ${INSTALLER} ${UNINSTALL_COMMAND} rustc ${INSTALLER_FLAGS} 2>/dev/null 1>&2 || true" eval "${SUDO_PREFIX} ${INSTALLER} ${UNINSTALL_COMMAND} rust ${INSTALLER_FLAGS} 2>/dev/null 1>&2 || true" hash -r >/dev/null 2>&1 || true echo -e "${txtpur}System Package Installer : ${txtcyn}${INSTALLER}" echo -n -e "${txtpur}Installing Rustup : " export RUSTUP_INIT_SKIP_PATH_CHECK=yes ( curl -fsSL https://sh.rustup.rs | sh -s -- -y ) >/dev/null 2>&1 local install_result=$? if [[ $install_result -ne 0 ]]; then status-err "Rustup installation failed with status ${install_result}" exit $install_result else status-ok fi # shellcheck disable=SC1091 [[ -s "${HOME}/.cargo/env" ]] && source "${HOME}/.cargo/env" rustup default stable } function pre-install-darwin() { if [[ ! -x "$(command -v brew)" ]]; then inf "Installing Homebrew as none were found..." echo "Installing Homebrew..." /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" fi } # function pre-install-linux() { # # linux specific installation steps # } function pre-install() { # Invoke pre-installer pre_install_function="pre-install-${OS}" [[ -n "$(type -t "${pre_install_function}")" && "$(type -t "${pre_install_function}")" == "function" ]] && eval "${pre_install_function}" rust-check } function print-configuration() { line inf "Install Configuration:" inf " " inf "${txtpur}System Package Installer : ${txtcyn}${INSTALLER}" inf "${txtpur}Installer Flags : ${txtcyn}${INSTALLER_FLAGS}" inf "${txtpur}Packages To Install : ${txtcyn}${PACKAGES}" line inf "${txtpur}RustC Compiler : ${txtcyn}$(which rustc)" inf "${txtpur}RustC Version : ${txtcyn}$(rustc --version)" inf "${txtpur}RUBY YJIT Enabled? : ${txtcyn}$([[ $RUBY_CONFIGURE_OPTS =~ enable-yjit ]] && echo 'YES' || echo 'NO')" inf "${txtpur}RBENV ROOT : ${txtcyn}${RBENV_ROOT}" inf "${txtpur}RUBY_CONFIGURE_OPTS : ${txtcyn}${RUBY_CONFIGURE_OPTS}" [[ -n $RUBY_CFLAGS ]] && \ inf "${txtpur}RUBY_CFLAGS : ${txtcyn}${RUBY_CFLAGS}${clr}" echo line } declare SUDO_PREFIX function installer-update() { eval "${SUDO_PREFIX} ${INSTALLER} update ${INSTALLER_FLAGS} >/dev/null || true" } function rbenv-update() { command -V rbenv >/dev/null || { inf "Installing rbenv..." eval "${SUDO_PREFIX} ${INSTALLER} install ${INSTALLER_FLAGS} rbenv" } export RBENV_ROOT=${RBENV_ROOT:-$(rbenv --prefix)} [[ -d $RBENV_ROOT/plugins ]] || mkdir -p "${RBENV_ROOT}/plugins" # Perform this update in a sub-shell ( cd "${RBENV_ROOT}/plugins" inf "Updating ruby-build..." [[ -d ruby-build ]] || git clone https://github.com/rbenv/ruby-build.git >/dev/null cd ruby-build && git pull ) export PATH="$RBENV_ROOT/bin:$RBENV_ROOT/shims:$PATH" } function packages-install() { eval "${SUDO_PREFIX} ${INSTALLER} ${INSTALL_COMMAND} ${INSTALLER_FLAGS} ${PACKAGES} || true" } function install-ruby() { set +e local SH SH="$(basename "${SHELL}")" # Initialize rbenv eval "$(rbenv init - "${SH}")" line echo inf "Installing Ruby (using -s flag, i.e. skipping existing.)" inf "If you need to overwrite an existing installation, please" inf "uninstall it using first using the command ${txtylw}rbenv uninstall ${clr}" inf " " inf "${clr}❯ ${txtylw}rbenv install -s ${RUBY_VERSION}${clr}\n" inf "${txtpur} ⏳ Please wait while your Ruby is being cooked...${clr}\n" output="/tmp/ruby-$RUBY_VERSION/build" mkdir -p "$output" set +e rbenv install -s "${RUBY_VERSION}" 1>"${output}"/"${NOW}".stdout 2>"${output}"/"${NOW}".stderr status=$? if [[ $status -ne 0 ]]; then line echo -e "${errclr}ERROR : → Ruby ${RUBY_VERSION} installation has failed with status ${status}.${clr}" sleep 0.5 [[ -s ${output}/${NOW}.stdout ]] && cp "${output}/${NOW}.stdout" . [[ -s ${output}/${NOW}.stderr ]] && cp "${output}/${NOW}.stderr" . [[ -s ${NOW}.stderr ]] && { err "${txtred}Standard Error:${clr}" echo -e "${txtred}" line cat "${NOW}.stderr" echo -e "${clr}" } [[ -s ${NOW}.output ]] && { inf "${txtcyn}NOTE : → Standard Output is available in the file ${txtylw}${NOW}.output${clr}" } [[ -s ${NOW}.stderr || -s ${NOW}.stdout ]] || { wrn "${txtylw}WARNING : → No STDOUT or STDERR was generated.${clr}" } inf "${txtgrn}HINT : → Set env variable RBENV_INSTALL_FLAGS=-k to keep Ruby Source code around.${clr}\n" exit $status else line inf "${bold}${txtgrn}SUCCESS : → Ruby ${RUBY_VERSION} installed successfully.${clr}" line echo fi rbenv global "${RUBY_VERSION}" echo "RBENV Status about currently install versions:" rbenv versions echo line } declare temp_dir function handle-ruby-version() { # Handle local .ruby-version if [[ -s .ruby-version ]]; then temp_dir=$(mktemp -d) export temp_dir mv -v .ruby-version "${temp_dir}/" fi } function print-post-install-status() { # Print the final results of the installation. echo -e "${txtylw}Ruby $RUBY_VERSION Build, Platform, Architecture, YJIT:" echo -e " ${txtgrn}$(ruby --version)${clr}\n" echo -e "${txtylw}Configure Flags:" echo -e "${txtgrn}$(ruby -e 'puts RbConfig::CONFIG["configure_args"].strip' | tr -d '"' | tr -d "'" | sed 's/^\s*//g' | fold -w 60 -s | sed 's/^/ /g')\e[0m\n" echo -e "${txtylw}Linked Libraries:" echo -e " ${txtgrn}$(ruby -e 'pp RbConfig::CONFIG["MAINLIBS"]' | tr -d '"')\e[0m\n" # Restore .ruby-version in the current directory if it were moved. if [[ -n $temp_dir && -d $temp_dir && -s $temp_dir/.ruby-version ]]; then mv -v $temp_dir/.ruby-version . fi } main() { fnc "parse-args($*)" parse-args "$@" fnc "header()" header fnc "configure($*)" configure "$@" fnc "pre-install()" pre-install fnc "installer-update()" installer-update fnc "rbenv-update()" rbenv-update fnc "packages-install()" packages-install fnc "print-configuration()" print-configuration fnc "handle-ruby-version()" handle-ruby-version fnc "install-ruby()" install-ruby fnc "print-post-install-status()" print-post-install-status } main "$@"