Last active
January 6, 2024 00:58
-
-
Save superkeyor/7d238245a28c899f0149890860c7c341 to your computer and use it in GitHub Desktop.
bash script to maintan configuration files in macOS (inspired by mackup)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
cfgsync() { | |
# Usage: cfgsync {uplink|linkstore|backup|restore|reset} {directory_or_file} [-q|--quiet] | |
# cfgsync backup Clipboard # initialize backup, assuming config/ subfolder in backup | |
# cfgsync restore Clipboard/config/clipboard.cfg # restore to a new machine | |
# | |
# uplink: (backup first + link second) Backs up the original file/folder, according to .cfg, by copying it to a backup location and then creates a symbolic link to the backup in the original place. | |
# linkstore: (link to an existing backup to restore) Restores the original file by removing it (if it exists) and creating a symbolic link to the backup in the original place. | |
# backup: (backup, no link) Backs up the original file/folder, according to .cfg, by copying it to a backup location. No symbolic link is created. | |
# restore: (restore, no link) Similar to linkstore, but instead of creating a symbolic link, it copies the backup back to the original location. | |
# reset: Deletes the original file or symbolic link, with the hope that the application will recreate a default configuration file if necessary. | |
# -q or --quiet to skip confirmation | |
# | |
# cfg format (non-existing file will be skipped): | |
# https://github.com/lra/mackup/tree/master/mackup/applications (this function hard-codingly converts [xdg_configuration_files] to .config) | |
# [application] | |
# name = Bartender | |
# | |
# [configuration_files] | |
# # assuming $HOME/ prefix | |
# Library/Preferences/com.surteesstudios.Bartender.plist | |
# Library/Preferences/com.surteesstudios.Bartender-setapp.plist | |
# Library/Application Support/Bartender/Bartender.BartenderPreferences | |
# # if path starts with /, then no prefix will be added, use it as is | |
# /Library/Application Support | |
# | |
# [xdg_configuration_files] | |
# # assuming $HOME/.config/ prefix | |
# karabiner | |
local quiet=$3 | |
prompt_for_confirmation() { | |
local message=$1 | |
if [[ "$quiet" == "-q" || "$quiet" == "--quiet" ]]; then | |
echo "$message ([y]/n):" # Optionally display the message | |
return 0 # Automatically confirm | |
fi | |
read -p "$message ([y]/n): " response | |
response=${response:-y} | |
if [[ $response == [Nn]* ]]; then | |
echo "User cancelled." | |
return 1 | |
fi | |
return 0 | |
} | |
local mode=$1 | |
local input_path=$2 | |
local cfg_file="" # cfg_file in input_path | |
local cfg_dir="" # cfg_dir containing cfg_file | |
local plist_files=() # plist_files extracted from cfg_file | |
local line # loop cfg_file | |
local plist_original # loop plist_files | |
local plist_backup # backup plist_files saved in cfg_dir | |
# Determine whether the input is a directory or a specific file | |
if [ -d "$input_path" ]; then | |
# Remove trailing slash if it exists for input path | |
input_path="${input_path%/}" | |
cfg_dir="$(realpath ${input_path}/config)" | |
# Find the first .cfg file in the directory | |
cfg_file="$(find "$cfg_dir" -maxdepth 1 -type f -name '*.cfg' | head -n 1)" | |
if [ -z "$cfg_file" ]; then | |
echo "No .cfg file found in directory $cfg_dir." | |
return 1 | |
fi | |
elif [ -f "$input_path" ]; then | |
cfg_file="$(realpath $input_path)" | |
cfg_dir="$(realpath $(dirname "$cfg_file"))" | |
else | |
# echo "Invalid input. Please provide a directory or a .cfg file." | |
echo "Usage: cfgsync {uplink|linkstore|backup|restore|reset} {directory_or_file}" | |
return 1 | |
fi | |
mode_title_case=$(echo "$mode" | awk '{print toupper(substr($0,1,1)) tolower(substr($0,2))}') | |
prompt_for_confirmation "Found $cfg_file. $mode_title_case?" | |
# Parse the configuration file to extract file paths | |
parseCfg() { | |
local in_config_section=false | |
local in_xdg_section=false | |
while IFS= read -r line || [[ -n "$line" ]]; do | |
# Check if the current line is a section header | |
if [[ $line =~ ^\[.*\]$ ]]; then | |
if [[ $line == "[configuration_files]" ]]; then | |
in_config_section=true | |
else | |
in_config_section=false | |
fi | |
if [[ $line == "[xdg_configuration_files]" ]]; then | |
in_xdg_section=true | |
else | |
in_xdg_section=false | |
fi | |
continue | |
fi | |
# If we are in the configuration_files section, add non-empty and non-comment lines to the array | |
if [[ $in_config_section == true && -n $line && ! $line =~ ^[[:space:]]*# ]]; then | |
# /Library/Application Support/ | |
if [[ $line == /* ]]; then | |
plist_files=("${plist_files[@]}" "${line}") | |
else | |
plist_files=("${plist_files[@]}" "$HOME/${line}") | |
fi | |
fi | |
if [[ $in_xdg_section == true && -n $line && ! $line =~ ^[[:space:]]*# ]]; then | |
plist_files=("${plist_files[@]}" "$HOME/.config/${line}") | |
fi | |
done < "$cfg_file" | |
} | |
parseCfg | |
# Apply operations on the extracted file paths | |
for plist_original in "${plist_files[@]}"; do | |
plist_backup="$cfg_dir/$(basename "$plist_original")" | |
case $mode in | |
uplink) | |
# skip if it has already been a soft link | |
if [ -L "$plist_original" ]; then | |
local linked_file=$(readlink "$plist_original") | |
if [ "$linked_file" = "$plist_backup" ]; then | |
echo "$plist_original is already a symbolic link to $plist_backup. No need to relink." | |
else | |
# red | |
echo -e "\033[31mCan not uplink! $plist_original is a symbolic link, but not pointing to $plist_backup.\033[0m" | |
fi | |
continue | |
fi | |
# .cfg may contain unnecessary files | |
if [ ! -e "$plist_original" ]; then | |
# grey | |
echo -e "\033[90mIgnored: $plist_original does not exist.\033[0m" | |
continue | |
fi | |
# remove previous backup, esp. needed if backup is a folder, in order for cp to work properly | |
rm -rf "$plist_backup" | |
# now we do cp, rm, ln | |
cp -r "$plist_original" "$plist_backup" | |
rm -r "$plist_original" | |
if [ -d "$plist_backup" ]; then | |
ln -sf "$plist_backup" "$(dirname "$plist_original")" | |
else | |
ln -sf "$plist_backup" "$plist_original" | |
fi | |
echo "Symbolic link created for backup of $plist_original." | |
;; | |
backup) | |
# skip if it is a soft link | |
if [ -L "$plist_original" ]; then | |
# red | |
echo -e "\033[31mCan not backup! $plist_original is just a symbolic link.\033[0m" | |
continue | |
fi | |
# .cfg may contain unnecessary files | |
if [ ! -e "$plist_original" ]; then | |
# grey | |
echo -e "\033[90mIgnored: $plist_original does not exist.\033[0m" | |
continue | |
fi | |
# remove previous backup, esp. needed if backup is a folder, in order for cp to work properly | |
rm -rf "$plist_backup" | |
# now we do cp | |
cp -r "$plist_original" "$plist_backup" | |
echo "Backup created for $plist_original." | |
;; | |
linkstore) | |
# Again, .cfg may contain unnecessary files (if no backup, no restore) | |
if [ ! -e "$plist_backup" ]; then | |
echo -e "\033[90mIgnored: $plist_backup does not exist.\033[0m" | |
continue | |
fi | |
# remove original file (could be a soft link) | |
rm -rf "$plist_original" | |
# create necessary parent folder if not there | |
mkdir -p "$(dirname "$plist_original")" | |
# at this point, plist_original should not exist, ln below would work properly esp. when it is a folder | |
if [ -d "$plist_backup" ]; then | |
ln -sf "$plist_backup" "$(dirname "$plist_original")" | |
else | |
ln -sf "$plist_backup" "$plist_original" | |
fi | |
echo "Configuration linkstored from backup for $plist_original." | |
;; | |
restore) | |
# restore is similar to linkstore, except for the last part | |
# Again, .cfg may contain unnecessary files | |
if [ ! -e "$plist_backup" ]; then | |
echo -e "\033[90mIgnored: $plist_backup does not exist.\033[0m" | |
continue | |
fi | |
# remove original file (could be a soft link) | |
rm -rf "$plist_original" | |
# create necessary parent folder if not there | |
mkdir -p "$(dirname "$plist_original")" | |
# the above are the same as restore | |
cp -r "$plist_backup" "$plist_original" | |
echo "Backup restored for $plist_original." | |
;; | |
reset) | |
rm -rf "$plist_original" | |
echo "Configuration reset for $plist_original." | |
# hopefully a default config file will be auto recreated by the app | |
# in case not, I have backups, hopefully already, in the .cfg folder | |
;; | |
*) | |
echo "Usage: cfgsync {uplink|linkstore|backup|restore|reset} {directory_or_file}" | |
return 1 | |
;; | |
esac | |
done | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment