dotfiles/bootstrap
Fernando Schauenburg d44a8175d1 bootstrap: prune broken links
The links are only removed if they are broken and the target was within
the dotfiles bin directory. This makes it easier to deal with removed
and renamed scripts so I don't have to go hunt for them by hand.
2021-07-22 16:22:42 +02:00

225 lines
6 KiB
Bash
Executable file

#!/bin/sh
set -eu
DRY_RUN=false
DEFAULT_GIT_USER="Fernando Schauenburg"
DEFAULT_GIT_EMAIL="fernando@schauenburg.me"
XDG_CACHE_HOME="${XDG_CACHE_HOME:-$HOME/.cache}"
XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-$HOME/.config}"
XDG_DATA_HOME="${XDG_DATA_HOME:-$HOME/.local/share}"
DOTFILES_DIR="$(dirname "$(realpath "$0")")"
DIRS=$(cat <<EOF
${HOME}/.local/bin
${HOME}/.local/etc/git
${HOME}/.ssh
${XDG_CONFIG_HOME}/alacritty
${XDG_CONFIG_HOME}/dircolors
${XDG_CONFIG_HOME}/git
${XDG_CONFIG_HOME}/mintty
${XDG_CONFIG_HOME}/python
${XDG_CONFIG_HOME}/readline
${XDG_CONFIG_HOME}/tmux
${XDG_CONFIG_HOME}/nvim
${XDG_CONFIG_HOME}/nvim/autoload
${XDG_DATA_HOME}/bash
${XDG_DATA_HOME}/bash-completion/completions
${XDG_DATA_HOME}/less
${XDG_DATA_HOME}/python
${XDG_DATA_HOME}/nvim
${XDG_DATA_HOME}/nvim/plugged
${XDG_DATA_HOME}/nvim/shada
EOF
)
DOTFILES=$(cat <<EOF
files/xcompose ${HOME}/.XCompose
files/bashrc ${HOME}/.bash_profile
files/bashrc ${HOME}/.bashrc
files/ssh_config ${HOME}/.ssh/config
files/alacritty.yml ${XDG_CONFIG_HOME}/alacritty/alacritty.yml
files/gitconfig ${XDG_CONFIG_HOME}/git/config
files/gitignore ${XDG_CONFIG_HOME}/git/ignore
files/inputrc ${XDG_CONFIG_HOME}/readline/inputrc
files/minttyrc ${XDG_CONFIG_HOME}/mintty/config
files/plug.vim ${XDG_CONFIG_HOME}/nvim/autoload/plug.vim
files/python-startup.py ${XDG_CONFIG_HOME}/python/startup.py
files/tmux-colors.conf ${XDG_CONFIG_HOME}/tmux/tmux-colors.conf
files/tmux.conf ${XDG_CONFIG_HOME}/tmux/tmux.conf
files/init.vim ${XDG_CONFIG_HOME}/nvim/init.vim
EOF
)
rst=$(tput sgr0)
red=$(tput setaf 1)
green=$(tput setaf 2)
yellow=$(tput setaf 3)
blue=$(tput setaf 4)
heading() { printf '%s\n' "${blue}===== $1 ==========${rst}"; }
info() { printf '%s\n' "${green}${1}${rst}"; }
warn() { printf '%s\n' "${yellow}${1}${rst}"; }
error() { printf '%s\n' "${red}${1}${rst}"; }
directory_present() { # Make sure directory $1 exists.
if [ -d "$1" ]; then
info "OK: $1"
else
warn "creating $1"
$DRY_RUN || mkdir -p "$1"
fi
}
file_absent() { # Make sure file $1 does not exist.
if [ ! -f "$1" ]; then
info "OK: $1"
else
warn "removing $1"
$DRY_RUN || rm "$1"
fi
}
file_touch() { # Make sure file $1 exists (content is irrelevant).
if [ -f "$1" ]; then
info "OK: $1"
else
warn "creating $1"
$DRY_RUN || touch "$1"
fi
}
prune_link() { # Remove $1 if it's a broken link to a dotfile script.
if [ -h "$1" ]; then # it's a symbolic link
target="$(readlink "$1")"
case "$target" in
"$DOTFILES_DIR/bin/"*) # it points into dotfiles bin
if [ ! -e "$1" ]; then # target of the link missing
warn "pruning $1 -> $target"
$DRY_RUN || rm -f "$1"
fi
;;
esac
fi
}
file_link() { # Make sure $1 is a link to $2.
target="$(realpath "$2")"
if [ "$(readlink "$1")" = "$target" ]; then
info "OK: $1"
else
warn "linking $1 -> $target"
$DRY_RUN || ln -sf "$target" "$1"
fi
}
file_content() { # Ensure $1 and $2 contents are equal, updating $1 if needed.
if diff "$1" "$2" >/dev/null 2>&1; then
info "OK: $1"
else
warn "overwriting $1 with $2:"
cat "$2"
$DRY_RUN || cp -f "$2" "$1"
fi
}
task_gather_user_info() {
heading "Gather user information"
printf 'git name [%s]: ' "$DEFAULT_GIT_USER"
read -r git_user
printf 'git e-mail [%s]: ' "$DEFAULT_GIT_EMAIL"
read -r git_email
}
task_remove_bash_profile() {
heading "Remove pre-installed bash profile"
for f in .bash_logout .profile; do
file_absent "$HOME/$f"
done
}
task_ensure_directories() {
heading "Ensure existence of directories used by dotfiles"
echo "$DIRS" | while read -r d; do directory_present "$d"; done
}
task_link_dotfiles() {
heading "Link dotfiles"
echo "$DOTFILES" | while read -r dest src; do
file_link "$src" "$dest"
done
}
task_deploy_templates() {
heading "Deploy templates"
temp_git="$(mktemp)"
cat >"$temp_git" <<EOF
# *************************************
# * DO NOT EDIT THIS FILE *
# *************************************
#
# This file was generated by the bootstrap script and any changes will be
# overwritten the next time the it is run. For local settings, use this
# instead:
#
# ~/.local/etc/git/config
#
EOF
git config -f "$temp_git" user.name "${git_user:-$DEFAULT_GIT_USER}"
git config -f "$temp_git" user.email "${git_email:-$DEFAULT_GIT_EMAIL}"
file_content "$HOME/.local/etc/git/config.host" "$temp_git"
}
task_prune_broken_bin() {
heading "Prune broken links to commands"
for cmd in $HOME/.local/bin/*; do
prune_link "$cmd"
done
}
task_link_local_bin() {
heading "Link local commands"
for cmd in bin/*; do
file_link "$HOME/.local/bin/$(basename "$cmd")" "$cmd"
done
}
task_link_dircolors() {
heading "Link dircolors"
for f in dircolors/*; do
file_link "$XDG_CONFIG_HOME/dircolors/$(basename "$f")" "$f"
done
}
task_make_logins_silent() {
heading "Make logins silent"
file_touch "$HOME/.hushlogin"
}
task_install_nvim_plugins() {
heading "Install neovim plugins"
if command -v nvim >/dev/null 2>&1; then
warn "Installing neovim plugins"
$DRY_RUN || nvim -nes -u "$XDG_CONFIG_HOME/nvim/init.vim" -c 'PlugInstall | qall!'
else
error "neovim is not installed; skipping plugin installation..."
fi
}
main() {
$DRY_RUN && warn "Dry run: no changes will actually be made."
task_gather_user_info
task_remove_bash_profile
task_ensure_directories
task_link_dotfiles
task_deploy_templates
task_prune_broken_bin
task_link_local_bin
task_link_dircolors
task_make_logins_silent
task_install_nvim_plugins
}
main