diff --git a/priv/script.sh b/priv/script.sh index 03f228e9..9dff34d0 100755 --- a/priv/script.sh +++ b/priv/script.sh @@ -2,441 +2,489 @@ set -eu +elixir_version=1.17.3-otp-27 +erlang_version=27.1.2 +phoenix_version=1.7.14 +postgres_version=15.1 + +OS="$(uname -s)" +case "$OS" in +Linux*) os_type=Linux ;; +Darwin*) os_type=macOS ;; +*) + printf >&2 "Unsupported OS: %s\n" "$OS" + exit 1 + ;; +esac + +has() { + command -V "${1:?}" >/dev/null 2>/dev/null +} + +mise_has() { + mise which "${1:?}" >/dev/null 2>/dev/null +} + +mise_has_version() { + has mise && + mise_has "${1:?}" && + [ "$(mise which --version "${1:?}")" = "${2:?}" ] +} + # Make sure important variables exist if not already defined # # $USER is defined by login(1) which is not always executed (e.g. containers) # POSIX: https://pubs.opengroup.org/onlinepubs/009695299/utilities/id.html USER=${USER:-$(id -u -n)} + # $HOME is defined at the time of login, but it could be unset. If it is unset, # a tilde by itself (~) will not be expanded to the current user's home directory. # POSIX: https://pubs.opengroup.org/onlinepubs/009696899/basedefs/xbd_chap08.html#tag_08_03 -HOME="${HOME:-$(getent passwd $USER 2>/dev/null | cut -d: -f6)}" -# macOS does not have getent, but this works even if $HOME is unset -HOME="${HOME:-$(eval echo ~$USER)}" - -bold='\033[1m' -normal='\033[0m' -red='\033[0;31m' -blue='\033[0;34m' -bblue='\033[1;34m' -white='\033[0;37m' -green='\033[0;32m' -cyan='\033[0;36m' - -elixir_version=1.17.2-otp-27 -erlang_version=27.0.1 -phoenix_version=1.7.14 -postgres_version=15.1 +if [ -z "$HOME" ]; then + set +e + if [ "$os_type" = Linux ]; then + HOME="$(getent passwd "$USER" 2>/dev/null | cut -d: -f6)" + elif [ "$os_type" = macOS ]; then + HOME="$(dscl . -read /Users/"$USER" NFSHomeDirectory)" + fi + + # If neither of the above commands works, fall back to `~$USER`. + HOME="${HOME:-$(eval echo ~"$USER")}" + set -e +fi + +# Disable colour output if NO_COLOR is set to any value at all. +# https://no-color.org +if [ -n "${NO_COLOR:-}" ]; then + bold='' + normal='' + bblue='' + cyan='' +elif has tput; then + # Prefer using `tput` for highlighting over manual ANSI colour codes. This + # would allow xterm 256 colour as well. + bold="$(tput bold)" + normal="$(tput sgr0)" + bblue="$(tput setaf 11)" + cyan="$(tput setaf 6)" +else + bold='\033[1m' + normal='\033[0m' + bblue='\033[1;34m' + cyan='\033[0;36m' +fi + +phx_tools_path="$HOME"/.config/phx_tools +phx_tools_sh_path="$phx_tools_path"/phx_tools.sh +phx_tools_fish_path="$phx_tools_path"/phx_tools.fish +homebrew_path= case "${SHELL:-}" in -*bash) - current_shell="bash" - config_file="$HOME/.bashrc" - ;; -*fish) - current_shell="fish" - config_file="$HOME/.config/fish/config.fish" - mkdir -p "$(dirname "$config_file")" - ;; -*zsh) - current_shell="zsh" - config_file="$HOME/.zshrc" - ;; +*/bash | */fish | */zsh) : ;; +'') + printf >&2 "Cannot discover shell, \$SHELL is unset.\n" + exit 1 + ;; *) - printf "Unsupported shell: %s\n" "$SHELL" - exit 1 - ;; + printf >&2 "Unsupported shell: %s\n" "$SHELL" + exit 1 + ;; esac -# Add OS detection -OS="$(uname -s)" - get_package_manager() { - if command -v apt-get >/dev/null; then - echo "apt" - elif command -v dnf >/dev/null; then - echo "dnf" - elif command -v pacman >/dev/null; then - echo "pacman" - elif command -v apk >/dev/null; then - echo "apk" + if has apt-get; then + echo apt + elif has dnf; then + echo dnf + elif has pacman; then + echo pacman + elif has apk; then + echo apk else - printf "Unsupported package manager. This script requires apt, dnf, pacman, or apk.\n" + printf >&2 "Unsupported package manager.\n" + printf >&2 "This script requires apt, dnf, pacman, or apk.\n" exit 1 fi } -case "${OS}" in - Linux*) - os_type=Linux - package_manager=$(get_package_manager) - ;; - Darwin*) - os_type=macOS - ;; +if [ "$os_type" = macOS ]; then + package_manager=homebrew + + if [ "$(uname -m)" = arm64 ]; then + homebrew_path=/opt/homebrew/bin + else + homebrew_path=/usr/local/bin + fi +else + package_manager="$(get_package_manager)" +fi + +already_installed() { + case "$1" in + Elixir) mise_has_version elixir "$elixir_version" ;; + Erlang) mise_has_version erl "$erlang_version" ;; + git) has git ;; + Homebrew) has brew ;; + mise) has mise ;; + Phoenix) has mix && mix phx.new --version >/dev/null 2>&1 ;; + PostgreSQL) mise_has initdb ;; + "Xcode Command Line Tools") has xcode-select ;; *) - printf "Unsupported OS: %s\n" "${OS}" + printf >&2 "Unknown tool provided for install check: %s\n" "$1" exit 1 ;; -esac + esac +} -already_installed() { - case "$1" in - "Elixir") - mise which elixir >/dev/null 2>&1 - ;; - "Erlang") - mise which erl >/dev/null 2>&1 - ;; - "git") - which git >/dev/null 2>&1 - ;; - "Homebrew") - which brew >/dev/null 2>&1 - ;; - "mise") - which mise >/dev/null 2>&1 - ;; - "Phoenix") - mix phx.new --version >/dev/null 2>&1 - ;; - "PostgreSQL") - mise which initdb >/dev/null 2>&1 - ;; - "Xcode Command Line Tools") - which xcode-select >/dev/null 2>&1 - ;; - *) - printf "Invalid name argument on checking: %s\n" "$1" - exit 1 - ;; +install_deps() { + case "$1" in + Elixir) + case "$package_manager" in + apt) + sudo apt-get update + sudo apt-get install -y unzip + ;; + dnf) sudo dnf install -y unzip ;; + pacman) sudo pacman -Sy --noconfirm unzip ;; + apk) sudo apk add --no-cache unzip ;; + esac + ;; + Erlang) + case "$package_manager" in + homebrew) + brew install autoconf openssl@3 wxwidgets libxslt fop + + if [ ! -f "$HOME"/.kerlrc ]; then + printf "KERL_CONFIGURE_OPTIONS=\"--with-ssl=%s --without-javac\"\n" \ + "'$(brew --prefix openssl@3)'" >"$HOME"/.kerlrc + fi + + # shellcheck disable=SC3045 + ulimit -n 1024 + ;; + apt) + sudo apt-get update + sudo apt-get install -y build-essential automake autoconf libssl-dev \ + libncurses5-dev + ;; + dnf) + sudo dnf groupinstall -y "Development Tools" + sudo dnf install -y openssl-devel ncurses-devel + ;; + pacman) + sudo pacman -Sy --noconfirm base-devel openssl ncurses + ;; + apk) + sudo apk add --no-cache build-base autoconf openssl-dev ncurses-dev + ;; + esac + + if [ "$os_type" = Linux ] && [ ! -f "$HOME"/kerlrc ]; then + printf "KERL_CONFIGURE_OPTIONS=\"--without-javac\"\n" >"$HOME"/.kerlrc + fi + ;; + PostgreSQL) + case "$package_manager" in + homebrew) brew install gcc readline zlib curl ossp-uuid ;; + apt) + sudo apt-get update + sudo apt-get install -y linux-headers-generic build-essential \ + libssl-dev libreadline-dev zlib1g-dev libcurl4-openssl-dev uuid-dev \ + icu-devtools + ;; + dnf) + sudo dnf groupinstall -y "Development Tools" + sudo dnf install -y kernel-headers openssl-devel readline-devel \ + zlib-devel libcurl-devel libuuid-devel libicu-devel + ;; + pacman) + sudo pacman -Sy --noconfirm linux-headers base-devel openssl readline \ + zlib curl util-linux icu + ;; + apk) + sudo apk add --no-cache linux-headers build-base openssl-dev \ + readline-dev zlib-dev curl-dev util-linux-dev icu-dev + ;; esac + ;; + esac } install() { + if [ -n "${DRY_RUN:-}" ]; then + return + fi + + install_deps "$1" + + case "$1" in + Elixir) mise use -g -y elixir@$elixir_version ;; + Erlang) mise use -g -y erlang@$erlang_version ;; + git) + case "$package_manager" in + "apt") + sudo apt-get update + sudo apt-get install -y git + ;; + "dnf") + sudo dnf install -y git + ;; + "pacman") + sudo pacman -Sy --noconfirm git + ;; + "apk") + sudo apk add --no-cache git + ;; + esac + ;; + Homebrew) + NONINTERACTIVE=1 /bin/bash -c \ + "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" + + eval "$("$homebrew_path"/brew shellenv sh)" + ;; + mise) + curl https://mise.run | sh + + export PATH="$HOME/.local/bin:$PATH" + ;; + Phoenix) + mise exec -- mix local.hex --force + mise exec -- mix local.rebar --force + mise exec -- mix archive.install --force hex phx_new $phoenix_version + ;; + PostgreSQL) + mise use -g -y postgres@$postgres_version + ;; + "Xcode Command Line Tools") + xcode-select --install + ;; + *) + printf >&2 "Unknown tool provided for install: %s\n" "$1" + exit 1 + ;; + esac +} + +maybe_install() { + if already_installed "$1"; then + printf "%s is already installed. Skipping...\n" "$1" + else case "$1" in - "Elixir") - if [ "$os_type" = "Linux" ]; then - case "$package_manager" in - "apt") - sudo apt-get update - sudo apt-get install -y unzip - ;; - "dnf") - sudo dnf install -y unzip - ;; - "pacman") - sudo pacman -Sy --noconfirm unzip - ;; - "apk") - sudo apk add --no-cache unzip - ;; - esac - fi - - mise use -g -y elixir@$elixir_version - ;; - "Erlang") - if [ "$os_type" = "macOS" ]; then - brew install autoconf openssl@1.1 wxwidgets libxslt fop - - if [ ! -f ~/.kerlrc ]; then - printf "KERL_CONFIGURE_OPTIONS=\"--with-ssl=$(brew --prefix openssl@1.1) --without-javac\"\n" >~/.kerlrc - fi - - ulimit -n 1024 - else - case "$package_manager" in - "apt") - sudo apt-get update - sudo apt-get install -y build-essential automake autoconf libssl-dev libncurses5-dev - ;; - "dnf") - sudo dnf groupinstall -y "Development Tools" - sudo dnf install -y openssl-devel ncurses-devel - ;; - "pacman") - sudo pacman -Sy --noconfirm base-devel openssl ncurses - ;; - "apk") - sudo apk add --no-cache build-base autoconf openssl-dev ncurses-dev - ;; - esac - - if [ ! -f ~/.kerlrc ]; then - printf "KERL_CONFIGURE_OPTIONS=\"--without-javac\"\n" >~/.kerlrc - fi - fi - mise use -g -y erlang@$erlang_version - - ;; - "git") - if [ "$os_type" = "Linux" ]; then - case "$package_manager" in - "apt") - sudo apt-get update - sudo apt-get install -y git - ;; - "dnf") - sudo dnf install -y git - ;; - "pacman") - sudo pacman -Sy --noconfirm git - ;; - "apk") - sudo apk add --no-cache git - ;; - esac - fi - ;; - "Homebrew") - NONINTERACTIVE=1 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" - UNAME_MACHINE="$(/usr/bin/uname -m)" - - if [[ "${UNAME_MACHINE}" == "arm64" ]]; then - if [ "$current_shell" = "fish" ]; then - fish_add_path /opt/homebrew/bin - else - ( - echo - echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' - ) >>$config_file - fi - eval "$(/opt/homebrew/bin/brew shellenv)" - else - if [ "$current_shell" = "fish" ]; then - fish_add_path /usr/local/bin - else - ( - echo - echo 'eval "$(/usr/local/bin/brew shellenv)"' - ) >>$config_file - fi - eval "$(/usr/local/bin/brew shellenv)" - fi - ;; - "mise") - curl https://mise.run | sh - - case $current_shell in - "bash") - echo 'eval "$(~/.local/bin/mise activate bash)"' >>$config_file - ;; - "fish") - echo '~/.local/bin/mise activate fish | source' >>$config_file - ;; - "zsh") - echo 'eval "$(~/.local/bin/mise activate zsh)"' >>$config_file - ;; - esac - - export PATH="$HOME/.local/bin:$PATH" - ;; - "Phoenix") - mise exec -- mix local.hex --force - mise exec -- mix local.rebar --force - mise exec -- mix archive.install --force hex phx_new $phoenix_version - ;; - "PostgreSQL") - if [ "$os_type" = "macOS" ]; then - brew install gcc readline zlib curl ossp-uuid - else - case "$package_manager" in - "apt") - sudo apt-get update - sudo apt-get install -y linux-headers-generic build-essential libssl-dev libreadline-dev zlib1g-dev libcurl4-openssl-dev uuid-dev icu-devtools - ;; - "dnf") - sudo dnf groupinstall -y "Development Tools" - sudo dnf install -y kernel-headers openssl-devel readline-devel zlib-devel libcurl-devel libuuid-devel libicu-devel - ;; - "pacman") - sudo pacman -Sy --noconfirm linux-headers base-devel openssl readline zlib curl util-linux icu - ;; - "apk") - sudo apk add --no-cache linux-headers build-base openssl-dev readline-dev zlib-dev curl-dev util-linux-dev icu-dev - ;; - esac - fi - mise use -g -y postgres@$postgres_version - ;; - "Xcode Command Line Tools") - xcode-select --install - ;; + Homebrew | Erlang) + printf "Installing %s... (this might take a while)\n" "$1" + ;; *) - printf "Invalid name argument on install: %s\n" "$1" - exit 1 - ;; + printf "Installing %s...\n" "$1" + ;; esac + + install "$1" + fi } -maybe_install() { - if already_installed "$1"; then - printf "%s is already installed. Skipping...\n" "$1" - else - printf "Installing %s...\n" "$1" +install_shell_scripts() { + mkdir -p "$phx_tools_path" + + local_bin=\"\$HOME\"/.local/bin + + cat >"$phx_tools_sh_path" <>"$phx_tools_sh_path" <>"$phx_tools_sh_path" </dev/null 2>/dev/null && [ -x $local_bin/mise ]; then + export PATH="$local_bin:\$PATH" +fi + +eval "\$(mise activate "\$(basename \$SHELL)")" +SH + + cat >"$phx_tools_fish_path" <>"$phx_tools_fish_path" <>"$phx_tools_fish_path" <>"$HOME"/.bashrc fi -} + fi -add_env() { - printf "\n" - - # Ask for sudo password upfront - sudo -v - - # Keep sudo alive - while true; do - sudo -n true - sleep 60 - kill -0 "$$" || exit - done 2>/dev/null & - - if [ "$os_type" = "macOS" ]; then - printf "${white}\n" - sleep 1.5 - maybe_install "Xcode Command Line Tools" - - printf "${white}\n" - sleep 1.5 - maybe_install "Homebrew" - else - printf "${white}\n" - sleep 1.5 - maybe_install "git" + if has zsh && [ -f "$HOME"/.zshrc ]; then + if ! grep -q "$sh_source_line" "$HOME"/.zshrc; then + printf "zsh: updating %s/.zshrc to source %s\n" "$HOME" \ + "$phx_tools_sh_path" + printf "\n%s\n" "$sh_source_line" >>"$HOME"/.zshrc fi + fi + + if has fish; then + fish_confd="$HOME/.config/fish/conf.d" + mkdir -p "$fish_confd" - printf "${white}\n" - sleep 1.5 - maybe_install "mise" + fish_confd="$fish_confd/$(basename "$phx_tools_fish_path")" - printf "${white}\n" - sleep 1.5 - maybe_install "Erlang" + printf "fish: linking %s to %s\n" "$phx_tools_fish_path" "$fish_confd" + ln -sf "$phx_tools_fish_path" "$fish_confd" + fi +} - printf "${white}\n" - sleep 1.5 - maybe_install "Elixir" +add_env() { + # Ask for sudo password upfront + cat </dev/null & + + if [ "$os_type" = macOS ]; then + maybe_install "Xcode Command Line Tools" + maybe_install Homebrew + else + maybe_install git + fi - printf "${white}\n" - sleep 1.5 - maybe_install "Phoenix" + maybe_install "mise" + maybe_install "Erlang" + maybe_install "Elixir" + maybe_install "Phoenix" + maybe_install "PostgreSQL" - printf "${white}\n" - sleep 1.5 - maybe_install "PostgreSQL" + install_shell_scripts - printf "${white}\n" - printf "${cyan}${bold}phx.tools setup is complete!\n" - printf "${cyan}${bold}Please restart the terminal and type in the following command:\n" - printf "${cyan}${bold}mix phx.new\n" - printf "${white}\n" + printf "\n%sphx.tools setup is complete!\n\n" "$normal$cyan$bold" + printf "%sPlease restart the terminal and type in the following command:\n" \ + "$cyan$bold" + printf "%s mix phx.new\n%s\n" "$cyan$bold" "$normal" } -phx_tools=" +banner=' ██████╗░██╗░░██╗██╗░░██╗  ████████╗░█████╗░░█████╗░██╗░░░░░░██████╗  ██╔══██╗██║░░██║╚██╗██╔╝  ╚══██╔══╝██╔══██╗██╔══██╗██║░░░░░██╔════╝  ██████╔╝███████║░╚███╔╝░  ░░░██║░░░██║░░██║██║░░██║██║░░░░░╚█████╗░  ██╔═══╝░██╔══██║░██╔██╗░  ░░░██║░░░██║░░██║██║░░██║██║░░░░░░╚═══██╗  ██║░░░░░██║░░██║██╔╝╚██╗  ░░░██║░░░╚█████╔╝╚█████╔╝███████╗██████╔╝  ╚═╝░░░░░╚═╝░░╚═╝╚═╝░░╚═╝  ░░░╚═╝░░░░╚════╝░░╚════╝░╚══════╝╚═════╝░  -" -by=" + ██████╗░██╗░░░██╗ ██╔══██╗╚██╗░██╔╝ ██████╦╝░╚████╔╝░ ██╔══██╗░░╚██╔╝░░ ██████╦╝░░░██║░░░ ╚═════╝░░░░╚═╝░░░ -" -optimum=" + ░█████╗░██████╗░████████╗██╗███╗░░░███╗██╗░░░██╗███╗░░░███╗██████╗░██╗░░██╗ ██╔══██╗██╔══██╗╚══██╔══╝██║████╗░████║██║░░░██║████╗░████║██╔══██╗██║░░██║ ██║░░██║██████╔╝░░░██║░░░██║██╔████╔██║██║░░░██║██╔████╔██║██████╦╝███████║ ██║░░██║██╔═══╝░░░░██║░░░██║██║╚██╔╝██║██║░░░██║██║╚██╔╝██║██╔══██╗██╔══██║ ╚█████╔╝██║░░░░░░░░██║░░░██║██║░╚═╝░██║╚██████╔╝██║░╚═╝░██║██████╦╝██║░░██║ ░╚════╝░╚═╝░░░░░░░░╚═╝░░░╚═╝╚═╝░░░░░╚═╝░╚═════╝░╚═╝░░░░░╚═╝╚═════╝░╚═╝░░╚═╝ -" - -printf "%s\n" "$phx_tools" -printf "%s\n" "$by" -printf "%s\n" "$optimum" - -sleep 3 - -printf "\n" - -printf "${bblue}${bold}Welcome to the phx.tools shell script for ${os_type}.\n" +' -sleep 3 +printf "%s\n" "$bblue$banner" -printf "\n" +printf "\n%sWelcome to the phx.tools shell script for %s.\n" \ + "$bblue$bold" "$os_type" -printf "${bblue}${bold}The following will be installed if not available already:\n" +printf "\n%sThe following will be installed if not available already:\n" \ + "$bblue$bold" -printf "${cyan}${bold}\n" +printf "%s\n" "$cyan$bold" -if [ "$os_type" = "macOS" ]; then - printf "1) Build dependencies\n" - printf "2) Homebrew\n" - printf "3) mise\n" - printf "4) Erlang\n" - printf "5) Elixir\n" - printf "6) Phoenix\n" - printf "7) PostgreSQL\n" +if [ "$os_type" = macOS ]; then + printf "1) Build dependencies\n" + printf "2) Homebrew\n" + printf "3) mise\n" + printf "4) Erlang\n" + printf "5) Elixir\n" + printf "6) Phoenix\n" + printf "7) PostgreSQL\n" else - printf "1) Build dependencies\n" - printf "2) mise\n" - printf "3) Erlang\n" - printf "4) Elixir\n" - printf "5) Phoenix\n" - printf "6) PostgreSQL\n" + printf "1) Build dependencies\n" + printf "2) mise\n" + printf "3) Erlang\n" + printf "4) Elixir\n" + printf "5) Phoenix\n" + printf "6) PostgreSQL\n" fi -printf "\n" -printf "${white}${bold}\n" - -sleep 1 - is_yn() { - case "$1" in - [yY] | [yY][eE][sS]) - true - ;; - [nN] | [nN][oO]) - true - ;; - *) - false - ;; - esac + case "$1" in + [yY] | [yY][eE][sS]) + true + ;; + [nN] | [nN][oO]) + true + ;; + *) + false + ;; + esac } +printf "\n" + answer='' while ! is_yn "$answer"; do - printf "Do you want to continue? (y/n) " - read -r answer - printf "\n" - case "$answer" in - [yY] | [yY][eE][sS]) - printf "\n" - sleep 3 - add_env - ;; - [nN] | [nN][oO]) - printf "Thank you for your time\n" - printf "\n" - ;; - *) - printf "Please enter y or n\n" - printf "\n" - ;; - esac + printf "%sDo you want to continue? (y/n) %s" "$normal$bold" "$normal" + read -r answer + + case "$answer" in + [yY] | [yY][eE][sS]) + break + ;; + [nN] | [nN][oO]) + printf "Thank you for your time\n\n" + exit 0 + ;; + *) + printf >&2 "Please enter y or n\n\n" + continue + ;; + esac done + +printf "\n" +add_env