aboutsummaryrefslogtreecommitdiffstats
path: root/dot-backup.sh
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-05-06 09:39:58 +0200
committerDanilo M. <danix@danix.xyz>2026-05-06 09:39:58 +0200
commit1076091203f9387385e663ad19c7ea3648650bec (patch)
treece57f4dc55f03f0ca3a8102b5de6a3455105eb20 /dot-backup.sh
parentd446b2521465f11828d172209b61abcb00b18cb4 (diff)
downloaddots-backup-1076091203f9387385e663ad19c7ea3648650bec.tar.gz
dots-backup-1076091203f9387385e663ad19c7ea3648650bec.zip
Add flags, config system, restore mode, and updated docs
- Add -n/--dry-run, -v/--verbose, -r/--restore, -q/--quiet flags - Auto-commit after backup with timestamp message - Load ~/.config/dot-backup/config for overriding defaults - Load extra dotfiles from ~/.config/dot-backup/files.list - Quiet mode redirects all output to ~/.local/share/dot-backup/backup.log - Add config.example, CLAUDE.md, and updated README Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'dot-backup.sh')
-rwxr-xr-xdot-backup.sh247
1 files changed, 202 insertions, 45 deletions
diff --git a/dot-backup.sh b/dot-backup.sh
index aef6fee..6813b14 100755
--- a/dot-backup.sh
+++ b/dot-backup.sh
@@ -1,12 +1,12 @@
#! /bin/bash
#####################################################################
-# ___ __ ___ __
-# __| _/ ____ _/ |_ \_ |__ _____ ____ | | ____ ________
-# / __ | / __ \\ __\ | __ \\__ \ _/ ___\| |/ / | \____ \
+# ___ __ ___ __
+# __| _/ ____ _/ |_ \_ |__ _____ ____ | | ____ ________
+# / __ | / __ \\ __\ | __ \\__ \ _/ ___\| |/ / | \____ \
# / /_/ |( \_\ )| | | \_\ \/ __ \_ \___| \| | / |_\ \
# \____ | \____/ |__| |___ /____ /\___ /__|_ \____/| ___/
-# \/ \/ \/ \/ \/ |__|
+# \/ \/ \/ \/ \/ |__|
#
# by danix
#
@@ -23,9 +23,53 @@ NC='\033[0m' # No Color
### DIRECTORIES
WORKDIR=$(pwd)
-# Output directory
-#DEFAULT_OUTPUT_DIR="${HOME}/Programming/GIT/my-dotfiles"
-DEFAULT_OUTPUT_DIR="/tmp/my-dotfiles"
+
+# Defaults (can be overridden by config file)
+DEFAULT_OUTPUT_DIR="${HOME}/Programming/GIT/my-dotfiles"
+LOG_FILE="${HOME}/.local/share/dot-backup/backup.log"
+DOTFILES_LIST="${HOME}/.config/dot-backup/files.list"
+CONFIG_FILE="${HOME}/.config/dot-backup/config"
+
+# Load config file if present
+if [[ -f "$CONFIG_FILE" ]]; then
+ # shellcheck source=/dev/null
+ source "$CONFIG_FILE"
+fi
+
+# Flags
+DRY_RUN=false
+VERBOSE=false
+RESTORE=false
+QUIET=false
+
+usage() {
+ echo "Usage: $0 [options]"
+ echo " -n, --dry-run Show what would be copied without copying"
+ echo " -v, --verbose Print each file as it is processed"
+ echo " -r, --restore Restore dotfiles from backup to their original locations"
+ echo " -q, --quiet Suppress stdout; write output to log instead"
+ echo " -h, --help Show this help"
+ exit 0
+}
+
+for arg in "$@"; do
+ case $arg in
+ -n|--dry-run) DRY_RUN=true ;;
+ -v|--verbose) VERBOSE=true ;;
+ -r|--restore) RESTORE=true ;;
+ -q|--quiet) QUIET=true ;;
+ -h|--help) usage ;;
+ *) echo -e "${RED}Unknown option: $arg${NC}"; usage ;;
+ esac
+done
+
+if [[ "$QUIET" == true ]]; then
+ mkdir -p "$(dirname "$LOG_FILE")"
+ # save original stdout so we can notify user after redirect
+ exec 3>&1
+ # strip color codes and write to log
+ exec > >(sed 's/\x1b\[[0-9;]*m//g' > "$LOG_FILE") 2>&1
+fi
# The list of dotfiles
DOTFILES=(
@@ -104,33 +148,137 @@ DOTFILES=(
"/etc/bash_completion.d"
)
+# Load external list if it exists, appending to built-in list
+if [[ -f "$DOTFILES_LIST" ]]; then
+ while IFS= read -r line || [[ -n "$line" ]]; do
+ [[ "$line" =~ ^#.*$ || -z "$line" ]] && continue
+ DOTFILES+=("$line")
+ done < "$DOTFILES_LIST"
+fi
+
# helper function to check if a string ($2) begins with another string ($1)
-beginswith() {
- case $2 in
+beginswith() {
+ case $2 in
"$1"*)
true
- ;;
+ ;;
*)
false
;;
esac;
}
-# clear the screen before we output anything
+lastupdate() {
+ D=$(date)
+ [ ! -f ${DEFAULT_OUTPUT_DIR}/lastupdate ] && touch ${DEFAULT_OUTPUT_DIR}/lastupdate
+ echo "Last update: $D" > ${DEFAULT_OUTPUT_DIR}/lastupdate
+}
+
+log_verbose() {
+ [[ "$VERBOSE" == true ]] && echo -e "$1"
+}
+
+do_rsync() {
+ local src="$1"
+ local dst="$2"
+ local is_dir="${3:-false}"
+ if [[ "$DRY_RUN" == true ]]; then
+ echo -e "\t ${YELLOW}[dry-run] would copy: $src → $dst${NC}"
+ else
+ if [[ "$is_dir" == true ]]; then
+ mkdir -p "$dst"
+ if [[ "$VERBOSE" == true ]]; then
+ rsync -av "$src/" "$dst/"
+ else
+ rsync -a "$src/" "$dst/" && echo -e "\t ${GREEN}Copied${NC}"
+ fi
+ else
+ if [[ "$VERBOSE" == true ]]; then
+ rsync -av "$src" "$dst"
+ else
+ rsync -a "$src" "$dst" && echo -e "\t ${GREEN}Copied${NC}"
+ fi
+ fi
+ fi
+}
+
+# ── RESTORE MODE ──────────────────────────────────────────────────────────────
+
+do_restore() {
+ if [[ ! -d "$DEFAULT_OUTPUT_DIR" ]]; then
+ echo -e "${RED}Backup directory not found: $DEFAULT_OUTPUT_DIR${NC}"
+ exit 1
+ fi
+
+ echo -e "${YELLOW}Restoring dotfiles from: ${BLUE}$DEFAULT_OUTPUT_DIR${NC}\n"
+
+ for i in "${DOTFILES[@]}"; do
+ if beginswith "/" "$i"; then
+ # system file
+ local backup_path="${DEFAULT_OUTPUT_DIR}/system/${i#/}"
+ local dest="$i"
+ else
+ local backup_path="${DEFAULT_OUTPUT_DIR}/home/$i"
+ local dest="${HOME}/$i"
+ fi
+
+ if [[ -f "$backup_path" ]]; then
+ echo -e "${GREEN}$dest${NC}"
+ if [[ "$DRY_RUN" == true ]]; then
+ echo -e "\t ${YELLOW}[dry-run] would restore: $backup_path → $dest${NC}"
+ else
+ mkdir -p "$(dirname "$dest")"
+ if [[ "$VERBOSE" == true ]]; then
+ rsync -av "$backup_path" "$dest"
+ else
+ rsync -a "$backup_path" "$dest" && echo -e "\t ${GREEN}Restored${NC}"
+ fi
+ fi
+ elif [[ -d "$backup_path" ]]; then
+ echo -e "${BLUE}$dest${NC}"
+ if [[ "$DRY_RUN" == true ]]; then
+ echo -e "\t ${YELLOW}[dry-run] would restore: $backup_path/ → $dest/${NC}"
+ else
+ mkdir -p "$dest"
+ if [[ "$VERBOSE" == true ]]; then
+ rsync -av "$backup_path/" "$dest/"
+ else
+ rsync -a "$backup_path/" "$dest/" && echo -e "\t ${GREEN}Restored${NC}"
+ fi
+ fi
+ else
+ log_verbose "\t ${YELLOW}SKIP (not in backup): $i${NC}"
+ fi
+ done
+
+ if [[ "$DRY_RUN" == true ]]; then
+ echo -e "\n${YELLOW}Dry run complete. Nothing restored.${NC}"
+ else
+ echo -e "\n${GREEN}Restore complete.${NC}"
+ fi
+ if [[ "$QUIET" == true ]]; then echo "Log written to: $LOG_FILE" >&3; fi
+ exit 0
+}
+
+# ── MAIN ──────────────────────────────────────────────────────────────────────
+
clear
+if [[ "$DRY_RUN" == true ]]; then
+ echo -e "${YELLOW}*** DRY RUN — no files will be copied ***${NC}\n"
+fi
+
+if [[ "$RESTORE" == true ]]; then
+ do_restore
+fi
+
echo -e "${YELLOW}Local Git Repository: ${NC}"
if [[ -d $DEFAULT_OUTPUT_DIR ]]; then
- echo -e "\t${BLUE}$DEFAULT_OUTPUT_DIR ${GREEN} ${NC}"
- # it exists, so we check for subdirectories
- # - home
- # - system
+ echo -e "\t${BLUE}$DEFAULT_OUTPUT_DIR ${GREEN} ${NC}"
[ -d $DEFAULT_OUTPUT_DIR/home ] || mkdir -p $DEFAULT_OUTPUT_DIR/home
[ -d $DEFAULT_OUTPUT_DIR/system ] || mkdir -p $DEFAULT_OUTPUT_DIR/system
- # check if we are in a git repository
cd $DEFAULT_OUTPUT_DIR
if [[ $(git rev-parse --is-inside-work-tree) == "true" ]]; then
- # we are inside a git repo
echo -e "${GREEN}Git Repository already initialized.${NC}"
else
echo -e "${YELLOW}Initializing Git Repository. Don't forget to add your remotes.${NC}"
@@ -138,55 +286,64 @@ if [[ -d $DEFAULT_OUTPUT_DIR ]]; then
git add .
fi
else
- echo -e "\t${BLUE}$DEFAULT_OUTPUT_DIR ${RED} ${NC}"
- # our backup directory doesn't exists, so we make it
- echo -e "${YELLOW}creating our backup directories${NC}"
- mkdir -p $DEFAULT_OUTPUT_DIR/{home,system}
- echo -e "${YELLOW}Initializing Git Repository. Don't forget to add your remotes.${NC}"
- cd $DEFAULT_OUTPUT_DIR
- git init
- git add .
+ echo -e "\t${BLUE}$DEFAULT_OUTPUT_DIR ${RED} ${NC}"
+ if [[ "$DRY_RUN" == false ]]; then
+ echo -e "${YELLOW}creating our backup directories${NC}"
+ mkdir -p $DEFAULT_OUTPUT_DIR/{home,system}
+ echo -e "${YELLOW}Initializing Git Repository. Don't forget to add your remotes.${NC}"
+ cd $DEFAULT_OUTPUT_DIR
+ git init
+ git add .
+ fi
fi
-# reset colors
+if [[ "$DRY_RUN" == false ]]; then
+ lastupdate
+fi
echo -e $NC
cd $WORKDIR
# we iterate all dotfiles in the list
-for i in ${DOTFILES[@]}; do
+for i in "${DOTFILES[@]}"; do
# if it's a file in my home
if [[ -f ${HOME}/$i ]]; then
- echo -e "${GREEN}${HOME}/$i\t  ${NC}"
- # it exists, we copy it to our output directory
- rsync -a ${HOME}/$i ${DEFAULT_OUTPUT_DIR}/home/ && echo -e "\t ${GREEN}Copied\t  ${NC}"
+ echo -e "${GREEN}${HOME}/$i${NC}"
+ do_rsync "${HOME}/$i" "${DEFAULT_OUTPUT_DIR}/home/$i"
# if it's a directory in my home
elif [[ -d ${HOME}/$i ]]; then
- echo -e "${BLUE}${HOME}/$i\t  ${NC}"
- # it exists, we copy it to our output directory
- rsync -a ${HOME}/$i ${DEFAULT_OUTPUT_DIR}/home/ && echo -e "\t ${GREEN}Copied\t  ${NC}"
- # if it begins with a / it's a system file/directory
+ echo -e "${BLUE}${HOME}/$i${NC}"
+ do_rsync "${HOME}/$i" "${DEFAULT_OUTPUT_DIR}/home/$i" true
+ # if it begins with a / it's a system file/directory
elif beginswith "/" $i; then
- # if it's a file
if [[ -f $i ]]; then
- echo -e "${GREEN}$i\t  ${NC}"
- # it exists, we copy it to our output directory
- rsync -a $i ${DEFAULT_OUTPUT_DIR}/system/ && echo -e "\t ${GREEN}Copied\t  ${NC}"
- # if it's a directory
+ echo -e "${GREEN}$i${NC}"
+ do_rsync "$i" "${DEFAULT_OUTPUT_DIR}/system/$i"
elif [[ -d $i ]]; then
- echo -e "${BLUE}$i\t  ${NC}"
- # it exists, we copy it to our output directory
- rsync -a $i ${DEFAULT_OUTPUT_DIR}/system/ && echo -e "\t ${GREEN}Copied\t  ${NC}"
+ echo -e "${BLUE}$i${NC}"
+ do_rsync "$i" "${DEFAULT_OUTPUT_DIR}/system/${i#/}" true
fi
- # if it doesn't exists
else
- echo -e "\n${RED}NOT FOUND: ${i}\t  ${NC}\n"
+ echo -e "\n${RED}NOT FOUND: ${i}${NC}\n"
fi
done
+if [[ "$DRY_RUN" == true ]]; then
+ echo -e "\n${YELLOW}Dry run complete. No files copied, no git changes made.${NC}"
+ exit 0
+fi
+
echo -e "\n${GREEN}Adding all copied files to git.${NC}"
cd $DEFAULT_OUTPUT_DIR
git add .
-# reset colors before exiting
+
+if git diff --cached --quiet; then
+ echo -e "${YELLOW}Nothing new to commit.${NC}"
+else
+ git commit -m "backup: $(date '+%Y-%m-%d %H:%M:%S')"
+ echo -e "${GREEN}Committed.${NC}"
+fi
+
echo -e $NC
cd $WORKDIR
+if [[ "$QUIET" == true ]]; then echo "Log written to: $LOG_FILE" >&3; fi
exit 0