rewrite install script to posix sh compatible

This commit is contained in:
thek4n 2024-11-03 23:21:52 +03:00
parent 0fc4f945bc
commit 1706e7bda9
2 changed files with 156 additions and 127 deletions

View File

@ -1,21 +1,20 @@
declare -r -A TARGETS=( readonly TARGETS="\
["colors"]=".config/terminal-colors.d" colors:.config/terminal-colors.d
["tmux"]=".config/tmux .tmux .config/systemd/user/tmux.service .local/bin/tmux_start_session.sh .local/bin/tmux_list_sessions.sh .local/bin/tmux_attach_session.sh .local/bin/tmux_kill_sessions.sh" tmux:.config/tmux .tmux .config/systemd/user/tmux.service .local/bin/tmux_start_session.sh .local/bin/tmux_list_sessions.sh .local/bin/tmux_attach_session.sh .local/bin/tmux_kill_sessions.sh
["zsh"]=".config/zsh .zshenv .inputrc %colors" zsh:.config/zsh .zshenv .inputrc %colors
["alacritty"]=".config/alacritty" alacritty:.config/alacritty
["nvim"]=".config/nvim .editorconfig .inputrc .editrc .local/bin/vim_askpass_helper" nvim:.config/nvim .editorconfig .inputrc .editrc .local/bin/vim_askpass_helper
["ssh"]="" ssh:
["less"]=".lesskey" less:.lesskey
["git"]=".config/git" git:.config/git
["ranger"]=".config/ranger" ranger:.config/ranger
["gpg"]="" 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" 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" bat:.config/bat
["font"]="" font:
["termux"]=".termux" termux:.termux
["arch"]="" arch:
["psql"]=".psqlrc" psql:.psqlrc
["docker"]=".docker/cli-plugins" docker:.docker/cli-plugins
["ipython"]=".ipython/profile_default/ipython_config.py" ipython:.ipython/profile_default/ipython_config.py
["gdb"]=".config/gdb" gdb:.config/gdb"
)

242
install
View File

@ -1,132 +1,163 @@
#!/usr/bin/env bash #!/bin/sh
set -ueo pipefail set -ue
shopt -s nullglob
declare -r TARGET_PATH="$HOME" readonly TARGET_PATH="$HOME"
_detect_current_script_real_directory() { _detect_current_script_real_directory() {
realpath -e -- "$(dirname -- "$(readlink -e -- "${BASH_SOURCE[0]:-$0}")")" realpath -e -- "$(dirname -- "$(readlink -e -- "${0}")")"
} }
declare DOTFILES_ROOT
DOTFILES_ROOT="$(_detect_current_script_real_directory)" DOTFILES_ROOT="$(_detect_current_script_real_directory)"
readonly DOTFILES_ROOT readonly DOTFILES_ROOT
declare -xr SUB="${DOTFILES_ROOT}/home/user" readonly SUB="${DOTFILES_ROOT}/home/user"
export SUB
source "${DOTFILES_ROOT}/TARGETS.sh" . "${DOTFILES_ROOT}/TARGETS.sh"
_die() { _die() {
echo "${0}: ${1}" >&2 echo "$(basename "${0}"): ${1}" >&2
exit $2 exit "${2}"
} }
_link_files_in_sandbox() { string_get_first_char() (
local targetfile printf %.1s "${1}"
)
string_exclude_first_char() (
echo "${1}" | tail -c+2
)
map_get_value() (
map="${1}"
key="${2}"
echo "${map}" | grep "${key}:" | cut -d ':' -f2
)
map_get_keys() (
map="${1}"
echo "${map}" | cut -d ':' -f1
)
map_key_exists() (
map="${1}"
key="${2}"
map_get_keys "${map}" | grep "${2}" 1>/dev/null
)
_link_files_in_sandbox() (
for targetfile in "$@" for targetfile in "$@"
do do
echo "installing: ${targetfile}" echo "installing: ${targetfile}"
if [[ "${targetfile::1}" = "%" ]]; then if [ "$(string_get_first_char "${targetfile}")" = "%" ]; then
_link_files_in_sandbox ${TARGETS["${targetfile:1}"]} files="$(map_get_value "${TARGETS}" "$(string_exclude_first_char "${targetfile}")")"
_link_files_in_sandbox ${files}
else else
if [[ ! "$(dirname "$targetfile")" = "." ]]; then if [ ! "$(dirname "$targetfile")" = "." ]; then
mkdir -p "${SANDBOX_PATH}/$(dirname "$targetfile")" mkdir -p "${SANDBOX_PATH}/$(dirname "$targetfile")"
fi fi
ln -sT "${SUB}/${targetfile}" "${SANDBOX_PATH}/${targetfile}" ln -sT "${SUB}/${targetfile}" "${SANDBOX_PATH}/${targetfile}"
fi fi
done done
} )
_compare_sandbox_to_home() { _compare_sandbox_to_home() (
local comparisons comparisons="$(diff -rq "$SANDBOX_PATH" "$TARGET_PATH")" || true
comparisons="$(diff -rq "$SANDBOX_PATH" "$TARGET_PATH")" echo "${comparisons}" | grep -vE "^Only in .+" || true
echo "$comparisons" | grep -vE "^Only in .+" || true )
}
_merge_sandbox_to_home() { _merge_sandbox_to_home() (
cp -RTnP "$SANDBOX_PATH" "$TARGET_PATH" || true cp -RTnP "${SANDBOX_PATH}" "${TARGET_PATH}" || true
} )
__install_from_sandbox() { __install_from_sandbox() (
local comparisons
comparisons="$(_compare_sandbox_to_home)" comparisons="$(_compare_sandbox_to_home)"
if [[ -n "$comparisons" ]]; then if [ -n "${comparisons}" ]; then
echo "$comparisons" >&2 echo "${comparisons}" >&2
_die "Found conflicting files. Exiting" 1 _die "Found conflicting files. Exiting" 1
fi fi
echo "Merging to home..." echo "Merging to home..."
_merge_sandbox_to_home _merge_sandbox_to_home
echo "Successfully installed" echo "Successfully installed"
} )
_execute_hook_if_executable() { _execute_hook_if_executable() (
# all hooks gets SUB and SANDBOX_PATH env variables # all hooks gets SUB and SANDBOX_PATH env variables
local -r target="$1" target="$1"
local -r hook_name="$2" hook_name="$2"
local hook_path="${DOTFILES_ROOT}/install-hooks/${target}/${hook_name}" hook_path="${DOTFILES_ROOT}/install-hooks/${target}/${hook_name}"
if [[ -x "$hook_path" ]]; then if [ -x "${hook_path}" ]; then
echo "Executing ${hook_name} for target '${target}'" echo "Executing ${hook_name} for target '${target}'"
"$hook_path" "${hook_path}"
fi fi
} )
execute_pre_hook() { execute_pre_hook() (
_execute_hook_if_executable "$1" "pre-install" _execute_hook_if_executable "${1}" "pre-install"
} )
recursive_execute_pre_hooks() { recursive_execute_pre_hooks() (
local targetfile files="$(map_get_value "${TARGETS}" "${1}")"
for targetfile in ${TARGETS["$1"]}
for targetfile in ${files}
do do
if [[ "${targetfile::1}" = "%" ]]; then if [ "$(string_get_first_char "${targetfile}")" = "%" ]; then
recursive_execute_pre_hooks "${targetfile:1}" recursive_execute_pre_hooks "$(string_exclude_first_char "${targetfile}")"
execute_pre_hook "${targetfile:1}" execute_pre_hook "$(string_exclude_first_char "${targetfile}")"
fi fi
done done
} )
execute_post_hook() { execute_post_hook() (
_execute_hook_if_executable "$1" "post-install" _execute_hook_if_executable "$1" "post-install"
} )
recursive_execute_post_hooks() { recursive_execute_post_hooks() (
local targetfile files="$(map_get_value "${TARGETS}" "${1}")"
for targetfile in ${TARGETS["$1"]}
for targetfile in ${files}
do do
if [[ "${targetfile::1}" = "%" ]]; then if [ "$(string_get_first_char "${targetfile}")" = "%" ]; then
recursive_execute_post_hooks "${targetfile:1}" recursive_execute_post_hooks "$(string_exclude_first_char "${targetfile}")"
execute_post_hook "${targetfile:1}" execute_post_hook "$(string_exclude_first_char "${targetfile}")"
fi fi
done done
} )
install_target() { install_target() (
local -r target="$1" target="${1}"
execute_pre_hook "$target" files="$(map_get_value "${TARGETS}" "${target}")"
recursive_execute_pre_hooks "$target"
_link_files_in_sandbox ${TARGETS["$target"]} execute_pre_hook "${target}"
recursive_execute_pre_hooks "${target}"
_link_files_in_sandbox ${files}
__install_from_sandbox __install_from_sandbox
recursive_execute_post_hooks "$target" recursive_execute_post_hooks "${target}"
execute_post_hook "$target" execute_post_hook "${target}"
} )
is_target_installed() { is_target_installed() (
local not_fully_installed=false target="${1}"
not_fully_installed=false
local targetfile files="$(map_get_value "${TARGETS}" "${target}")"
for targetfile in ${TARGETS["$1"]}
for targetfile in ${files}
do do
if [[ "${targetfile::1}" = "%" ]]; then if [ "$(string_get_first_char "${targetfile}")" = "%" ]; then
is_target_installed "${targetfile:1}" || not_fully_installed=true is_target_installed "$(string_exclude_first_char "${targetfile}")" || not_fully_installed=true
else else
if [[ ! -e "$TARGET_PATH/$targetfile" ]]; then if [ ! -e "${TARGET_PATH}/${targetfile}" ]; then
echo "${targetfile} not linked" echo "${targetfile} not linked"
not_fully_installed=true not_fully_installed=true
fi fi
@ -138,86 +169,85 @@ is_target_installed() {
return 1 return 1
fi fi
return 0 return 0
} )
find_targets_that_depend_on() { find_targets_that_depend_on() (
local target for target in $(map_get_keys "${TARGETS}")
for target in "${!TARGETS[@]}"
do do
if [[ " ${TARGETS["$target"]} " =~ " %${1} " ]]; then files="$(map_get_value "${TARGETS}" "${target}")"
echo "$target"
if map_key_exists "${TARGETS}" "%${1}"; then
echo "${target}"
fi fi
done done
} )
die_if_installed_targets_depend_on() { die_if_installed_targets_depend_on() (
for reverse_dependecy in $(find_targets_that_depend_on "$1") for reverse_dependecy in $(find_targets_that_depend_on "$1")
do do
if is_target_installed "$reverse_dependecy" >/dev/null; then if is_target_installed "${reverse_dependecy}" >/dev/null; then
_die "target '${reverse_dependecy}' is depends on installed target '${1}'. Exiting..." 1 _die "target '${reverse_dependecy}' is depends on installed target '${1}'. Exiting..." 1
fi fi
done done
} )
cmd_unlink() { cmd_unlink() (
local target targetfile
for target in "$@" for target in "$@"
do do
die_if_installed_targets_depend_on "$target" die_if_installed_targets_depend_on "${target}"
for targetfile in ${TARGETS["$target"]} files="$(map_get_value "${TARGETS}" "${target}")"
for targetfile in ${files}
do do
if [[ "${targetfile::1}" = "%" ]]; then if [ "$(string_get_first_char "${targetfile}")" = "%" ]; then
continue continue
fi fi
if [[ -e "${TARGET_PATH}/${targetfile}" ]]; then if [ -e "${TARGET_PATH}/${targetfile}" ]; then
unlink "${TARGET_PATH}/${targetfile}" unlink "${TARGET_PATH}/${targetfile}"
fi fi
done done
done done
} )
cmd_no_target() { cmd_no_target() (
_die "TARGET not exists" 1 _die "TARGET not exists" 1
} )
cmd_list() { cmd_list() (
echo "${!TARGETS[@]}" map_get_keys "${TARGETS}"
} )
target_exists() { target_exists() (
local -r target="$1" target="$1"
[[ " ${!TARGETS[*]} " =~ " ${target} " ]] map_key_exists "${TARGETS}" "${target}"
} )
cmd_install() { cmd_install() (
local target
for target in "$@" for target in "$@"
do do
if target_exists "$target"; then if target_exists "${target}"; then
SANDBOX_PATH="$(mktemp -td "${USER:-user}.dotfiles_XXXXXXX")" SANDBOX_PATH="$(mktemp -td "${USER:-user}.dotfiles_XXXXXXX")"
export SANDBOX_PATH export SANDBOX_PATH
install_target "$target" install_target "${target}"
rm -rf "$SANDBOX_PATH" rm -rf "$SANDBOX_PATH"
else else
cmd_no_target cmd_no_target
fi fi
done done
} )
cmd_help() { cmd_help() (
echo "Dotfiles installation script: echo "Dotfiles installation script:
Usage: ./install TARGET... Usage: ./install TARGET...
Usage: ./install unlink TARGET... Usage: ./install unlink TARGET...
Usage: ./install check TARGET Usage: ./install check TARGET
Usage: ./install list" Usage: ./install list"
} )
unset executed_command readonly executed_command="${1}"
readonly executed_command="$1"
case "$executed_command" in case "${executed_command}" in
unlink) shift; cmd_unlink "$@" ;; unlink) shift; cmd_unlink "$@" ;;
check) shift; is_target_installed "$@" ;; check) shift; is_target_installed "$@" ;;
list) shift; cmd_list "$@" ;; list) shift; cmd_list "$@" ;;