#!/usr/bin/env bash # shellcheck disable=SC2155,SC2223 set -o nounset set -o errexit set -o shwordsplit 2>/dev/null || true # Make zsh behave like bash HOME=${HOME:-$(cd ~ && pwd)} have_command() { command -v "${1}" >/dev/null 2>&1 } link_directory_contents() { local SRCDIR="${1}" local DESTDIR="${2}" local PREFIX="${3}" local file # shellcheck disable=SC2086 find "${SRCDIR}" \( -name .git -o \ -name install.sh -o \ -name README.md -o \ -name .gitignore \) \ -prune -o -type f -print | \ while read -r file ; do local TARGET="${DESTDIR}/${PREFIX}${file#"${SRCDIR}"/}" mkdir -p "$(dirname "${TARGET}")" ln -s -f "${file}" "${TARGET}" done } ssh_key_already_installed() { # Return 1 if the key isn't already installed, 0 if it is local AK="${HOME}/.ssh/authorized_keys" if [[ ! -f "$AK" ]] ; then return 1 fi # Extract the key data (field 2) from the key file, ignoring comments local key_data key_data=$(awk '/^ssh-/ {print $2}' "$1") if [[ -z "${key_data}" ]]; then # Not a valid key file return 1 fi # Use grep with fixed-string matching to see if the key is present. # The exit code of grep is 0 on match, 1 on no match, which is perfect. grep -F -q -- "${key_data}" "${AK}" } install_ssh_keys() { # Install SSH keys verbose 'Installing SSH keys...' local AK="${HOME}/.ssh/authorized_keys" local key local keydir if [[ "${TRUST_ALL_KEYS}" = 1 ]] ; then keydir="${BASEDIR}/keys/ssh" else keydir="${BASEDIR}/keys/ssh/trusted" fi for key in "${keydir}"/* ; do if [[ ! -f "${key}" ]] ; then continue fi if ssh_key_already_installed "${key}" ; then verbose "Key $(basename "${key}") already installed..." continue fi echo "# $(basename "${key}") added from skel on $(date +%Y-%m-%d)" >> "${AK}" cat "${key}" >> "${AK}" done } install_gpg_keys() { have_command gpg || \ return 0 local key for key in "${BASEDIR}"/keys/gpg/* ; do gpg --import < "${key}" >/dev/null 2>&1 done } install_known_hosts() { verbose 'Installing known hosts...' >&2 local skel_hosts="${BASEDIR}/keys/known_hosts" local user_hosts="${HOME}/.ssh/known_hosts" local merge_script="${BASEDIR}/skeltools/merge_authorized_keys" if [[ ! -f "${skel_hosts}" ]]; then return 0 fi mkdir -p "${HOME}/.ssh" if [[ -f "${user_hosts}" ]]; then # User has an existing known_hosts file, merge is required. local tmpf tmpf="$(mktemp)" if [[ -x "${merge_script}" ]]; then # Use the robust awk script for merging. verbose "Merging known_hosts with authoritative script..." "${merge_script}" "${skel_hosts}" "${user_hosts}" > "$tmpf" else # Fallback to the old, less robust method if the script is missing. verbose "Warning: ${merge_script} not found or not executable. Using simple sort." cat "${skel_hosts}" "${user_hosts}" | sort -u > "$tmpf" fi # Safely replace the original file. cat "$tmpf" >| "${user_hosts}" rm "$tmpf" else # User does not have a known_hosts file, just copy the new one. cp "${skel_hosts}" "${user_hosts}" fi } install_keys() { install_ssh_keys install_gpg_keys install_known_hosts } read_saved_prefs() { # Can't use basedir here as we don't have it yet local pref_file="$(dirname "$0")/.installed-prefs" if [[ -f "${pref_file}" ]] ; then verbose "Loading saved skel preferences from ${pref_file}" # source is a bashism # shellcheck disable=SC1090 . "${pref_file}" fi } save_prefs() { [[ "$SAVE" = 1 ]] || return 0 local pref_file=${BASEDIR}/.installed-prefs { echo "BASEDIR=\"${BASEDIR}\"" echo "MINIMAL=\"${MINIMAL}\"" echo "INSTALL_KEYS=\"${INSTALL_KEYS}\"" echo "TRUST_ALL_KEYS=\"${TRUST_ALL_KEYS}\"" echo "VERBOSE=\"${VERBOSE}\"" } >| "$pref_file" } cleanup() { if [[ -x "${BASEDIR}/bin/prune-broken-symlinks.sh" ]]; then "${BASEDIR}/bin/prune-broken-symlinks.sh" -y "${HOME}/.zshrc.d" "${BASEDIR}/bin/prune-broken-symlinks.sh" -y "${HOME}/bin" fi } verbose() { [[ "${VERBOSE:-0}" = 1 ]] && echo "$@" >&2 || return 0 } # Operations install_dotfiles() { link_directory_contents "${BASEDIR}/dotfiles" "${HOME}" "." if [[ -d "${BASEDIR}/local_dotfiles" ]] ; then link_directory_contents "${BASEDIR}/local_dotfiles" "${HOME}" "." fi if [[ -d "${BASEDIR}/dotfile_overlays" ]] ; then for dotfiledir in "${BASEDIR}/dotfile_overlays/"* ; do if [[ -d "${dotfiledir}" ]] ; then link_directory_contents "${dotfiledir}" "${HOME}" "." fi done fi } install_main() { if [[ -d "${BASEDIR}/.git" ]] && have_command git ; then if [[ -z "$(git -C "${BASEDIR}" status --porcelain)" ]]; then git -C "${BASEDIR}" pull --ff-only || true else echo "Skipping self-update: repository has local changes." >&2 fi fi [[ "$MINIMAL" = 1 ]] || { # Install vim-plug if not already present local VIM_PLUG_URL="https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim" local VIM_AUTOLOAD_DIR="${HOME}/.vim/autoload" local VIM_PLUG_FILE="${VIM_AUTOLOAD_DIR}/plug.vim" if [[ ! -f "${VIM_PLUG_FILE}" ]]; then verbose "Installing vim-plug..." mkdir -p "${VIM_AUTOLOAD_DIR}" if have_command curl; then curl -fsLo "${VIM_PLUG_FILE}" --create-dirs "${VIM_PLUG_URL}" else echo "Error: curl not found. Cannot install vim-plug." >&2 fi fi # Install TPM (Tmux Plugin Manager) if not already present local TPM_DIR="${HOME}/.tmux/plugins/tpm" local TPM_REPO="https://github.com/tmux-plugins/tpm" if [[ ! -d "${TPM_DIR}" ]]; then verbose "Installing TPM (Tmux Plugin Manager)..." if have_command git; then git clone --depth 1 "${TPM_REPO}" "${TPM_DIR}" else echo "Error: git not found. Cannot install TPM." >&2 fi fi # try to update dotfile overlays if [[ -d "${BASEDIR}/dotfile_overlays" ]] ; then for dotfiledir in "${BASEDIR}/dotfile_overlays/"* ; do if [[ -d "${dotfiledir}/.git" ]] ; then git -C "${dotfiledir}" pull --ff-only || true fi done fi } install_dotfiles link_directory_contents "${BASEDIR}/bin" "${HOME}/bin" "" # macOS specific Homebrew bundle installation if [[ "$(uname)" == "Darwin" ]] && have_command brew && [[ -f "${BASEDIR}/Brewfile" ]]; then verbose "Checking Homebrew bundle..." brew bundle install --file="${BASEDIR}/Brewfile" fi [[ "$INSTALL_KEYS" = 1 ]] && install_keys save_prefs cleanup } # Setup variables read_saved_prefs # Defaults if not passed in or saved. # TODO: use flags instead of environment variables. : ${BASEDIR:=$HOME/.skel} : ${MINIMAL:=0} : ${INSTALL_KEYS:=1} : ${TRUST_ALL_KEYS:=0} : ${VERBOSE:=0} : ${SAVE:=1} # Check prerequisites if [[ ! -d "$BASEDIR" ]] ; then echo "Please install to $BASEDIR!" 1>&2 exit 1 fi OPERATION=${1:-install} case $OPERATION in install) install_main ;; dotfiles) install_dotfiles ;; *) echo "Unknown operation $OPERATION." >&2 exit 1 ;; esac echo "OK"