modify install script
This commit is contained in:
361
install
361
install
@@ -1,60 +1,50 @@
|
||||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" >/dev/null 2>&1 &&
|
||||
pwd)"
|
||||
PROFILES_DIR="${SCRIPT_DIR}/profiles"
|
||||
STOW_ARGS=(
|
||||
"--no-folding"
|
||||
"--adopt"
|
||||
"--dir" "${PROFILES_DIR}"
|
||||
"--target" "${HOME}"
|
||||
script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" >/dev/null 2>&1 &&
|
||||
pwd)"
|
||||
readonly script_dir
|
||||
readonly profiles_dir="$script_dir/profiles"
|
||||
readonly -a stow_args=(
|
||||
"--no-folding"
|
||||
"--adopt"
|
||||
"--dir" "$profiles_dir"
|
||||
"--target" "$HOME"
|
||||
)
|
||||
SCRIPT_NAME="${0##*/}"
|
||||
|
||||
readonly SCRIPT_DIR
|
||||
readonly PROFILES_DIR
|
||||
readonly -a STOW_ARGS
|
||||
readonly SCRIPT_NAME
|
||||
|
||||
print_opt() {
|
||||
local -r option="$1"
|
||||
local -r description="$2"
|
||||
printf " %-22s %s\n" "${option}" "${description}"
|
||||
}
|
||||
readonly script_name="${0##*/}"
|
||||
|
||||
shell_quote() {
|
||||
local -r string="$1"
|
||||
printf "'%s'" "${string//'/'\\''/}"
|
||||
local -r string="$1"
|
||||
printf "'%s'" "${string//'/'\\''/}"
|
||||
}
|
||||
|
||||
array_contains() {
|
||||
local -r target_item="$1"
|
||||
shift
|
||||
local -a -r array=("$@")
|
||||
local item
|
||||
for item in "${array[@]}"; do
|
||||
if [[ ${item} == "${target_item}" ]]; then
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
local -r target_item="$1"
|
||||
shift
|
||||
local -a -r array=("$@")
|
||||
local item
|
||||
for item in "${array[@]}"; do
|
||||
if [[ ${item} == "$target_item" ]]; then
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
die() {
|
||||
local -r message="$1"
|
||||
printf "%s: %b\n" "${SCRIPT_NAME}" "${message}" >&2
|
||||
exit 1
|
||||
local -r message="$1"
|
||||
printf "%s: %b\n" "$script_name" "$message" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
invalid_option() {
|
||||
local -r option="$1"
|
||||
die "invalid option $(shell_quote "${option}")
|
||||
Try '${SCRIPT_NAME} --help' for usage."
|
||||
local -r option="$1"
|
||||
die "invalid option $(shell_quote "$option")
|
||||
Try '$script_name --help' for usage."
|
||||
}
|
||||
|
||||
check_deps() {
|
||||
if ! command -v stow >/dev/null 2>&1; then
|
||||
cat >&2 <<EOF
|
||||
if ! command -v stow >/dev/null 2>&1; then
|
||||
cat >&2 <<EOF
|
||||
Error: 'stow' not found in PATH.
|
||||
|
||||
GNU Stow is a symlink farm manager used to deploy dotfiles/packages
|
||||
@@ -71,14 +61,14 @@ source if GNU Stow is not packaged or your system requires it.
|
||||
|
||||
Project: https://www.gnu.org/software/stow/
|
||||
EOF
|
||||
exit 1
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
require_profiles_dir() {
|
||||
if [[ ! -d "${PROFILES_DIR}" ]]; then
|
||||
cat >&2 <<EOF
|
||||
Error: profiles directory not found: ${PROFILES_DIR}
|
||||
if [[ ! -d "$profiles_dir" ]]; then
|
||||
cat >&2 <<EOF
|
||||
Error: profiles directory not found: $profiles_dir
|
||||
|
||||
This directory must contain profile subdirectories with dotfiles.
|
||||
|
||||
@@ -87,98 +77,93 @@ Make sure you:
|
||||
- create the directory, or
|
||||
- clone the dotfiles repository correctly.
|
||||
|
||||
More information about what is profile can be found using '${SCRIPT_NAME} --help profile'.
|
||||
More information about what is profile can be found using '$script_name --help profile'.
|
||||
EOF
|
||||
exit 1
|
||||
fi
|
||||
mapfile -t PROFILES < <(find "${PROFILES_DIR}" -mindepth 1 -maxdepth 1 \
|
||||
-type d -printf '%f\n')
|
||||
readonly PROFILES
|
||||
exit 1
|
||||
fi
|
||||
mapfile -t PROFILES < <(find "$profiles_dir" -mindepth 1 -maxdepth 1 \
|
||||
-type d -printf '%f\n')
|
||||
readonly PROFILES
|
||||
}
|
||||
|
||||
parse_args() {
|
||||
while (($# > 0)); do
|
||||
case $1 in
|
||||
-h)
|
||||
show_general_help
|
||||
exit 0
|
||||
;;
|
||||
--help)
|
||||
if (($# >= 2)) && [[ $2 ]] && [[ $2 != -* ]]; then
|
||||
show_help_topic "$2"
|
||||
else
|
||||
show_general_help
|
||||
fi
|
||||
exit 0
|
||||
;;
|
||||
--help=*)
|
||||
local -r topic="${1#*=}"
|
||||
[[ -z ${topic} ]] && show_general_help && exit 0
|
||||
show_help_topic "${topic}"
|
||||
exit 0
|
||||
;;
|
||||
-l | --list-profiles)
|
||||
list_profiles="true"
|
||||
shift
|
||||
;;
|
||||
-P | --prune-symlinks)
|
||||
prune_symlinks="true"
|
||||
shift
|
||||
;;
|
||||
-v | --verbose)
|
||||
verbose="true"
|
||||
shift
|
||||
;;
|
||||
-p | --profile)
|
||||
( (($# < 2)) || [[ -z "$2" ]]) && die "--profile requires an argument"
|
||||
profile="$2"
|
||||
shift 2
|
||||
;;
|
||||
*) invalid_option "$1" ;;
|
||||
esac
|
||||
done
|
||||
while (($# > 0)); do
|
||||
case $1 in
|
||||
-h)
|
||||
show_general_help
|
||||
exit 0
|
||||
;;
|
||||
--help)
|
||||
if (($# >= 2)) && [[ $2 ]] && [[ $2 != -* ]]; then
|
||||
show_help_topic "$2"
|
||||
else
|
||||
show_general_help
|
||||
fi
|
||||
exit 0
|
||||
;;
|
||||
--help=*)
|
||||
local -r topic="${1#*=}"
|
||||
[[ -z $topic ]] && show_general_help && exit 0
|
||||
show_help_topic "$topic"
|
||||
exit 0
|
||||
;;
|
||||
-l | --list-profiles)
|
||||
list_profiles="true"
|
||||
shift
|
||||
;;
|
||||
-v | --verbose)
|
||||
verbose="true"
|
||||
shift
|
||||
;;
|
||||
-p | --profile)
|
||||
( (($# < 2)) || [[ -z "$2" ]]) &&
|
||||
die "--profile requires an argument"
|
||||
profile="$2"
|
||||
shift 2
|
||||
;;
|
||||
*) invalid_option "$1" ;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
validate_args() {
|
||||
if [[ -n "${list_profiles}" ]] &&
|
||||
[[ -n "${prune_symlinks}" || -n "${verbose}" || -n "${profile}" ]]; then
|
||||
die "options '--list-profiles' and others cannot be used together"
|
||||
fi
|
||||
if [[ -n "$list_profiles" ]] &&
|
||||
[[ -n "$verbose" || -n "$profile" ]]; then
|
||||
die "options '--list-profiles' and others cannot be used together"
|
||||
fi
|
||||
|
||||
if [[ -n "${profile}" ]] &&
|
||||
! array_contains "${profile}" "${PROFILES[@]}"; then
|
||||
die "profile $(shell_quote "${profile}") cannot be found"
|
||||
fi
|
||||
if [[ -n "$profile" ]] &&
|
||||
! array_contains "$profile" "${PROFILES[@]}"; then
|
||||
die "profile $(shell_quote "$profile") cannot be found"
|
||||
fi
|
||||
}
|
||||
|
||||
show_general_help() {
|
||||
echo "Usage: ${SCRIPT_NAME} [options]"
|
||||
echo " ${SCRIPT_NAME} -l | --list-profiles"
|
||||
echo " ${SCRIPT_NAME} --help[=TOPIC]"
|
||||
echo
|
||||
echo "Options:"
|
||||
print_opt "-h, --help[=TOPIC]" \
|
||||
"show general help or help for a specific topic"
|
||||
print_opt "-p, --profile PROFILE" "select profile"
|
||||
print_opt "-v, --verbose" "increase verbosity"
|
||||
print_opt "-P, --prune-symlinks" \
|
||||
"remove dangling symlinks in home directory"
|
||||
print_opt "-l, --list-profiles" \
|
||||
"list available profiles (cannot be combined with other options)"
|
||||
echo
|
||||
echo "Topics:"
|
||||
print_opt "profile" "explanation of profile structure and usage"
|
||||
echo
|
||||
echo "Profile:"
|
||||
echo " Directory containing dotfiles plus optional parent and hooks/."
|
||||
echo " Used by the installer to group and deploy related configurations."
|
||||
cat <<EOF
|
||||
Usage: $script_name [options]
|
||||
$script_name -l | --list-profiles
|
||||
$script_name --help[=TOPIC]
|
||||
|
||||
Options:
|
||||
-h, --help[=TOPIC] show general help or help for a specific topic
|
||||
-p, --profile PROFILE select profile
|
||||
-v, --verbose increase verbosity
|
||||
-l, --list-profiles list available profiles (cannot be combined with other options)
|
||||
|
||||
Topics:
|
||||
profile explanation of profile structure and usage
|
||||
|
||||
Profile:
|
||||
Directory containing dotfiles plus optional parent and hooks/.
|
||||
Used by the installer to group and deploy related configurations.
|
||||
EOF
|
||||
}
|
||||
|
||||
show_help_topic() {
|
||||
local topic="$1"
|
||||
case "${topic}" in
|
||||
profile)
|
||||
cat <<EOF
|
||||
local topic="$1"
|
||||
case "$topic" in
|
||||
profile)
|
||||
cat <<EOF
|
||||
A profile is a directory under 'profiles/' that groups dotfiles to be
|
||||
installed together using GNU Stow.
|
||||
|
||||
@@ -196,97 +181,97 @@ tree automatically and installs profiles in the correct order.
|
||||
Use .stow-local-ignore to prevent linking internal files such as hooks/
|
||||
and parent.
|
||||
EOF
|
||||
;;
|
||||
*) die "unknown help topic: ${topic}" ;;
|
||||
esac
|
||||
;;
|
||||
*) die "unknown help topic: $topic" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
run_pre_hooks() {
|
||||
local -r target_profile="$1"
|
||||
for hook in "${PROFILES_DIR}/${target_profile}"/hooks/pre/*; do
|
||||
if [[ -f "${hook}" && -x "${hook}" ]]; then
|
||||
echo "Running pre-install hook ${hook##*/}"
|
||||
"${hook}"
|
||||
fi
|
||||
done
|
||||
local -r target_profile="$1"
|
||||
for hook in "$profiles_dir/$target_profile"/hooks/pre/*; do
|
||||
if [[ -f "$hook" && -x "$hook" ]]; then
|
||||
echo "Running pre-install hook ${hook##*/}"
|
||||
"$hook"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
run_post_hooks() {
|
||||
local -r target_profile="$1"
|
||||
for hook in "${PROFILES_DIR}/${target_profile}"/hooks/post/*; do
|
||||
if [[ -f "${hook}" && -x "${hook}" ]]; then
|
||||
echo "Running post-install hook ${hook##*/}"
|
||||
"${hook}"
|
||||
fi
|
||||
done
|
||||
local -r target_profile="$1"
|
||||
for hook in "$profiles_dir/$target_profile"/hooks/post/*; do
|
||||
if [[ -f "$hook" && -x "$hook" ]]; then
|
||||
echo "Running post-install hook ${hook##*/}"
|
||||
"$hook"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
prune_symlinks() {
|
||||
echo -n "Removing dangling symlinks in home directory... "
|
||||
echo -n "Removing dangling symlinks in home directory... "
|
||||
|
||||
pushd "${HOME}" >/dev/null || exit 1
|
||||
local common_files
|
||||
local -r target_profile="$1"
|
||||
mapfile -t common_files < <(comm -12 <(find "${HOME}" -type l \
|
||||
-printf '%P\n' 2>/dev/null | sort) \
|
||||
<(find "${PROFILES_DIR}/${target_profile}" -type f -printf '%P\n' | sort))
|
||||
local target_file
|
||||
for target_file in "${common_files[@]}"; do
|
||||
local -a rm_args=("--force")
|
||||
[[ ${verbose} ]] && rm_args+=("--verbose")
|
||||
rm "${rm_args[@]}" -- "${target_file}"
|
||||
done
|
||||
popd >/dev/null || exit 1
|
||||
pushd "${HOME}" >/dev/null || exit 1
|
||||
local common_files
|
||||
local -r target_profile="$1"
|
||||
mapfile -t common_files < <(comm -12 <(find "${HOME}" -type l \
|
||||
-printf '%P\n' 2>/dev/null | sort) \
|
||||
<(find "$profiles_dir/$target_profile" -type f -printf '%P\n' | sort))
|
||||
local target_file
|
||||
for target_file in "${common_files[@]}"; do
|
||||
local -a rm_args=("--force")
|
||||
[[ ${verbose} ]] && rm_args+=("--verbose")
|
||||
rm "${rm_args[@]}" -- "$target_file"
|
||||
done
|
||||
popd >/dev/null || exit 1
|
||||
|
||||
echo "OK"
|
||||
echo "OK"
|
||||
}
|
||||
|
||||
stow_profile() {
|
||||
local -r target_profile="$1"
|
||||
if stow "${STOW_ARGS[@]}" "${target_profile}" 2>&1; then
|
||||
echo "Profile ${target_profile} has been stowed"
|
||||
else
|
||||
echo "Failed to create symlinks pointing to dotfiles" \
|
||||
"in profile ${target_profile}"
|
||||
fi
|
||||
local -r target_profile="$1"
|
||||
if stow "${stow_args[@]}" "$target_profile" 2>&1; then
|
||||
echo "Profile $target_profile has been stowed"
|
||||
else
|
||||
echo "Failed to create symlinks pointing to dotfiles" \
|
||||
"in profile $target_profile"
|
||||
fi
|
||||
}
|
||||
|
||||
fetch_parents() {
|
||||
local -r target_profile="$1"
|
||||
if [[ -s "${PROFILES_DIR}/${target_profile}/parent" ]]; then
|
||||
local parent
|
||||
parent="$(cat "${PROFILES_DIR}/${target_profile}/parent")"
|
||||
if [[ -s "${PROFILES_DIR}/${parent}/parent" ]]; then
|
||||
fetch_parents "${parent}"
|
||||
local -r target_profile="$1"
|
||||
if [[ -s "$profiles_dir/$target_profile/parent" ]]; then
|
||||
local parent
|
||||
parent="$(cat "$profiles_dir/$target_profile/parent")"
|
||||
if [[ -s "$profiles_dir/$parent/parent" ]]; then
|
||||
fetch_parents "$parent"
|
||||
fi
|
||||
echo "$parent"
|
||||
fi
|
||||
echo "${parent}"
|
||||
fi
|
||||
}
|
||||
|
||||
build_deptree() {
|
||||
local -r target_profile="$1"
|
||||
fetch_parents "${target_profile}"
|
||||
echo "${target_profile}"
|
||||
local -r target_profile="$1"
|
||||
fetch_parents "$target_profile"
|
||||
echo "$target_profile"
|
||||
}
|
||||
|
||||
install_dotfiles() {
|
||||
local target_profile
|
||||
for target_profile in $(build_deptree "${profile}"); do
|
||||
run_pre_hooks "${target_profile}"
|
||||
[[ -n "${prune_symlinks}" ]] && prune_symlinks "${target_profile}"
|
||||
stow_profile "${target_profile}"
|
||||
run_post_hooks "${target_profile}"
|
||||
done
|
||||
local target_profile
|
||||
for target_profile in $(build_deptree "$profile"); do
|
||||
run_pre_hooks "${target_profile}"
|
||||
prune_symlinks "${target_profile}"
|
||||
stow_profile "${target_profile}"
|
||||
run_post_hooks "${target_profile}"
|
||||
done
|
||||
}
|
||||
|
||||
main() {
|
||||
check_deps
|
||||
parse_args "$@"
|
||||
require_profiles_dir
|
||||
(($# == 0)) && show_general_help && exit 1
|
||||
validate_args
|
||||
[[ -n "${list_profiles}" ]] && echo "${PROFILES[@]}" && exit 0
|
||||
install_dotfiles
|
||||
check_deps
|
||||
parse_args "$@"
|
||||
require_profiles_dir
|
||||
(($# == 0)) && show_general_help && exit 1
|
||||
validate_args
|
||||
[[ -n "$list_profiles" ]] && echo "${PROFILES[@]}" && exit 0
|
||||
install_dotfiles
|
||||
}
|
||||
|
||||
main "$@"
|
||||
|
||||
Reference in New Issue
Block a user