Files
System-Sync-Restore/bin/ssr-mackup
T
Oliver Surke 61091aa6bf refactor: ssr-*.cfg files are user-owned after creation
ssr mackup scan now only suggests NEW files — it never overwrites an
existing ssr-*.cfg. Once created, the file belongs to the user who
edits it directly in ~/.mackup/ to customise which paths are backed up.

- _ssr_mackup_write_cfg: returns early if file already exists
- scan: drops UPD logic; only shows NEW suggestions
- _ssr_mackup_resolve_paths: removed SSR_MACKUP_SELECTIVE_PATHS support
  (ssr.conf config variable removed — edit the cfg files instead)
- Scan output: hints to edit files directly after creation

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-12 15:06:43 +02:00

219 lines
7.5 KiB
Bash
Executable File

#!/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
# Only suggest items that have NO cfg file yet.
# Existing files are user-owned — edit them directly in ~/.mackup/.
local -a to_write=()
local -a cfg_paths=()
local it app_name cfg_file
for it in "${items[@]:-}"; do
app_name="$(_ssr_mackup_cfg_name "$it")"
cfg_file="$cfg_dir/${app_name}.cfg"
[[ -f "$cfg_file" ]] && continue # user owns it — skip
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 path="${cfg_paths[$i]##*/}"
# Show resolved paths (selective default or whole dir).
local resolved; resolved="$(_ssr_mackup_resolve_paths "${to_write[$i]}")"
local line_count; line_count="$(printf '%s\n' "$resolved" | wc -l | tr -d ' ')"
local paths_hint
if [[ "$line_count" -eq 1 && "$resolved" == "${to_write[$i]}" ]]; then
paths_hint="${to_write[$i]}/"
else
paths_hint="$line_count sub-path(s) of ${to_write[$i]}/"
fi
printf " \033[32mNEW\033[0m %-38s %s\n" \
"~/.mackup/$path" "$paths_hint"
done
printf '\n\033[2mEdit the generated files directly in ~/.mackup/ to customise paths.\033[0m\n'
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 files (never overwrites existing ones).
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 '\nEdit ~/.mackup/ssr-*.cfg directly to customise paths.\n'
printf 'Run \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