Files
dotfiles/install.sh
T
2026-08-12 15:57:01 +00:00

156 lines
4.2 KiB
Bash
Executable File

#!/usr/bin/env bash
set -Eeuo pipefail
readonly APP_NAME='dotfiles'
readonly REPO_URI="${REPO_URI:-https://github.com/Donaldttt/dotfiles.git}"
readonly REPO_BRANCH="${REPO_BRANCH:-main}"
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" 2>/dev/null && pwd -P || true)"
if [[ -n "$SCRIPT_DIR" && -e "$SCRIPT_DIR/.git" ]]; then
APP_PATH="${APP_PATH:-$SCRIPT_DIR}"
else
APP_PATH="${APP_PATH:-$HOME/.$APP_NAME}"
fi
readonly APP_PATH
readonly VIM_PATH="${VIM_PATH:-$HOME/.vim}"
readonly MANAGED_START='# >>> dotfiles >>>'
readonly MANAGED_END='# <<< dotfiles <<<'
info() { printf '\033[34m[•]\033[0m %s\n' "$*"; }
success() { printf '\033[32m[✔]\033[0m %s\n' "$*"; }
warn() { printf '\033[33m[!]\033[0m %s\n' "$*" >&2; }
die() { printf '\033[31m[✘]\033[0m %s\n' "$*" >&2; exit 1; }
usage() {
cat <<'EOF'
Usage: ./install.sh [all|vim|shell|tmux]...
With no arguments, installs every component. Components can be combined, for
example: ./install.sh vim tmux
EOF
}
require() {
command -v "$1" >/dev/null 2>&1 || die "Required program not found: $1"
}
ensure_repo() {
require git
if [[ -d "$APP_PATH/.git" ]]; then
return
fi
[[ ! -e "$APP_PATH" ]] || die "$APP_PATH exists but is not a Git repository."
info "Cloning $REPO_URI into $APP_PATH"
git clone --branch "$REPO_BRANCH" --depth 1 "$REPO_URI" "$APP_PATH"
}
backup_path() {
local target=$1
if [[ -e "$target" || -L "$target" ]]; then
local backup="${target}.backup.$(date +%Y%m%d_%H%M%S)" suffix=0
while [[ -e "$backup" || -L "$backup" ]]; do
((suffix += 1))
backup="${target}.backup.$(date +%Y%m%d_%H%M%S).$suffix"
done
mv -- "$target" "$backup"
warn "Backed up $target to $backup"
fi
}
link_config() {
local source=$1 target=$2 current=''
if [[ -L "$target" ]]; then
current=$(readlink "$target" || true)
[[ "$current" == "$source" ]] && return
fi
backup_path "$target"
ln -s "$source" "$target"
success "Linked $target -> $source"
}
remove_managed_block() {
local file=$1 temp
[[ -f "$file" ]] || return
temp=$(mktemp)
awk -v start="$MANAGED_START" -v end="$MANAGED_END" '
$0 == start { managed = 1; next }
$0 == end { managed = 0; next }
!managed { print }
' "$file" > "$temp"
cat "$temp" > "$file"
rm -f "$temp"
}
install_vim() {
require vim
require curl
if ! vim --clean -Nu NONE -n -es \
'+if v:version < 900 | cquit | endif' '+qall'; then
die 'Vim 9 or newer is required.'
fi
link_config "$APP_PATH/.vimrc" "$HOME/.vimrc"
mkdir -p "$VIM_PATH/autoload"
local temp
temp=$(mktemp)
curl --fail --location --silent --show-error \
https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim \
--output "$temp"
mv "$temp" "$VIM_PATH/autoload/plug.vim"
vim -Nu "$APP_PATH/.vimrc" -n -es \
'+set nomore' '+PlugInstall --sync' '+qall'
success 'Installed Vim configuration and plugins.'
}
install_shell() {
local bashrc="$HOME/.bashrc" quoted_source
touch "$bashrc"
remove_managed_block "$bashrc"
printf -v quoted_source '%q' "$APP_PATH/bash_tools.sh"
{
printf '\n%s\n' "$MANAGED_START"
printf '[ -r %s ] && source %s\n' "$quoted_source" "$quoted_source"
printf 'command -v zoxide >/dev/null 2>&1 && eval "$(zoxide init bash)"\n'
printf '%s\n' "$MANAGED_END"
} >> "$bashrc"
success "Installed managed Bash block in $bashrc"
}
install_tmux() {
require tmux
link_config "$APP_PATH/.tmux.conf" "$HOME/.tmux.conf"
local tpm="$HOME/.tmux/plugins/tpm"
if [[ ! -d "$tpm/.git" ]]; then
[[ ! -e "$tpm" ]] || die "$tpm exists but is not a Git repository."
mkdir -p "$(dirname "$tpm")"
git clone --depth 1 https://github.com/tmux-plugins/tpm "$tpm"
fi
success 'Installed tmux configuration and TPM.'
}
main() {
case "${1:-}" in
-h|--help) usage; return ;;
esac
ensure_repo
local components=("$@") component
((${#components[@]})) || components=(all)
for component in "${components[@]}"; do
case "$component" in
all)
install_vim
install_shell
install_tmux
;;
vim) install_vim ;;
shell) install_shell ;;
tmux) install_tmux ;;
*) usage; die "Unknown component: $component" ;;
esac
done
}
main "$@"