feat: ssr mackup scan|list|clean — on-demand cfg generation with confirmation
Instead of silently writing ~/.mackup/ssr-*.cfg on every sync, generation is now an explicit opt-in command: ssr mackup scan ssr mackup scan — dry-run: shows NEW/UPD/DEL changes, prompts before writing ssr mackup list — list existing generated files ssr mackup clean — remove all generated files (with confirmation) ssr sync no longer calls generate_custom_cfg automatically. Completions updated for zsh, bash, fish. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -34,6 +34,7 @@ Commands:
|
|||||||
config show|edit Print or edit the active config file.
|
config show|edit Print or edit the active config file.
|
||||||
completions Install or remove shell completions (zsh/bash/fish).
|
completions Install or remove shell completions (zsh/bash/fish).
|
||||||
status Show last sync time, cloud target, and scheduled state.
|
status Show last sync time, cloud target, and scheduled state.
|
||||||
|
mackup scan|list|clean Manage auto-generated Mackup app definitions.
|
||||||
version Print version.
|
version Print version.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
@@ -62,7 +63,7 @@ done
|
|||||||
|
|
||||||
cmd="$1"; shift
|
cmd="$1"; shift
|
||||||
case "$cmd" in
|
case "$cmd" in
|
||||||
install|sync|restore|update|schedule|config|completions|status)
|
install|sync|restore|update|schedule|config|completions|status|mackup)
|
||||||
target="$SSR_BIN_DIR/ssr-$cmd"
|
target="$SSR_BIN_DIR/ssr-$cmd"
|
||||||
[[ -x "$target" ]] || ssr::die "Subcommand not executable: $target"
|
[[ -x "$target" ]] || ssr::die "Subcommand not executable: $target"
|
||||||
exec "$target" "$@"
|
exec "$target" "$@"
|
||||||
|
|||||||
Executable
+219
@@ -0,0 +1,219 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# ssr-mackup — Manage auto-generated Mackup app definitions for dot-dirs.
|
||||||
|
#
|
||||||
|
# Subcommands:
|
||||||
|
# scan Dry-run: show which ssr-*.cfg files would be created / updated,
|
||||||
|
# then ask for confirmation before writing anything.
|
||||||
|
# list List existing ~/.mackup/ssr-*.cfg files with the path they cover.
|
||||||
|
# clean Remove all generated ssr-*.cfg files (prompts first).
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
SSR_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
# shellcheck source=../lib/common.sh
|
||||||
|
source "$SSR_ROOT/lib/common.sh"
|
||||||
|
# shellcheck source=../lib/cloud.sh
|
||||||
|
source "$SSR_ROOT/lib/cloud.sh"
|
||||||
|
# shellcheck source=../lib/mackup.sh
|
||||||
|
source "$SSR_ROOT/lib/mackup.sh"
|
||||||
|
|
||||||
|
ssr::require_macos
|
||||||
|
ssr::load_config
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<EOF
|
||||||
|
Usage: ssr mackup <subcommand>
|
||||||
|
|
||||||
|
Manage auto-generated Mackup app definitions for dot-dirs not covered
|
||||||
|
by Mackup's built-in app definitions.
|
||||||
|
|
||||||
|
Subcommands:
|
||||||
|
scan Show suggested ~/.mackup/ssr-*.cfg files and confirm before writing.
|
||||||
|
list List existing generated files.
|
||||||
|
clean Remove all generated ssr-*.cfg files.
|
||||||
|
|
||||||
|
Run 'ssr mackup scan' once after install (or whenever new dot-dirs appear)
|
||||||
|
and commit the generated files alongside your dotfiles.
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# scan — dry-run then confirm
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
cmd_scan() {
|
||||||
|
local mackup_dir
|
||||||
|
mackup_dir="$(ssr::cloud::mackup_dir 2>/dev/null)" \
|
||||||
|
|| mackup_dir="" # allow offline use; _in_backup will just never match
|
||||||
|
|
||||||
|
local cfg_dir="$HOME/.mackup"
|
||||||
|
mkdir -p "$cfg_dir"
|
||||||
|
|
||||||
|
local skip_set=" ${_SSR_MACKUP_SKIP[*]} "
|
||||||
|
|
||||||
|
_in_backup() { [[ -n "$mackup_dir" && -e "$mackup_dir/$1" ]]; }
|
||||||
|
_is_declared() { printf '%s\n' "$declared" | grep -qxF "$1"; }
|
||||||
|
|
||||||
|
local declared
|
||||||
|
declared="$(_ssr_mackup_declared_paths)"
|
||||||
|
|
||||||
|
# Collect candidate items (same logic as generate_custom_cfg).
|
||||||
|
local -a items=()
|
||||||
|
local item
|
||||||
|
for item in "${_SSR_MACKUP_CURATED[@]}" ${SSR_MACKUP_EXTRA_PATHS:-}; do
|
||||||
|
[[ -e "$HOME/$item" ]] || continue
|
||||||
|
_is_declared "$item" && continue
|
||||||
|
_in_backup "$item" && continue
|
||||||
|
items+=("$item")
|
||||||
|
done
|
||||||
|
local name
|
||||||
|
while IFS= read -r dotdir; do
|
||||||
|
name="${dotdir##*/}"
|
||||||
|
[[ "$skip_set" == *" $name "* ]] && continue
|
||||||
|
_is_declared "$name" && continue
|
||||||
|
_in_backup "$name" && continue
|
||||||
|
local dup=false
|
||||||
|
for item in "${items[@]:-}"; do [[ "$item" == "$name" ]] && dup=true && break; done
|
||||||
|
$dup && continue
|
||||||
|
items+=("$name")
|
||||||
|
done < <(find "$HOME" -maxdepth 1 -mindepth 1 -name '.*' -type d 2>/dev/null | sort)
|
||||||
|
|
||||||
|
# Check existing stale duplicates.
|
||||||
|
local -a to_remove=()
|
||||||
|
local stale_cfg stale_path
|
||||||
|
for stale_cfg in "$cfg_dir"/ssr-*.cfg; do
|
||||||
|
[[ -f "$stale_cfg" ]] || continue
|
||||||
|
stale_path="$(awk '/^\[configuration_files\]/{f=1;next}/^\[/{f=0}f&&/^[^#[:space:]]/{print $1;exit}' "$stale_cfg")"
|
||||||
|
_is_declared "$stale_path" && to_remove+=("$stale_cfg")
|
||||||
|
done
|
||||||
|
|
||||||
|
# Classify each candidate: NEW or UPD (content differs from existing).
|
||||||
|
local -a to_write=()
|
||||||
|
local -a labels=()
|
||||||
|
local -a cfg_paths=()
|
||||||
|
|
||||||
|
local it app_name desired existing cfg_file
|
||||||
|
for it in "${items[@]:-}"; do
|
||||||
|
app_name="$(_ssr_mackup_cfg_name "$it")"
|
||||||
|
cfg_file="$cfg_dir/${app_name}.cfg"
|
||||||
|
desired="$(printf '[application]\nname = %s\n\n[configuration_files]\n%s\n' "$app_name" "$it")"
|
||||||
|
if [[ -f "$cfg_file" ]]; then
|
||||||
|
existing="$(cat "$cfg_file")"
|
||||||
|
if [[ "$existing" == "$desired" ]]; then
|
||||||
|
continue # already up-to-date, skip
|
||||||
|
fi
|
||||||
|
labels+=("UPD")
|
||||||
|
else
|
||||||
|
labels+=("NEW")
|
||||||
|
fi
|
||||||
|
to_write+=("$it")
|
||||||
|
cfg_paths+=("$cfg_file")
|
||||||
|
done
|
||||||
|
|
||||||
|
# Nothing to do?
|
||||||
|
if [[ ${#to_write[@]} -eq 0 && ${#to_remove[@]} -eq 0 ]]; then
|
||||||
|
ssr::ok "All ~/.mackup/ssr-*.cfg files are up-to-date — nothing to do."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Show plan.
|
||||||
|
if [[ ${#to_write[@]} -gt 0 ]]; then
|
||||||
|
printf '\nSuggested Mackup app definitions:\n'
|
||||||
|
local i
|
||||||
|
for i in "${!to_write[@]}"; do
|
||||||
|
local label="${labels[$i]}"
|
||||||
|
local path="${cfg_paths[$i]##*/}"
|
||||||
|
local colour
|
||||||
|
[[ "$label" == "NEW" ]] && colour="\033[32m" || colour="\033[33m"
|
||||||
|
printf " ${colour}%s\033[0m %-38s (%s)\n" \
|
||||||
|
"$label" "~/.mackup/$path" "${to_write[$i]}"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ${#to_remove[@]} -gt 0 ]]; then
|
||||||
|
printf '\nStale duplicates to remove (now covered by built-in):\n'
|
||||||
|
local f
|
||||||
|
for f in "${to_remove[@]}"; do
|
||||||
|
printf " \033[31mDEL\033[0m %s\n" "~/.mackup/${f##*/}"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
printf '\n'
|
||||||
|
|
||||||
|
# Confirm.
|
||||||
|
local total=$(( ${#to_write[@]} + ${#to_remove[@]} ))
|
||||||
|
ssr::confirm "Apply $total change(s)?" 60 || { printf 'Aborted.\n'; return 0; }
|
||||||
|
|
||||||
|
# Write new/updated files.
|
||||||
|
for it in "${to_write[@]:-}"; do
|
||||||
|
_ssr_mackup_write_cfg "$cfg_dir" "$it"
|
||||||
|
done
|
||||||
|
|
||||||
|
# Remove stale duplicates.
|
||||||
|
for f in "${to_remove[@]:-}"; do
|
||||||
|
rm -f "$f"
|
||||||
|
ssr::log "Removed stale: ${f##*/}"
|
||||||
|
done
|
||||||
|
|
||||||
|
ssr::ok "Done — ${#to_write[@]} written, ${#to_remove[@]} removed."
|
||||||
|
printf '\nRun \033[1mssr sync\033[0m to back up with the new definitions.\n\n'
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# list — show existing generated files
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
cmd_list() {
|
||||||
|
local cfg_dir="$HOME/.mackup"
|
||||||
|
local found=0 cfg path
|
||||||
|
|
||||||
|
for cfg in "$cfg_dir"/ssr-*.cfg; do
|
||||||
|
[[ -f "$cfg" ]] || continue
|
||||||
|
path="$(awk '/^\[configuration_files\]/{f=1;next}/^\[/{f=0}f&&/^[^#[:space:]]/{print $1;exit}' "$cfg")"
|
||||||
|
printf ' %-42s %s\n' "~/.mackup/${cfg##*/}" "$path"
|
||||||
|
found=$((found + 1))
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ $found -eq 0 ]]; then
|
||||||
|
printf 'No generated ssr-*.cfg files found in ~/.mackup/\n'
|
||||||
|
printf 'Run: ssr mackup scan\n'
|
||||||
|
else
|
||||||
|
printf '\n%d file(s) total.\n' "$found"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# clean — remove all generated files
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
cmd_clean() {
|
||||||
|
local cfg_dir="$HOME/.mackup"
|
||||||
|
local -a found=()
|
||||||
|
local cfg
|
||||||
|
for cfg in "$cfg_dir"/ssr-*.cfg; do
|
||||||
|
[[ -f "$cfg" ]] && found+=("$cfg")
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ ${#found[@]} -eq 0 ]]; then
|
||||||
|
ssr::ok "No generated ssr-*.cfg files to remove."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf 'Will remove %d file(s):\n' "${#found[@]}"
|
||||||
|
for cfg in "${found[@]}"; do
|
||||||
|
printf ' %s\n' "~/.mackup/${cfg##*/}"
|
||||||
|
done
|
||||||
|
printf '\n'
|
||||||
|
|
||||||
|
ssr::confirm "Remove all generated ssr-*.cfg files?" 60 || { printf 'Aborted.\n'; return 0; }
|
||||||
|
for cfg in "${found[@]}"; do rm -f "$cfg"; done
|
||||||
|
ssr::ok "Removed ${#found[@]} file(s)."
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Dispatch
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
[[ $# -gt 0 ]] || { usage; exit 1; }
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
scan) cmd_scan ;;
|
||||||
|
list) cmd_list ;;
|
||||||
|
clean) cmd_clean ;;
|
||||||
|
-h|--help) usage ;;
|
||||||
|
*) ssr::err "Unknown subcommand: $1"; usage; exit 1 ;;
|
||||||
|
esac
|
||||||
@@ -48,12 +48,6 @@ ssr::brew::upgrade
|
|||||||
# Timeout (seconds) is configurable; default 180s.
|
# Timeout (seconds) is configurable; default 180s.
|
||||||
ssr::icloud::wait "$mackup_dir" "${SSR_ICLOUD_WAIT_TIMEOUT:-180}"
|
ssr::icloud::wait "$mackup_dir" "${SSR_ICLOUD_WAIT_TIMEOUT:-180}"
|
||||||
|
|
||||||
# Auto-generate ~/.mackup/ssr-custom.cfg for dot-dirs not yet covered by
|
|
||||||
# Mackup's built-in definitions (e.g. .claude, .cursor, .easymcp, …).
|
|
||||||
if [[ "${SSR_MACKUP_AUTODISCOVER:-true}" == "true" ]]; then
|
|
||||||
ssr::mackup::generate_custom_cfg "$mackup_dir"
|
|
||||||
fi
|
|
||||||
|
|
||||||
ssr::mackup::backup
|
ssr::mackup::backup
|
||||||
ssr::brew::dump "$backup_dir"
|
ssr::brew::dump "$backup_dir"
|
||||||
ssr::brew::cleanup
|
ssr::brew::cleanup
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ _ssr() {
|
|||||||
'config:Show or edit the active config file'
|
'config:Show or edit the active config file'
|
||||||
'completions:Install or remove shell completions'
|
'completions:Install or remove shell completions'
|
||||||
'status:Show last sync time, cloud target, schedule state'
|
'status:Show last sync time, cloud target, schedule state'
|
||||||
|
'mackup:Manage auto-generated Mackup app definitions'
|
||||||
'version:Print version'
|
'version:Print version'
|
||||||
'help:Show top-level help'
|
'help:Show top-level help'
|
||||||
)
|
)
|
||||||
@@ -59,6 +60,12 @@ _ssr() {
|
|||||||
'uninstall[Remove shell completions]' \
|
'uninstall[Remove shell completions]' \
|
||||||
'status[Show installed completions]'
|
'status[Show installed completions]'
|
||||||
;;
|
;;
|
||||||
|
mackup)
|
||||||
|
_values 'mackup action' \
|
||||||
|
'scan[Suggest and confirm ssr-*.cfg files]' \
|
||||||
|
'list[List generated ssr-*.cfg files]' \
|
||||||
|
'clean[Remove all generated ssr-*.cfg files]'
|
||||||
|
;;
|
||||||
esac
|
esac
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ _ssr() {
|
|||||||
cword=$COMP_CWORD
|
cword=$COMP_CWORD
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local commands="install sync restore update schedule config completions status version help"
|
local commands="install sync restore update schedule config completions status mackup version help"
|
||||||
local global_flags="-c --config -h --help"
|
local global_flags="-c --config -h --help"
|
||||||
|
|
||||||
# Detect command (first non-flag word after `ssr`).
|
# Detect command (first non-flag word after `ssr`).
|
||||||
@@ -60,6 +60,9 @@ _ssr() {
|
|||||||
completions)
|
completions)
|
||||||
COMPREPLY=( $(compgen -W "install uninstall status" -- "$cur") )
|
COMPREPLY=( $(compgen -W "install uninstall status" -- "$cur") )
|
||||||
;;
|
;;
|
||||||
|
mackup)
|
||||||
|
COMPREPLY=( $(compgen -W "scan list clean" -- "$cur") )
|
||||||
|
;;
|
||||||
*)
|
*)
|
||||||
COMPREPLY=()
|
COMPREPLY=()
|
||||||
;;
|
;;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# fish completion for ssr — System Sync & Restore.
|
# fish completion for ssr — System Sync & Restore.
|
||||||
# Install: drop into ~/.config/fish/completions/ssr.fish
|
# Install: drop into ~/.config/fish/completions/ssr.fish
|
||||||
|
|
||||||
set -l ssr_cmds install sync restore update schedule config completions status version help
|
set -l ssr_cmds install sync restore update schedule config completions status mackup version help
|
||||||
|
|
||||||
# True when no subcommand has been picked yet (only flags / option-args seen so far).
|
# True when no subcommand has been picked yet (only flags / option-args seen so far).
|
||||||
function __ssr_needs_subcmd
|
function __ssr_needs_subcmd
|
||||||
@@ -69,3 +69,5 @@ complete -c ssr -n '__ssr_using_subcmd update' -s n -l dry-run -d 'Preview with
|
|||||||
complete -c ssr -n '__ssr_using_subcmd schedule' -a 'on off status'
|
complete -c ssr -n '__ssr_using_subcmd schedule' -a 'on off status'
|
||||||
complete -c ssr -n '__ssr_using_subcmd config' -a 'show edit path'
|
complete -c ssr -n '__ssr_using_subcmd config' -a 'show edit path'
|
||||||
complete -c ssr -n '__ssr_using_subcmd completions' -a 'install uninstall status'
|
complete -c ssr -n '__ssr_using_subcmd completions' -a 'install uninstall status'
|
||||||
|
complete -c ssr -n __ssr_needs_subcmd -a mackup -d 'Manage Mackup app definitions'
|
||||||
|
complete -c ssr -n '__ssr_using_subcmd mackup' -a 'scan list clean'
|
||||||
|
|||||||
Reference in New Issue
Block a user