#!/bin/sh # # Installs this dotfiles repo by: # - cloning Oh My Zsh into $ZSH # - symlinking the managed config files from this repository # - installing the tmux plugin manager and bundled tmux plugins # - cloning the powerlevel10k theme # # Run it from a checked out copy of this repository: # sh .config/dotfiles/install.sh # # You can tweak the install behavior by setting variables when running the script: # ZSH=~/.zsh sh .config/dotfiles/install.sh # # Respects the following environment variables: # ZSH - path to the Oh My Zsh installation (default: $HOME/.oh-my-zsh) # REPO - Oh My Zsh GitHub repo name (default: robbyrussell/oh-my-zsh) # REMOTE - full remote URL of the Oh My Zsh git repo # BRANCH - Oh My Zsh branch to check out immediately after install # # Other options: # CHSH - 'no' means the installer will not change the default shell (default: yes) # RUNZSH - 'no' means the installer will not run zsh after the install (default: yes) # # You can also pass some arguments to the install script to set some of these options: # --skip-chsh: has the same behavior as setting CHSH to 'no' # --unattended: sets both CHSH and RUNZSH to 'no' # For example: # sh .config/dotfiles/install.sh --unattended # set -e # Default settings ZSH=${ZSH:-~/.oh-my-zsh} REPO=${REPO:-ohmyzsh/ohmyzsh} REMOTE=${REMOTE:-https://github.com/${REPO}.git} BRANCH=${BRANCH:-master} # Other options CHSH=${CHSH:-yes} RUNZSH=${RUNZSH:-yes} DOTFILES_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) TMUX_PLUGIN_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/tmux/plugins" TMUX_RESURRECT_DIR="${XDG_STATE_HOME:-$HOME/.local/state}/tmux/resurrect" P10K_REMOTE=${P10K_REMOTE:-https://github.com/romkatv/powerlevel10k.git} command_exists() { command -v "$@" >/dev/null 2>&1 } require_command() { command_name=$1 if ! command_exists "$command_name"; then error "$command_name is required but not installed. Install it first via your package manager." exit 1 fi } error() { echo ${RED}"Error: $@"${RESET} >&2 } skip() { echo "${YELLOW}Skipping $1${RESET}" } setup_color() { # Only use colors if connected to a terminal if [ -t 1 ]; then RED=$(printf '\033[31m') GREEN=$(printf '\033[32m') YELLOW=$(printf '\033[33m') BLUE=$(printf '\033[34m') BOLD=$(printf '\033[1m') RESET=$(printf '\033[m') else RED="" GREEN="" YELLOW="" BLUE="" BOLD="" RESET="" fi } apply_dotfiles() { echo "${BLUE}Applying dotfiles...${RESET}" require_command lsd require_command tmux require_command nvim require_command rg remove_legacy_dotfile "$HOME/.vimrc" link_dotfile "$DOTFILES_DIR/.zshrc" "$HOME/.zshrc" link_dotfile "$DOTFILES_DIR/.zprofile" "$HOME/.zprofile" link_dotfile "$DOTFILES_DIR/.gitconfig" "$HOME/.gitconfig" link_dotfile "$DOTFILES_DIR/.p10k.zsh" "$HOME/.p10k.zsh" mkdir -p "$HOME/.config" link_dotfile "$DOTFILES_DIR/kitty" "$HOME/.config/kitty" link_dotfile "$DOTFILES_DIR/nvim" "$HOME/.config/nvim" link_dotfile "$DOTFILES_DIR/tmux" "$HOME/.config/tmux" if [ "$(uname -s)" = "Darwin" ]; then install_karabiner_config fi install_kitty_icon install_tmux_plugins install_powerlevel10k echo } remove_legacy_dotfile() { target_path=$1 if [ -L "$target_path" ] || [ -e "$target_path" ]; then echo "${YELLOW}Removing legacy $target_path${RESET}" rm -rf "$target_path" fi } replace_dir() { source_path=$1 target_path=$2 if [ -e "$target_path" ] || [ -L "$target_path" ]; then rm -rf "$target_path" fi mv "$source_path" "$target_path" } link_dotfile() { source_path=$1 target_path=$2 if [ -L "$target_path" ]; then current_target=$(readlink "$target_path") if [ "$current_target" = "$source_path" ]; then echo "${GREEN}$target_path already linked.${RESET}" return fi rm -f "$target_path" elif [ -e "$target_path" ]; then echo "${YELLOW}Removing existing $target_path${RESET}" rm -rf "$target_path" fi ln -s "$source_path" "$target_path" echo "${GREEN}Linked $target_path -> $source_path${RESET}" } install_karabiner_config() { mkdir -p "$HOME/.config/karabiner" link_dotfile "$DOTFILES_DIR/karabiner/karabiner.json" "$HOME/.config/karabiner/karabiner.json" link_dotfile "$DOTFILES_DIR/karabiner/assets" "$HOME/.config/karabiner/assets" } install_kitty_icon() { icon_path="$DOTFILES_DIR/kitty/kitty.app.png" if [ "$(uname -s)" != "Darwin" ]; then return fi if ! command_exists kitty; then skip "kitty icon setup: kitty is not installed." return fi if [ ! -d /Applications/kitty.app ]; then skip "kitty icon setup: /Applications/kitty.app was not found." return fi if [ ! -f "$icon_path" ]; then skip "kitty icon setup: $icon_path was not found." return fi echo "${BLUE}Applying custom kitty icon...${RESET}" kitty +runpy 'from kitty.fast_data_types import cocoa_set_app_icon; import sys; cocoa_set_app_icon(*sys.argv[1:]); print("OK")' \ "$icon_path" /Applications/kitty.app echo } install_tmux_plugins() { echo "${BLUE}Installing tmux plugins...${RESET}" rm -rf "$TMUX_PLUGIN_DIR" mkdir -p "$TMUX_PLUGIN_DIR" "$TMUX_RESURRECT_DIR" git clone --depth=1 https://github.com/tmux-plugins/tpm \ "$TMUX_PLUGIN_DIR/tpm" git clone --depth=1 https://github.com/tmux-plugins/tmux-resurrect \ "$TMUX_PLUGIN_DIR/tmux-resurrect" git clone --depth=1 https://github.com/tmux-plugins/tmux-continuum \ "$TMUX_PLUGIN_DIR/tmux-continuum" echo } setup_ohmyzsh() { # Prevent the cloned repository from having insecure permissions. Failing to do # so causes compinit() calls to fail with "command not found: compdef" errors # for users with insecure umasks (e.g., "002", allowing group writability). Note # that this will be ignored under Cygwin by default, as Windows ACLs take # precedence over umasks except for filesystems mounted with option "noacl". umask g-w,o-w echo "${BLUE}Cloning Oh My Zsh...${RESET}" command_exists git || { error "git is not installed" exit 1 } if [ "$OSTYPE" = cygwin ] && git --version | grep -q msysgit; then error "Windows/MSYS Git is not supported on Cygwin" error "Make sure the Cygwin git package is installed and is first on the \$PATH" exit 1 fi zsh_parent=$(dirname "$ZSH") mkdir -p "$zsh_parent" zsh_stage=$(mktemp -d "${zsh_parent%/}/.oh-my-zsh.XXXXXX") git clone -c core.eol=lf -c core.autocrlf=false \ -c fsck.zeroPaddedFilemode=ignore \ -c fetch.fsck.zeroPaddedFilemode=ignore \ -c receive.fsck.zeroPaddedFilemode=ignore \ --depth=1 --branch "$BRANCH" "$REMOTE" "$zsh_stage" || { rm -rf "$zsh_stage" error "git clone of oh-my-zsh repo failed" exit 1 } replace_dir "$zsh_stage" "$ZSH" echo } install_powerlevel10k() { theme_dir="${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/themes/powerlevel10k" theme_parent=$(dirname "$theme_dir") mkdir -p "$theme_parent" theme_stage=$(mktemp -d "${theme_parent%/}/.powerlevel10k.XXXXXX") echo "${BLUE}Installing powerlevel10k...${RESET}" git clone --depth=1 "$P10K_REMOTE" "$theme_stage" || { rm -rf "$theme_stage" error "git clone of powerlevel10k repo failed" exit 1 } replace_dir "$theme_stage" "$theme_dir" echo } setup_shell() { # Skip setup if the user wants or stdin is closed (not running interactively). if [ $CHSH = no ]; then return fi # If this user's login shell is already "zsh", do not attempt to switch. if [ "$(basename "$SHELL")" = "zsh" ]; then return fi # If this platform doesn't provide a "chsh" command, bail out. if ! command_exists chsh; then cat <<-EOF I can't change your shell automatically because this system does not have chsh. ${BLUE}Please manually change your default shell to zsh${RESET} EOF return fi echo "${BLUE}Time to change your default shell to zsh:${RESET}" # Prompt for user choice on changing the default login shell printf "${YELLOW}Do you want to change your default shell to zsh? [Y/n]${RESET} " read opt case $opt in y*|Y*|"") echo "Changing the shell..." ;; n*|N*) echo "Shell change skipped."; return ;; *) echo "Invalid choice. Shell change skipped."; return ;; esac # Check if we're running on Termux case "$PREFIX" in *com.termux*) termux=true; zsh=zsh ;; *) termux=false ;; esac if [ "$termux" != true ]; then # Test for the right location of the "shells" file if [ -f /etc/shells ]; then shells_file=/etc/shells elif [ -f /usr/share/defaults/etc/shells ]; then # Solus OS shells_file=/usr/share/defaults/etc/shells else error "could not find /etc/shells file. Change your default shell manually." return fi # Get the path to the right zsh binary # 1. Use the most preceding one based on $PATH, then check that it's in the shells file # 2. If that fails, get a zsh path from the shells file, then check it actually exists if ! zsh=$(which zsh) || ! grep -qx "$zsh" "$shells_file"; then if ! zsh=$(grep '^/.*/zsh$' "$shells_file" | tail -1) || [ ! -f "$zsh" ]; then error "no zsh binary found or not present in '$shells_file'" error "change your default shell manually." return fi fi fi # We're going to change the default shell, so back up the current one if [ -n "$SHELL" ]; then echo $SHELL > ~/.shell.pre-oh-my-zsh else grep "^$USER:" /etc/passwd | awk -F: '{print $7}' > ~/.shell.pre-oh-my-zsh fi # Actually change the default shell to zsh if ! chsh -s "$zsh"; then error "chsh command unsuccessful. Change your default shell manually." else export SHELL="$zsh" echo "${GREEN}Shell successfully changed to '$zsh'.${RESET}" fi echo } main() { # Run as unattended if stdin is closed if [ ! -t 0 ]; then RUNZSH=no CHSH=no fi # Parse arguments while [ $# -gt 0 ]; do case $1 in --unattended) RUNZSH=no; CHSH=no ;; --skip-chsh) CHSH=no ;; esac shift done setup_color if ! command_exists zsh; then echo "${YELLOW}Zsh is not installed.${RESET} Please install zsh first." exit 1 fi setup_ohmyzsh setup_shell apply_dotfiles printf "$GREEN" cat <<-'EOF' __ __ ____ / /_ ____ ___ __ __ ____ _____/ /_ / __ \/ __ \ / __ `__ \/ / / / /_ / / ___/ __ \ / /_/ / / / / / / / / / / /_/ / / /_(__ ) / / / \____/_/ /_/ /_/ /_/ /_/\__, / /___/____/_/ /_/ /____/ ....is now installed! Your dotfiles have been installed to your home directory. EOF printf "$RESET" if [ $RUNZSH = no ]; then echo "${YELLOW}Run zsh to try it out.${RESET}" exit fi exec zsh -l } main "$@"