Last active
February 18, 2025 20:20
-
-
Save kigster/b9519732c227da4618cb41ec1f101aa6 to your computer and use it in GitHub Desktop.
Ruby Install Minimal script for MacOS and Linux. You can use it to install a recent Ruby using `rbenv` and `ruby-build`, with Jemalloc & YJIT enabled. Run it like so: `bash -c "$(curl -fsSL https://bit.ly/ruby-install-0-2-1)" -- 3.4.1`
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env bash | |
# vim: ft=bash | |
# | |
# © 2025 Konstantin Gredeskoul <kig AT kig.re> | |
# 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-0-2-1)" -- 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-0-2-1)" -- ' | |
# | |
# 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 <version>" 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 <version>${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 "$@" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Installation on Ubuntu 20.04: