dotfiles/install
2024-09-02 16:25:04 +03:00

227 lines
5.3 KiB
Bash
Executable File

#!/usr/bin/env bash
set -ueo pipefail
shopt -s nullglob
declare -r TARGET_PATH="$HOME"
_detect_current_script_real_directory() {
realpath -e -- "$(dirname -- "$(readlink -e -- "${BASH_SOURCE[0]:-$0}")")"
}
declare DOTFILES_ROOT
DOTFILES_ROOT="$(_detect_current_script_real_directory)"
readonly DOTFILES_ROOT
declare -xr SUB="${DOTFILES_ROOT}/home/user"
source "${DOTFILES_ROOT}/TARGETS.sh"
_die() {
echo "${0}: ${1}" >&2
exit $2
}
_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() {
# all hooks gets SUB and SANDBOX_PATH env variables
local -r target="$1"
local -r hook_name="$2"
local hook_path="${DOTFILES_ROOT}/install-hooks/${target}/${hook_name}"
if [[ -x "$hook_path" ]]; then
echo "Executing ${hook_name} for target '${target}'"
"$hook_path"
fi
}
execute_pre_hook() {
_execute_hook_if_executable "$1" "pre-install"
}
recursive_execute_pre_hooks() {
local targetfile
for targetfile in ${TARGETS["$1"]}
do
if [[ "${targetfile::1}" = "%" ]]; then
recursive_execute_pre_hooks "${targetfile:1}"
execute_pre_hook "${targetfile:1}"
fi
done
}
execute_post_hook() {
_execute_hook_if_executable "$1" "post-install"
}
recursive_execute_post_hooks() {
local targetfile
for targetfile in ${TARGETS["$1"]}
do
if [[ "${targetfile::1}" = "%" ]]; then
recursive_execute_post_hooks "${targetfile:1}"
execute_post_hook "${targetfile:1}"
fi
done
}
install_target() {
local -r target="$1"
execute_pre_hook "$target"
recursive_execute_pre_hooks "$target"
_link_files_in_sandbox ${TARGETS["$target"]}
__install_from_sandbox
recursive_execute_post_hooks "$target"
execute_post_hook "$target"
}
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() {
local -r target="$1"
[[ " ${!TARGETS[*]} " =~ " ${target} " ]]
}
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