Skip to content

Instantly share code, notes, and snippets.

@kigster
Last active February 18, 2025 20:20
Show Gist options
  • Save kigster/b9519732c227da4618cb41ec1f101aa6 to your computer and use it in GitHub Desktop.
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`
#!/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 "$@"
@kigster
Copy link
Author

kigster commented Feb 18, 2025

Installation on Ubuntu 20.04:

CleanShot 2025-02-18 at 12 15 43

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment