#!/usr/bin/env bash # script-template.sh https://gist.github.com/m-radzikowski/53e0b39e9a59a1518990e76c2bff8038 by Maciej Radzikowski # MIT License https://gist.github.com/m-radzikowski/d925ac457478db14c2146deadd0020cd # https://betterdev.blog/minimal-safe-bash-script-template/ set -Eeuo pipefail trap cleanup SIGINT SIGTERM ERR EXIT # shellcheck disable=SC2034 script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd -P) # This ensures that on Mac with ARMs / Apple Silicon the do script can do its job # and refer to things like brew without reloading or absolute paths as they are # typically not available on the $PATH on a blank/stock macOS installation. BREW_PREFIX="/opt/homebrew" if sysctl -n machdep.cpu.brand_string | grep -q 'Intel'; then BREW_PREFIX="/usr/local" fi export PATH="${BREW_PREFIX}/bin:${PATH:-}" usage() { cat <&2 -e "${1-}" } die() { local msg=$1 local code=${2-1} # default exit status 1 msg "$msg" msg "" usage exit "$code" } parse_params() { # default values of variables set from params brewfile="" rosetta="true" dotfiles="*" source_repository='https://github.com/Okeanos/dotfiles.git' target="${HOME}/Developer" theme="light" while :; do case "${1-}" in -h | --help) usage ;; -v | --verbose) set -x ;; --no-color) NO_COLOR=1 ;; -a | --dark-theme) theme="dark" ;; -n | --no-rosetta) rosetta="false" ;; -b | --brewfile) brewfile="${2-}" shift ;; -d | --dotfiles) dotfiles="${2-}" shift ;; -s | --source) source_repository="${2-}" shift ;; -t | --target) target="${2-}" shift ;; -?*) die "Unknown option: $1" ;; *) break ;; esac shift done args=("$@") # check required params and arguments [[ ${#args[@]} -eq 0 ]] && die "Missing script arguments" [[ "${args[0]}" != "show" ]] && [[ ${args[0]} != "link" ]] && [[ ${args[0]} != "unlink" ]] && die "Unknown script argument" [[ -z ${source_repository-} ]] && die "Missing parameter: --source" return 0 } parse_params "$@" setup_colors # script logic here repository="${target}/dotfiles" if [[ "${args[0]}" == "show" ]]; then msg "Available dotfiles:" if [[ ! -d "${repository}" ]]; then die "The location '${repository}' doesn't exist. Cannot list available dotfiles. Please checkout the repository first." fi find "${repository}/stow" -type d -printf '%P\n' msg "" msg "Use the leading directory name as input for the -d (--dotfiles) parameter." elif [[ "${args[0]}" == "link" ]]; then msg "Installing prerequisites & setting up dotfiles" msg "Will use the following inputs:" msg "- Target location: '${target}'" msg "- Dotfiles repository location: '${repository}'" msg "- Dotfiles to install: '${dotfiles}'" msg "- Installing Rosetta: '${rosetta}'" msg "- Theme is Selenized (https://github.com/jan-warchol/selenized) in: '${theme}'" msg "" read -rp "Do you want to continue (y/n)? " -n 1 if [[ ! ${REPLY} =~ ^[Yy]$ ]]; then die "Installation canceled" fi msg "" msg "Installing prerequisites" if [[ "${rosetta}" == "true" ]] && ! sysctl -n machdep.cpu.brand_string | grep -q 'Intel'; then msg "Installing Rosetta" sudo softwareupdate --install-rosetta --agree-to-license else msg "${YELLOW}Skip installing Rosetta on Intel machines${NOFORMAT}" fi if ! which brew >/dev/null; then msg "Installing Homebrew" /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" fi msg "" msg "Setting up dotfiles repository in ${repository}" if [[ ! -d "${target}" ]]; then msg "Creating target location '${target}' for dotfiles" mkdir -p "${target}" fi if [[ -d "${repository}" ]]; then if git -C "${repository}" rev-parse; then msg "${YELLOW}Assuming the Git repository '${repository}' belongs to this script and will use it as a 'dotfiles' source.${NOFORMAT}" else die "${RED}The target location '${repository}' already exists but is ${NOFORMAT}not${RED} a Git repository. Please remove it and try again.${NOFORMAT}" fi else msg "Cloning dotfiles to: ${repository}" git clone --quiet "${source_repository}" "${repository}" fi msg "" msg "Installing Brewfile contents" if [[ -n "${brewfile}" ]] && [[ -f "${brewfile}" ]]; then msg "Using custom Brewfile located at: ${brewfile}" else msg "Using default Brewfile located at: ${repository}/Brewfile" brewfile="${repository}/Brewfile" fi read -rp "Do you want to review the Brewfile now (y/n)? " -n 1 msg "" if [[ ${REPLY} =~ ^[Yy]$ ]]; then msg "Brewfile contents:" msg "---" echo "$(<"${brewfile}")" msg "---" fi msg "${RED}The dotfiles require a number of tools from the Brewfile to work. You can skip installing them here if you are sure everything is already set up as expected.${NOFORMAT}" read -rp "Do you want to install the Brewfile contents (y/n)? " -n 1 msg "" if [[ ${REPLY} =~ ^[Yy]$ ]]; then # Upgrade any already-installed formulae. brew upgrade # Install everything inside Brewfile brew bundle install --file "${brewfile}" fi msg "" if [[ -f "${BREW_PREFIX}/bin/bash" ]]; then if ! grep --fixed-strings --quiet "${BREW_PREFIX}/bin/bash" /etc/shells; then read -rp "Do you want to make the modern Bash ('${BREW_PREFIX}/bin/bash') known to the system (y/n)? " -n 1 msg "" if [[ ${REPLY} =~ ^[Yy]$ ]]; then echo "${BREW_PREFIX}/bin/bash" | sudo tee -a /etc/shells fi fi current_shell=$(finger "${USER}" | grep -F "Shell:" | cut -d':' -f3 | xargs) if [[ "${current_shell}" != "${BREW_PREFIX}/bin/bash" ]]; then read -rp "Do you want to make the modern Bash ('${BREW_PREFIX}/bin/bash') your user shell (y/n)? " -n 1 msg "" if [[ ${REPLY} =~ ^[Yy]$ ]]; then chsh -s "${BREW_PREFIX}/bin/bash" fi fi fi msg "" msg "Creating static locations to which to link the dotfiles. This prevents accidentally syncing sensitives files later on with the repository." msg "Creating XDG (https://specifications.freedesktop.org/basedir-spec/latest/) locations:" mkdir -v -p "${HOME}/.cache/bash" mkdir -v -p "${HOME}/.cache/ruby/gem" mkdir -v -p "${HOME}/.cache/vim/swap" mkdir -v -p "${HOME}/.config/"{bash,git} mkdir -v -p "${HOME}/.config/vim/autoload" mkdir -v -p "${HOME}/.local/"{share,state} mkdir -v -p "${HOME}/.local/share/node" mkdir -v -p "${HOME}/.local/share/vim/"{bundle,plugged} mkdir -v -p "${HOME}/.local/state/vim/"{backup,undo} msg "Creating other locations" mkdir -v -p "${HOME}/."{gradle,m2} mkdir -v -p "${HOME}/.ssh/config.d" mkdir -v -p "${HOME}/Library/Application Support/Code/User" if [[ "${theme}" == "dark" ]]; then msg "${YELLOW}Converting dotfiles to Selenized Dark${NOFORMAT}" if command -v gsed >/dev/null; then gsed -i "s/Selenized-Light/Selenized-Dark/g" "${repository}/stow/shell/dot-config/bat/config" gsed -i "s/background=light/background=dark/g" "${repository}/stow/shell/dot-config/vim/vimrc" gsed -i "s/Default Light+/Default Dark+/g" "${repository}/stow/vscode/settings.json" else sed -i '' "s/Selenized-Light/Selenized-Dark/g" "${repository}/stow/shell/dot-config/bat/config" sed -i '' "s/background=light/background=dark/g" "${repository}/stow/shell/dot-config/vim/vimrc" sed -i '' "s/Default Light+/Default Dark+/g" "${repository}/stow/vscode/settings.json" fi fi msg "" msg "Linking dotfiles: ${dotfiles}" msg "This may overwrite existing files in your home directory. Only continue if you are sure you want this to happen." read -rp "Do you want to link the named dotfiles (y/n)? " -n 1 msg "" if [[ ! ${REPLY} =~ ^[Yy]$ ]]; then die "${RED}Aborting. Please specify a subset of dotfiles if you do not want everything. The 'shell' parts are effectively required for the dotfiles to work/make sense.${NOFORMAT}" fi skip_tools=("vscode") for tmp in "${repository}/stow"/*; do toolname=$(basename "${tmp}") if [[ "${dotfiles}" != "*" ]] && [[ "${dotfiles}" != *"${toolname}"* ]]; then continue fi [[ ! "${skip_tools[*]}" == *"${toolname}"* ]] && stow --dotfiles --dir "${repository}/stow" "${toolname}" --target "${HOME}" done if [[ "${dotfiles}" == "*" ]] || [[ "${dotfiles}" == *"vscode"* ]]; then stow --dotfiles --dir "${repository}/stow" "vscode" --target "${HOME}/Library/Application Support/Code/User" fi msg "" msg "Loading new bash profile with updated \$PATH etc." source "${HOME}/.bash_profile" if command -v bat >/dev/null; then msg "Rebuild bat cache so the theme works as expected" bat cache --build fi msg "" if [[ -f "${HOME}/.config/git/user" ]]; then msg "The Git user config '${HOME}/.config/git/user' already exists. Skipping setup." else msg "Creating Git user config" username="" email="" signing_enabled="false" signing_format="openpgp" sign_selection="" signing_key="" read -rp "Enter your Git Username: " username while [[ -z "${username}" ]]; do read -rp "Enter your Git Username (must not be empty): " username done read -rp "Enter your Git E-Mail address: " email while [[ -z "${email}" ]] || [[ "${email}" != *"@"* ]]; do read -rp "Enter your Git E-Mail address (must not be empty & contain an @): " email done msg "Will set up Git commit signing, check the following links for more information:" msg "- Git User Signing Key documentation: https://git-scm.com/docs/git-config#Documentation/git-config.txt-usersigningKey" msg "- Git Signing Formats: https://git-scm.com/docs/git-config#Documentation/git-config.txt-gpgformat" msg "" msg "The Git user config '${HOME}/.config/git/user' can be updated later on if you want to set up commit signing later on." msg "" select signing_type in "GPG (OpenPGP)" "SSH" "Set up later"; do [[ -n "${signing_type}" ]] || { echo "Please select how you want to sign your Git commits." >&2 continue } sign_selection="${signing_type}" break done if [[ "${sign_selection}" != "Set up later" ]]; then while [[ -z "${signing_key}" ]]; do read -rp "Enter your GPG or SSH Signing Key ID: " signing_key done if [[ "${sign_selection}" == "SSH" ]]; then touch "${HOME}/.ssh/allowed_signers" signing_format="ssh" fi fi printf '%s\n' \ "[user]" \ "" \ " name = ${username}" \ " email = ${email}" \ " signingKey = ${signing_key}" \ "" \ "[gpg]" \ "" \ " format = ${signing_format}" \ "" \ "[gpg \"ssh\"]" \ "" \ " allowedSignersFile = ~/.ssh/allowed_signers" \ "" \ "[commit]" \ "" \ " gpgsign = ${signing_enabled}" \ >"${HOME}/.config/git/user" fi msg "" msg "${GREEN}Done setting things up. You probably want to restart your shell now (type 'exit' or Ctrl+D).${NOFORMAT}" elif [[ "${args[0]}" == "unlink" ]]; then msg "Unlinking dotfiles: ${dotfiles}" if [[ ! -d "${repository}" ]]; then die "The location '${repository}' doesn't exist. Cannot unlink dotfiles." fi read -rp "This will unlink the dotfiles from your home directory. No files will actually be deleted. Are you sure? (y/n) " -n 1 if [[ ! ${REPLY} =~ ^[Yy]$ ]]; then die "${RED}Aborting. Please specify a subset of dotfiles if you do not want to unlink everything.${NOFORMAT}" fi for tmp in "stow"/*; do toolname=$(basename "${tmp}") if [[ "${dotfiles}" != "*" ]] && [[ "${dotfiles}" != *"${toolname}"* ]]; then continue fi if [[ "${toolname}" != "vscode" ]]; then msg "Unlinking '${toolname}' from '${HOME}'" stow --dotfiles --delete --dir "stow" "${toolname}" --target "${HOME}" fi done if [[ "${dotfiles}" == "*" ]] || [[ "${dotfiles}" == *"vscode"* ]]; then msg "Unlinking 'vscode' from '${HOME}/Library/Application Support/Code/User'" stow --dotfiles --dir "stow" "vscode" --target "${HOME}/Library/Application Support/Code/User" fi msg "" msg "${GREEN}Done unlinking the dotfiles. You probably want to restart your shell now (type 'exit' or Ctrl+D).${NOFORMAT}" fi