#!/bin/sh pebkac() { [ "${*}" ] && printf "%s\n\n" "${*}" cat < [user ...] -c Check symlinks for sanity -i Install symlinks -r Remove symlinks This script will adjust the bash-config symlinks in the homedir of the supplied user, or all users if unspecified. This script will avoid tampering with files that it thinks are manually added. Use -c to check, and verify the files manually afterwards. EOF exit "${#}" } files="bash_profile bashrc" userlist() { # Ignore users with shell not in /etc/shells # Ignore users with invalid homedir # Ignore subsequent users with the same homedir (root, toor, daemon, etc) # List all remaining usernames local _home_cache getent passwd | while IFS=: read user pass uid gid gecos home shell extra do getent shells "${shell}" >&- || continue [ -d "${home}" ] || continue echo "${_home_cache}" | grep -q " ${home} " && continue _home_cache="${_home_cache} ${home} " echo "${user}" done } userdir() { [ "${1}" ] || return 1 # This is a loop because read will not work after pipe due to subshell isolation getent passwd "${1}" | while IFS=: read user pass uid gid gecos home shell extra do echo "${home}" break done } mine() { [ "${1}" ] || return 1 user="${1}"; shift [ "${#}" -eq 0 ] && set - ${files} while [ "${1}" ] do file="${1}" link="$(userdir "${user}")/.${file}" real="/usr/local/etc/bash-config/${file}" if [ ! -L "${link}" ] || [ "$(realpath "$(readlink "${link}")")" != "$(realpath "${real}")" ] then return 1 fi shift done return 0 } gone() { [ "${1}" ] || return 1 user="${1}"; shift [ "${#}" -eq 0 ] && set - ${files} while [ "${1}" ] do file="${1}" link="$(userdir "${user}")/.${file}" [ -f "${link}" ] && return 1 shift done return 0 } check() { [ "${1}" ] || return 1 user="${1}"; shift [ "${#}" -eq 0 ] && set - ${files} echo "Checking user ${user}..." while [ "${1}" ] do file="${1}" if gone "${user}" "${file}" then echo " File ${file} does not exist." else if mine "${user}" "${file}" then echo " File ${file} is mine." else echo " File ${file} exists, but is not mine." fi fi shift done } install() { [ "${1}" ] || return 1 user="${1}" if mine "${user}" then echo "User symlinks appear to be correct" return 0 fi if ! gone "${user}" then echo "User files (${files}) appear to exist, and are not mine" echo "Refusing to molest them" return 1 fi for file in ${files} do link="$(userdir "${user}")/.${file}" real="/usr/local/etc/bash-config/${file}" [ "${link%%.${file}}" = "/" ] && return 1 su "${user}" -c "ln -s '${real}' '${link}'" || return $? done return 0 } remove() { [ "${1}" ] || return 1 user="${1}" if gone "${user}" then echo "User symlinks already removed" return 0 fi if ! mine "${user}" then echo "User files (${files}) appear to exist, and are not mine" echo "Refusing to molest them" return 1 fi for file in ${files} do link="$(userdir "${user}")/.${file}" [ "${link%%.${file}}" = "/" ] && return 1 rm -f "${link}" || return $? done return 0 } state() { [ -z "${cmd}" ] || pebkac "Only specify one of -c -i -r" cmd="${1}" } cmd="" while getopts "cir" opt do case "${opt}" in c) state check ;; i) state install ;; r) state remove ;; [?]) pebkac "Unrecognized option ${OPTARG}" ;; esac done shift $(( $OPTIND - 1 )) [ "${cmd}" ] || pebkac # Default to current user if not root if [ "$(id -u)" -gt 0 ] then if [ "${*}" -a "${*}" != "${USER}" ] then echo "Mortal users can only modify themselves" >&2 exit 1 fi [ "${#}" -eq 0 ] && set - ${USER} else [ "${#}" -eq 0 ] && set - $(userlist) fi printf "Working on user(s) " echo "${@}" while [ "${1}" ] do "${cmd}" "${1}" shift done