dotfiles/install
2024-01-13 00:57:42 +03:00

213 lines
5.2 KiB
Bash
Executable File

#!/bin/bash
set -ueo pipefail
shopt -s nullglob
declare -r TARGET_PATH="$HOME"
declare -r DOTFILES_ROOT="$PWD"
declare -r SUB="$DOTFILES_ROOT/home/user"
export SUB
declare -r -A TARGETS=(
["bash"]=".config/bash .bashrc .profile"
["zsh"]="%bash .config/zsh .zshenv .zprofile .zlogout .inputrc"
["tmux"]=".tmux.conf"
["alacritty"]=".config/alacritty"
["nvim"]=".config/nvim .local/bin/vim_askpass_helper .local/bin/vim_askpass_helper_python"
["ssh"]=""
["less"]=".lesskey"
["git"]=".config/git"
["ranger"]=".config/ranger"
["gpg"]=""
["i3"]=".xinitrc .xprofile .Xresources .config/i3 .config/i3status .local/bin/i3status_wrapper .config/rofi .config/picom .local/bin/slm .local/bin/slm_rofi.sh .local/bin/power_rofi.sh .local/bin/wifi .local/bin/bluetooth .local/bin/i3_switch_workspace.sh"
["bat"]=".config/bat"
["font"]=""
["termux"]=""
["arch"]=""
["psql"]=".psqlrc"
["docker"]=".docker/cli-plugins"
["ipython"]=".ipython/profile_default/ipython_config.py"
)
_die() {
echo "$0: $1" >&2
exit $2
}
test "$0" = "./install" || _die "Error: Please, run this file from root of dotfiles, like this './install.sh TARGET" 1
_link_files_in_sandbox() {
local targetfile
for targetfile in "$@"
do
echo "installing: $targetfile"
if [ "${targetfile::1}" = "%" ]; then
_link_files_in_sandbox ${TARGETS["${targetfile:1}"]}
else
if [ ! "$(dirname "$targetfile")" = "." ]; then
mkdir -p "$SANDBOX_PATH/$(dirname "$targetfile")"
fi
ln -sT "$SUB/$targetfile" "$SANDBOX_PATH/$targetfile"
fi
done
}
_compare_sandbox_to_home() {
local comparisons
comparisons="$(diff -rq "$SANDBOX_PATH" "$TARGET_PATH")"
echo "$comparisons" | grep -vE "^Only in .+" || true
}
_merge_sandbox_to_home() {
cp -RTnP "$SANDBOX_PATH" "$TARGET_PATH" || true
}
__install_from_sandbox() {
local comparisons
comparisons="$(_compare_sandbox_to_home)"
if [ -n "$comparisons" ]; then
echo "$comparisons" >&2
_die "Found conflicting files. Exiting" 1
fi
echo "Merging to home..."
_merge_sandbox_to_home
echo "Successfully installed"
}
_execute_hook_if_executable() {
local hook_path="$DOTFILES_ROOT/install-hooks/$1/$2"
if [ -x "$hook_path" ]; then
echo "Executing $2 for target '$1'"
"$hook_path"
fi
}
execute_pre_hook() {
_execute_hook_if_executable "$1" "pre-install"
}
execute_post_hook() {
_execute_hook_if_executable "$1" "post-install"
}
install_target() {
execute_pre_hook "$1"
_link_files_in_sandbox ${TARGETS["$1"]}
__install_from_sandbox
execute_post_hook "$1"
}
is_target_installed() {
local not_fully_installed=false
local targetfile
for targetfile in ${TARGETS["$1"]}
do
if [ "${targetfile::1}" = "%" ]; then
is_target_installed "${targetfile:1}" || not_fully_installed=true
else
if [ ! -e "$TARGET_PATH/$targetfile" ]; then
echo "$targetfile not linked"
not_fully_installed=true
fi
fi
done
if $not_fully_installed; then
echo "Target '$1' not fully installed"
echo
return 1
fi
return 0
}
find_targets_that_depend_on() {
local target
for target in "${!TARGETS[@]}"
do
if [[ " ${TARGETS["$target"]} " =~ " %$1 " ]]; then
echo "$target"
fi
done
}
die_if_installed_targets_depend_on() {
for reverse_dependecy in $(find_targets_that_depend_on "$1")
do
if is_target_installed "$reverse_dependecy" >/dev/null; then
_die "target '$reverse_dependecy' is depends on installed target '$1'. Exiting..." 1
fi
done
}
cmd_unlink() {
local target targetfile
for target in "$@"
do
die_if_installed_targets_depend_on "$target"
for targetfile in ${TARGETS["$target"]}
do
if [ "${targetfile::1}" = "%" ]; then
continue
fi
if [ -e "$TARGET_PATH/$targetfile" ]; then
unlink "$TARGET_PATH/$targetfile"
fi
done
done
}
cmd_no_target() {
_die "TARGET not exists" 1
}
cmd_list() {
echo "${!TARGETS[@]}"
}
target_exists() {
[[ " ${!TARGETS[*]} " =~ " $1 " ]]
}
cmd_install() {
local target
for target in "$@"
do
if target_exists "$target"; then
SANDBOX_PATH="$(mktemp -td "${USER:-user}.dotfiles_XXXXXXX")"
export SANDBOX_PATH
install_target "$target"
rm -rf "$SANDBOX_PATH"
else
cmd_no_target
fi
done
}
cmd_help() {
echo "Dotfiles installation script:
Usage: ./install TARGET...
Usage: ./install unlink TARGET...
Usage: ./install check TARGET
Usage: ./install list"
}
unset executed_command
readonly executed_command="$1"
case "$executed_command" in
unlink) shift; cmd_unlink "$@" ;;
check) shift; is_target_installed "$@" ;;
list) shift; cmd_list "$@" ;;
help) shift; cmd_help "$@" ;;
*) shift; cmd_install "$executed_command" "$@" ;;
esac
exit 0