#!/usr/bin/env bash # # Ctlos Linux https://ctlos.github.io # # Dot file management script using zenity, git & rsync # Written by Nathaniel Maia, November 2017 - March 2018 # Config ~/.dotsrc # Ignore github ~/.gitignore # git.io/ctldotsrc # wget git.io/ctldots # sudo cp -fv ~/.bin/dots /usr/bin/dots # immutable values readonly NAME=$(basename "$0") readonly CFG="$HOME/.${NAME}rc" readonly B='\E[1;34m' readonly R='\E[1;31m' readonly G='\E[1;32m' readonly N='\E[0m' readonly BLD='\E[1m' readonly WRN="${R}[WARN]${N}:" readonly INF="${B}[INFO]${N}:" readonly YN="${B}[${R}y${B}/${R}${BLD}N${N}${B}]${N}: " readonly MENUTXT="\n Enter number or letter and press [Enter]. 0/q to cancel\n\n> " readonly MENU=" \n\t ${R}1 ${N}| ${R}${BLD}b${N}ackup\t\t-> Backup Files \t${B}------------------------------------------${N} \n\t ${R}2 ${N}| ${R}${BLD}r${N}estore\t\t-> Restore Files \t${B}------------------------------------------${N}" main_menu() { local choice clear echo -e "$MENU" draw_box 12 printf "$MENUTXT" read -e -r choice clear [[ $choice =~ (v|V) ]] && V="v" case "$choice" in 0|[Qq]|quit) exit 0 ;; 1*|[Bb]*|backup*) [[ $BASE_DIR ]] && { backup_files; exit 0; } || edit_config ;; 2*|[Rr]*|restore*) restore_files && exit 0 esac src_cfg main_menu } draw_box() { local h=${1:-12} # $1 Box Height local w=${2:-57} # $2 Box Width local row=${3:-1} # $3 Starting Row local col=${4:-2} # $4 Starting Column local co=${5:-1} # $5 Box Color, 1-7 ((h--)) # account for corner ((w--)) # account for corner local endrow=$((row + h)) # end row local endcol=$((col + w)) # end column echo -ne "\E[3${co}m" # foreground colour local hz="-" vt="|" cn="+" # horizontal, vertical, and corner chars plot_char() { echo -e "\E[${1};${2}H""$3" } local i=0 for ((r=row; i/dev/null && hash pacman &>/dev/null; then echo -e "\n\n\n\tInstalling:\n\t\tgit\n\t\trsync\n\n\tPlease Wait.." draw_box 8 sleep 1 clear sudo pacman -S git rsync --noconfirm --needed fi } prep_directory() { if [[ $REQ != "True" ]]; then check_reqs if [[ $BASE_DIR ]]; then # is set in config but might not exist yet if [[ ! -d $BASE_DIR ]]; then # is not an existing dir if [[ $REPO ]] && grep -wq '^https://.*/.*/.*$' <<< "$REPO"; then # is REPO actually and address git clone "$REPO" "$BASE_DIR" else mkdir -p$V "$BASE_DIR" # local backup fi fi [[ -d $BASE_DIR ]] && REQ="True" else echo -e "$WRN BASE_DIR must be set in config to continue. $EXITING" fi fi } clean_backup_dirs() { if [[ ${PREV_BACKUPS[*]} ]] && sub_choice "Perform clean backup (wipe BASE_DIR)" "${R}$BASE_DIR${N}"; then for dir in "${PREV_BACKUPS[@]}"; do if [[ -d $dir ]] && sub_choice "Wipe all files in /$(basename "$dir")" "${R}$dir${N}"; then cd "$dir" git rm -r -f . # rm -rf$V "$dir" [[ $V ]] && sleep 0.5 fi done fi } commit_changes() { if grep -q 'https://' <<< "$REPO"; then if sub_choice "Commit and push changes to REPO" "${R}$REPO${N}"; then printf "\nEnter commit message below, an example has been provided\n\n> " read -e -i "$(date +%Y.%m.%d) Update " -r comment if grep -wq '[a-zA-Z0-9]*' <<< "$comment"; then cd "$BASE_DIR/" || return 1 git add . git commit -m "$comment" git push origin "${BRANCH:-HEAD}" else echo "Bad commit message" sleep 1 commit_changes fi fi fi } backup_files() { prep_directory clean_backup_dirs if [[ ${USER_PATHS[*]} || ${ROOT_PATHS[*]} ]]; then echo -e "$INF ${B}Copying files, please wait..$N\n" for f in "${USER_PATHS[@]}"; do [[ -e $f ]] && rsync -aR$V $f "$BASE_DIR/$USER_DIR/" [[ $V ]] && sleep 0.5 done for f in "${ROOT_PATHS[@]}"; do [[ -e $f ]] && rsync -aR$V $f "$BASE_DIR/$ROOT_DIR/" [[ $V ]] && sleep 0.5 done echo -e "$INF ${G}Backup complete$N" sleep 1 else echo -e "$WRN No valid file paths were found.." sleep 2 fi commit_changes } restore_files() { prep_directory if [[ ${PREV_BACKUPS[*]} ]]; then local msg="" for x in "${PREV_BACKUPS[@]}"; do if [[ -e $x ]] && sub_choice "Restore everything from /$(basename "$x")" "${R}$x${N}"; then if grep -q "$BASE_DIR/$USER_DIR" <<< "$x"; then rsync -avPC --filter="exclude $ROOT_DIR" "$BASE_DIR/$USER_DIR/" "$HOME/" elif grep -q "$BASE_DIR/$ROOT_DIR" <<< "$x"; then sudo rsync -avn "$BASE_DIR/$ROOT_DIR/" / else echo -e "\n\n\n\t$WRN Unable to restore\n\n\t$x" draw_box 9 sleep 1 fi fi [[ $V ]] && sleep 0.5 done else echo -e "\n\n\n\t$INF ${R}No Existing backups to restore${N}" draw_box 9 sleep 1 fi } mk_cfg() { [[ $1 == "new" ]] && edit_config for f in "${USER_PATHS[@]}"; do if [[ -e $f ]] && ! grep "$f" <<< "$u_paths"; then u_paths="$u_paths\"$f\"\n" fi done for f in "${ROOT_PATHS[@]}"; do if [[ -e $f ]] && ! grep "$f" <<< "$r_paths"; then r_paths="$r_paths\"$f\"\n" fi done local cfg="# config file for dfm (dotfile manager) \n# repo https address for cloning & pushing (empty for local backup) REPO=\"$REPO\" \n# branch, defaults to current branch (HEAD) BRANCH=\"$BRANCH\" \n# location where backup folder or repo will be created or cloned BASE_DIR=\"$BASE_DIR\" \n# names for storage directories within BASE_DIR. # created only if needed, stores files from below arrays USER_DIR=\"${USER_DIR}\" ROOT_DIR=\"${ROOT_DIR:-root}\" \n# file paths which will be backed up into the directories above USER_PATHS=(\n$u_paths) \nROOT_PATHS=(\n$r_paths)" echo -e "$cfg" > "$CFG" && clear src_cfg } edit_config() { $EDITOR "$CFG" src_cfg } src_cfg() { PREV_BACKUPS=() ! . "$CFG" 2>/dev/null && mk_cfg "new" [[ -d $BASE_DIR/$USER_DIR ]] && PREV_BACKUPS+=("$BASE_DIR/$USER_DIR") [[ -d $BASE_DIR/$ROOT_DIR ]] && PREV_BACKUPS+=("$BASE_DIR/$ROOT_DIR") } sub_choice() { local choice clear printf "\n\n\n\t$1? $YN\n\n\t$2" draw_box 9 tput cup 3 $((${#1} + 17)) read -r choice clear grep -q '[yY]' <<< "$choice" && return 0 || return 1 } usage() { cat <