fix: skip dot-paths already declared in Mackup built-in app definitions

Previously ssr-custom cfg generation only checked whether the path was
already in the cloud backup directory.  It did not consult Mackup's
~400 built-in app definitions, causing duplicates like ssr-claude.cfg
when Mackup already has a 'claude-code' entry covering .claude.

Changes:
- _ssr_mackup_declared_paths(): parses all built-in *.cfg files from
  Mackup's Python package apps/ dir + hand-written ~/.mackup/*.cfg
  (excluding our own ssr-*.cfg) into a sorted path set
- generate_custom_cfg now skips any path already in that set
- Stale ssr-*.cfg files that now duplicate a built-in are removed
  automatically on the next sync run

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-05-12 11:49:04 +02:00
parent b50ab61d5c
commit d849400f63
+68 -8
View File
@@ -71,11 +71,44 @@ _ssr_mackup_write_cfg() {
printf '%s\n' "$desired" > "$cfg_file" printf '%s\n' "$desired" > "$cfg_file"
} }
# Build a newline-separated list of all paths already declared in Mackup's
# built-in app definitions AND user-defined ~/.mackup/*.cfg files.
# Our own generated ssr-*.cfg files are excluded to avoid circular checks.
_ssr_mackup_declared_paths() {
local apps_dir=""
# Locate Mackup's built-in apps/ directory via Python.
if command -v python3 >/dev/null 2>&1; then
apps_dir="$(python3 -c \
'import os, mackup; print(os.path.join(os.path.dirname(mackup.__file__), "apps"))' \
2>/dev/null || true)"
fi
# Also include hand-written user definitions (not our generated ssr-*.cfg).
local user_cfg_dir="$HOME/.mackup"
{
[[ -d "$apps_dir" ]] \
&& grep -rh '' "$apps_dir"/*.cfg 2>/dev/null || true
[[ -d "$user_cfg_dir" ]] \
&& grep -rhl '' "$user_cfg_dir"/*.cfg 2>/dev/null \
| grep -v '/ssr-' \
| xargs grep -h '' 2>/dev/null || true
} | awk '
/^\[configuration_files\]/ { in_section=1; next }
/^\[/ { in_section=0 }
in_section && /^[^#[:space:]]/ { print $1 }
' | sort -u
}
# Generate one ~/.mackup/ssr-<name>.cfg per uncovered dot-dir/file. # Generate one ~/.mackup/ssr-<name>.cfg per uncovered dot-dir/file.
# 1. Curated entries that exist in $HOME and are not yet in the Mackup backup. # 1. Curated entries that exist in $HOME and are not yet covered.
# 2. Auto-discovered dot-dirs in $HOME not in the skip list and not in backup. # 2. Auto-discovered dot-dirs in $HOME not in the skip list and not covered.
# #
# Removes the old combined ssr-custom.cfg if found (migration from old format). # "Covered" means: already declared in a built-in or hand-written Mackup app
# definition OR already materialised in the Mackup backup directory.
#
# Removes the old combined ssr-custom.cfg if found (migration).
# Each file is picked up automatically by `mackup backup/restore`. # Each file is picked up automatically by `mackup backup/restore`.
ssr::mackup::generate_custom_cfg() { ssr::mackup::generate_custom_cfg() {
local mackup_dir="$1" local mackup_dir="$1"
@@ -85,8 +118,34 @@ ssr::mackup::generate_custom_cfg() {
# Remove legacy combined file if present. # Remove legacy combined file if present.
[[ -f "$cfg_dir/ssr-custom.cfg" ]] && rm -f "$cfg_dir/ssr-custom.cfg" [[ -f "$cfg_dir/ssr-custom.cfg" ]] && rm -f "$cfg_dir/ssr-custom.cfg"
# Build declared-paths lookup early so we can also purge stale ssr-*.cfg
# files that now duplicate a built-in definition (e.g. ssr-claude.cfg after
# Mackup added a built-in "claude-code" app).
local declared
declared="$(_ssr_mackup_declared_paths)"
local stale_cfg stale_path
for stale_cfg in "$cfg_dir"/ssr-*.cfg; do
[[ -f "$stale_cfg" ]] || continue
# Extract the single path from [configuration_files].
stale_path="$(awk '/^\[configuration_files\]/{f=1;next}/^\[/{f=0}f&&/^[^#[:space:]]/{print $1;exit}' "$stale_cfg")"
if printf '%s\n' "$declared" | grep -qxF "$stale_path"; then
ssr::log "Removing stale duplicate: ${stale_cfg##*/} ($stale_path now covered by built-in)"
rm -f "$stale_cfg"
fi
done
local skip_set=" ${_SSR_MACKUP_SKIP[*]} " local skip_set=" ${_SSR_MACKUP_SKIP[*]} "
# Build declared-paths lookup (newline-separated, sorted).
local declared
declared="$(_ssr_mackup_declared_paths)"
# Return true when <path> is already declared in any existing app definition.
_is_declared() {
printf '%s\n' "$declared" | grep -qxF "$1"
}
# Return true when <path> is already present in the Mackup cloud backup.
_in_backup() { [[ -e "$mackup_dir/$1" ]]; } _in_backup() { [[ -e "$mackup_dir/$1" ]]; }
local -a items=() local -a items=()
@@ -95,6 +154,7 @@ ssr::mackup::generate_custom_cfg() {
local item local item
for item in "${_SSR_MACKUP_CURATED[@]}" ${SSR_MACKUP_EXTRA_PATHS:-}; do for item in "${_SSR_MACKUP_CURATED[@]}" ${SSR_MACKUP_EXTRA_PATHS:-}; do
[[ -e "$HOME/$item" ]] || continue [[ -e "$HOME/$item" ]] || continue
_is_declared "$item" && continue
_in_backup "$item" && continue _in_backup "$item" && continue
items+=("$item") items+=("$item")
done done
@@ -104,7 +164,8 @@ ssr::mackup::generate_custom_cfg() {
while IFS= read -r dotdir; do while IFS= read -r dotdir; do
name="${dotdir##*/}" name="${dotdir##*/}"
[[ "$skip_set" == *" $name "* ]] && continue [[ "$skip_set" == *" $name "* ]] && continue
_in_backup "$name" && continue _is_declared "$name" && continue
_in_backup "$name" && continue
local dup=false local dup=false
for item in "${items[@]:-}"; do [[ "$item" == "$name" ]] && dup=true && break; done for item in "${items[@]:-}"; do [[ "$item" == "$name" ]] && dup=true && break; done
$dup && continue $dup && continue
@@ -118,12 +179,11 @@ ssr::mackup::generate_custom_cfg() {
local written=0 it local written=0 it
for it in "${items[@]}"; do for it in "${items[@]}"; do
if _ssr_mackup_write_cfg "$cfg_dir" "$it"; then _ssr_mackup_write_cfg "$cfg_dir" "$it"
written=$((written + 1)) written=$((written + 1))
fi
done done
ssr::ok "~/.mackup/ssr-*.cfg: ${#items[@]} app definitions written (${written} new/updated)" ssr::ok "~/.mackup/ssr-*.cfg: ${written} definition(s) written"
ssr::log "${items[*]}" ssr::log "${items[*]}"
} }