fix(mackup): correct built-in coverage detection and per-item scan confirm
_ssr_mackup_declared_paths: - Fix wrong subdir: apps/ → applications/ (coverage was completely broken) - Use Mackup's bundled Python interpreter (not system python3) - Track xdg_configuration_files sections separately and prefix paths with .config/ so paths like gh/config.yml → .config/gh/config.yml are properly recognized as covering .config/gh _is_declared: - Use `command grep` to bypass colour aliases - Replace grep -E regex (dot-escaping issues) with bash glob: == "$p/"* - Applied to both lib/mackup.sh and bin/ssr-mackup _SSR_MACKUP_CURATED: - Remove entries now correctly detected as built-in covered: .claude (claude-code.cfg), .codex, .docker, .config/gh (github-cli xdg) - Keep .cursor (built-in covers Library/Application Support, not .cursor/) _SSR_MACKUP_SELECTIVE: - Remove entries for removed curated items ssr mackup scan: - Confirm each new item individually (y/n per candidate) - Confirm each stale removal individually - Show resolved sub-paths per item before asking Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+102
-61
@@ -37,7 +37,7 @@ EOF
|
|||||||
}
|
}
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# scan — dry-run then confirm
|
# scan — show each candidate and confirm per-item
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
cmd_scan() {
|
cmd_scan() {
|
||||||
local mackup_dir
|
local mackup_dir
|
||||||
@@ -49,13 +49,27 @@ cmd_scan() {
|
|||||||
|
|
||||||
local skip_set=" ${_SSR_MACKUP_SKIP[*]} "
|
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
|
local declared
|
||||||
declared="$(_ssr_mackup_declared_paths)"
|
declared="$(_ssr_mackup_declared_paths)"
|
||||||
|
|
||||||
# Collect candidate items (same logic as generate_custom_cfg).
|
# Returns true when <path> is declared exactly OR when any built-in declares
|
||||||
|
# a sub-path of <path> (e.g. .byobu/profile covers .byobu, .claude/settings
|
||||||
|
# covers .claude, etc.).
|
||||||
|
# Uses `command grep` (bypasses colour aliases) and a bash glob for the
|
||||||
|
# prefix check (avoids regex escaping issues with dots in paths).
|
||||||
|
_is_declared() {
|
||||||
|
local p="$1" decl
|
||||||
|
command grep -qxF "$p" <<< "$declared" && return 0
|
||||||
|
while IFS= read -r decl; do
|
||||||
|
[[ "$decl" == "$p/"* ]] && return 0
|
||||||
|
done <<< "$declared"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
_in_backup() { [[ -n "$mackup_dir" && -e "$mackup_dir/$1" ]]; }
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Collect candidate items — same logic as generate_custom_cfg.
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
local -a items=()
|
local -a items=()
|
||||||
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
|
||||||
@@ -76,83 +90,110 @@ cmd_scan() {
|
|||||||
items+=("$name")
|
items+=("$name")
|
||||||
done < <(find "$HOME" -maxdepth 1 -mindepth 1 -name '.*' -type d 2>/dev/null | sort)
|
done < <(find "$HOME" -maxdepth 1 -mindepth 1 -name '.*' -type d 2>/dev/null | sort)
|
||||||
|
|
||||||
# Check existing stale duplicates.
|
# -----------------------------------------------------------------------
|
||||||
local -a to_remove=()
|
# Stale ssr-*.cfg files: exist but now covered by a built-in definition.
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
local -a stale_cfgs=()
|
||||||
local stale_cfg stale_path
|
local stale_cfg stale_path
|
||||||
for stale_cfg in "$cfg_dir"/ssr-*.cfg; do
|
for stale_cfg in "$cfg_dir"/ssr-*.cfg; do
|
||||||
[[ -f "$stale_cfg" ]] || continue
|
[[ -f "$stale_cfg" ]] || continue
|
||||||
stale_path="$(awk '/^\[configuration_files\]/{f=1;next}/^\[/{f=0}f&&/^[^#[:space:]]/{print $1;exit}' "$stale_cfg")"
|
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")
|
_is_declared "$stale_path" && stale_cfgs+=("$stale_cfg")
|
||||||
done
|
done
|
||||||
|
|
||||||
# Only suggest items that have NO cfg file yet.
|
# -----------------------------------------------------------------------
|
||||||
# Existing files are user-owned — edit them directly in ~/.mackup/.
|
# Filter to NEW items only (user owns existing files).
|
||||||
local -a to_write=()
|
# -----------------------------------------------------------------------
|
||||||
local -a cfg_paths=()
|
local -a new_items=()
|
||||||
|
local -a new_cfgs=()
|
||||||
local it app_name cfg_file
|
local it app_name cfg_file
|
||||||
for it in "${items[@]:-}"; do
|
for it in "${items[@]:-}"; do
|
||||||
app_name="$(_ssr_mackup_cfg_name "$it")"
|
app_name="$(_ssr_mackup_cfg_name "$it")"
|
||||||
cfg_file="$cfg_dir/${app_name}.cfg"
|
cfg_file="$cfg_dir/${app_name}.cfg"
|
||||||
[[ -f "$cfg_file" ]] && continue # user owns it — skip
|
[[ -f "$cfg_file" ]] && continue
|
||||||
to_write+=("$it")
|
new_items+=("$it")
|
||||||
cfg_paths+=("$cfg_file")
|
new_cfgs+=("$cfg_file")
|
||||||
done
|
done
|
||||||
|
|
||||||
# Nothing to do?
|
if [[ ${#new_items[@]} -eq 0 && ${#stale_cfgs[@]} -eq 0 ]]; then
|
||||||
if [[ ${#to_write[@]} -eq 0 && ${#to_remove[@]} -eq 0 ]]; then
|
|
||||||
ssr::ok "All ~/.mackup/ssr-*.cfg files are up-to-date — nothing to do."
|
ssr::ok "All ~/.mackup/ssr-*.cfg files are up-to-date — nothing to do."
|
||||||
return 0
|
return 0
|
||||||
fi
|
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'
|
printf '\n'
|
||||||
|
local written=0 removed=0
|
||||||
|
|
||||||
# Confirm.
|
# -----------------------------------------------------------------------
|
||||||
local total=$(( ${#to_write[@]} + ${#to_remove[@]} ))
|
# New files — confirm each individually.
|
||||||
ssr::confirm "Apply $total change(s)?" 60 || { printf 'Aborted.\n'; return 0; }
|
# -----------------------------------------------------------------------
|
||||||
|
if [[ ${#new_items[@]} -gt 0 ]]; then
|
||||||
|
printf '\033[1mNew Mackup app definitions\033[0m (%d candidate(s))\n\n' "${#new_items[@]}"
|
||||||
|
|
||||||
# Write new files (never overwrites existing ones).
|
local i
|
||||||
for it in "${to_write[@]:-}"; do
|
for i in "${!new_items[@]}"; do
|
||||||
_ssr_mackup_write_cfg "$cfg_dir" "$it"
|
it="${new_items[$i]}"
|
||||||
done
|
cfg_file="${new_cfgs[$i]}"
|
||||||
|
local cfg_name="${cfg_file##*/}"
|
||||||
|
|
||||||
# Remove stale duplicates.
|
# Resolved paths preview.
|
||||||
for f in "${to_remove[@]:-}"; do
|
local resolved; resolved="$(_ssr_mackup_resolve_paths "$it")"
|
||||||
rm -f "$f"
|
local line_count; line_count="$(printf '%s\n' "$resolved" | wc -l | tr -d ' ')"
|
||||||
ssr::log "Removed stale: ${f##*/}"
|
|
||||||
done
|
|
||||||
|
|
||||||
ssr::ok "Done — ${#to_write[@]} written, ${#to_remove[@]} removed."
|
# Header for this item.
|
||||||
printf '\nEdit ~/.mackup/ssr-*.cfg directly to customise paths.\n'
|
printf ' \033[32m+\033[0m \033[1m~/.mackup/%s\033[0m\n' "$cfg_name"
|
||||||
printf 'Run \033[1mssr sync\033[0m to back up with the new definitions.\n\n'
|
if [[ "$line_count" -eq 1 && "$resolved" == "$it" ]]; then
|
||||||
|
printf ' backs up: ~/%s/ (whole directory)\n' "$it"
|
||||||
|
else
|
||||||
|
printf ' backs up: %d path(s) inside ~/%s/\n' "$line_count" "$it"
|
||||||
|
printf '%s\n' "$resolved" | while IFS= read -r p; do
|
||||||
|
printf ' · ~/%s\n' "$p"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
printf '\n'
|
||||||
|
|
||||||
|
if ssr::confirm " Create ~/.mackup/$cfg_name?" 30; then
|
||||||
|
_ssr_mackup_write_cfg "$cfg_dir" "$it"
|
||||||
|
written=$((written + 1))
|
||||||
|
printf ' \033[32m✓ written\033[0m\n\n'
|
||||||
|
else
|
||||||
|
printf ' skipped\n\n'
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Stale files — confirm each removal individually.
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
if [[ ${#stale_cfgs[@]} -gt 0 ]]; then
|
||||||
|
printf '\033[1mStale duplicates\033[0m (now covered by a Mackup built-in)\n\n'
|
||||||
|
|
||||||
|
local f
|
||||||
|
for f in "${stale_cfgs[@]}"; do
|
||||||
|
printf ' \033[31m-\033[0m \033[1m%s\033[0m\n' "~/.mackup/${f##*/}"
|
||||||
|
# Show which built-in covers it.
|
||||||
|
stale_path="$(awk '/^\[configuration_files\]/{f=1;next}/^\[/{f=0}f&&/^[^#[:space:]]/{print $1;exit}' "$f")"
|
||||||
|
printf ' path \033[2m%s\033[0m is now declared by a built-in Mackup app\n\n' "$stale_path"
|
||||||
|
|
||||||
|
if ssr::confirm " Remove ${f##*/}?" 30; then
|
||||||
|
rm -f "$f"
|
||||||
|
removed=$((removed + 1))
|
||||||
|
printf ' \033[31m✓ removed\033[0m\n\n'
|
||||||
|
else
|
||||||
|
printf ' skipped\n\n'
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Summary.
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
if [[ $written -gt 0 || $removed -gt 0 ]]; then
|
||||||
|
ssr::ok "Done — $written written, $removed removed."
|
||||||
|
[[ $written -gt 0 ]] && printf '\nEdit ~/.mackup/ssr-*.cfg directly to adjust paths.\n'
|
||||||
|
printf 'Run \033[1mssr sync\033[0m to back up with the updated definitions.\n\n'
|
||||||
|
else
|
||||||
|
printf 'Nothing applied.\n'
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|||||||
+85
-34
@@ -14,20 +14,24 @@
|
|||||||
# Custom Mackup app definition auto-generator
|
# Custom Mackup app definition auto-generator
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
# Curated dot-paths that matter for developer workflows but are not in
|
# Curated dot-paths for developer workflows NOT covered by Mackup's built-in
|
||||||
# Mackup's built-in app definitions. Add your own via SSR_MACKUP_EXTRA_PATHS
|
# app definitions. Add your own via SSR_MACKUP_EXTRA_PATHS in ssr.conf.
|
||||||
# (space-separated) in ssr.conf.
|
#
|
||||||
|
# Built-ins that ALREADY cover common tools (do NOT add them here):
|
||||||
|
# claude-code.cfg → .claude/{plugins,settings,skills,agents,settings.json,CLAUDE.md}
|
||||||
|
# codex.cfg → .codex/{config.toml,auth.json}
|
||||||
|
# cursor.cfg → Library/Application Support/Cursor/User/... (not .cursor/)
|
||||||
|
# docker.cfg → .docker/{config.json,daemon.json}
|
||||||
|
# github-cli.cfg → .config/gh/config.yml (xdg_configuration_files)
|
||||||
|
# vscode.cfg → Library/Application Support/Code/User/... (not .vscode/)
|
||||||
|
# vscode-insiders.cfg → Library/Application Support/Code - Insiders/...
|
||||||
_SSR_MACKUP_CURATED=(
|
_SSR_MACKUP_CURATED=(
|
||||||
.claude # Claude AI CLI config, skills, agents
|
# .cursor/ rules and workspace-specific settings live here — the built-in
|
||||||
.cursor # Cursor IDE config + rules
|
# cursor.cfg only covers Library/Application Support/Cursor (different path).
|
||||||
.codex # OpenAI Codex config
|
.cursor
|
||||||
.easymcp # EasyMCP config
|
.easymcp # EasyMCP config (no built-in)
|
||||||
.factory # Factory AI config
|
.factory # Factory AI config (no built-in)
|
||||||
.docker # Docker Desktop — selective files only (see _SSR_MACKUP_SELECTIVE)
|
.config/op # 1Password CLI config (no built-in)
|
||||||
.config/claude
|
|
||||||
.config/cursor
|
|
||||||
.config/gh # GitHub CLI (if not already in mackup)
|
|
||||||
.config/op # 1Password CLI config
|
|
||||||
bin # personal ~/bin scripts
|
bin # personal ~/bin scripts
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -40,15 +44,13 @@ _SSR_MACKUP_CURATED=(
|
|||||||
# config files we actually need. Extend via SSR_MACKUP_SELECTIVE_PATHS
|
# config files we actually need. Extend via SSR_MACKUP_SELECTIVE_PATHS
|
||||||
# (same format, space-separated entries) in ssr.conf.
|
# (same format, space-separated entries) in ssr.conf.
|
||||||
_SSR_MACKUP_SELECTIVE=(
|
_SSR_MACKUP_SELECTIVE=(
|
||||||
".claude=settings.json,CLAUDE.md,skills,plugins,agents,commands"
|
# .cursor/ — only the stable config sub-paths; exclude logs/, extensions/,
|
||||||
|
# workspaceStorage/ etc. The built-in cursor.cfg covers Library/Application
|
||||||
|
# Support paths; this covers the dot-dir used for rules and workspace config.
|
||||||
".cursor=settings.json,keybindings.json,snippets,rules"
|
".cursor=settings.json,keybindings.json,snippets,rules"
|
||||||
".codex=auth.json,config.toml"
|
|
||||||
".vscode=settings.json,keybindings.json,snippets"
|
|
||||||
".vscode-insiders=settings.json,keybindings.json,snippets"
|
|
||||||
".factory=settings.json,droids"
|
".factory=settings.json,droids"
|
||||||
# Docker Desktop rejects ~/.docker as a symlink — back up individual files
|
# Add selective overrides for any SSR_MACKUP_EXTRA_PATHS entries here if
|
||||||
# only so ~/.docker/ stays a real directory that Docker Desktop can manage.
|
# you need sub-path granularity instead of whole-directory backup.
|
||||||
".docker=config.json,daemon.json"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Dot-dirs/files to never auto-include.
|
# Dot-dirs/files to never auto-include.
|
||||||
@@ -128,12 +130,16 @@ _ssr_mackup_write_cfg() {
|
|||||||
# built-in app definitions AND user-defined ~/.mackup/*.cfg files.
|
# built-in app definitions AND user-defined ~/.mackup/*.cfg files.
|
||||||
# Our own generated ssr-*.cfg files are excluded to avoid circular checks.
|
# Our own generated ssr-*.cfg files are excluded to avoid circular checks.
|
||||||
_ssr_mackup_declared_paths() {
|
_ssr_mackup_declared_paths() {
|
||||||
local apps_dir=""
|
# Locate Mackup's built-in applications/ directory.
|
||||||
|
# Mackup bundles its own Python interpreter — use it so the import works
|
||||||
# Locate Mackup's built-in apps/ directory via Python.
|
# regardless of which system python3 is active.
|
||||||
if command -v python3 >/dev/null 2>&1; then
|
local apps_dir="" mackup_bin
|
||||||
apps_dir="$(python3 -c \
|
mackup_bin="$(command -v mackup 2>/dev/null)"
|
||||||
'import os, mackup; print(os.path.join(os.path.dirname(mackup.__file__), "apps"))' \
|
if [[ -n "$mackup_bin" ]]; then
|
||||||
|
local mackup_py
|
||||||
|
mackup_py="$(head -1 "$mackup_bin" | sed 's/^#!//')"
|
||||||
|
apps_dir="$("$mackup_py" -c \
|
||||||
|
'import os, mackup; print(os.path.join(os.path.dirname(mackup.__file__), "applications"))' \
|
||||||
2>/dev/null || true)"
|
2>/dev/null || true)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -142,15 +148,17 @@ _ssr_mackup_declared_paths() {
|
|||||||
|
|
||||||
{
|
{
|
||||||
[[ -d "$apps_dir" ]] \
|
[[ -d "$apps_dir" ]] \
|
||||||
&& grep -rh '' "$apps_dir"/*.cfg 2>/dev/null || true
|
&& grep -rh '' "$apps_dir"/ 2>/dev/null || true
|
||||||
[[ -d "$user_cfg_dir" ]] \
|
[[ -d "$user_cfg_dir" ]] \
|
||||||
&& grep -rhl '' "$user_cfg_dir"/*.cfg 2>/dev/null \
|
&& grep -rhl '' "$user_cfg_dir"/ 2>/dev/null \
|
||||||
| grep -v '/ssr-' \
|
| grep -v '/ssr-' \
|
||||||
| xargs grep -h '' 2>/dev/null || true
|
| xargs grep -h '' 2>/dev/null || true
|
||||||
} | awk '
|
} | awk '
|
||||||
/^\[configuration_files\]/ { in_section=1; next }
|
/^\[configuration_files\]/ { in_cfg=1; in_xdg=0; next }
|
||||||
/^\[/ { in_section=0 }
|
/^\[xdg_configuration_files\]/ { in_xdg=1; in_cfg=0; next }
|
||||||
in_section && /^[^#[:space:]]/ { print $1 }
|
/^\[/ { in_cfg=0; in_xdg=0 }
|
||||||
|
in_cfg && /^[^#[:space:]]/ { print $1 }
|
||||||
|
in_xdg && /^[^#[:space:]]/ { print ".config/" $1 }
|
||||||
' | sort -u
|
' | sort -u
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,9 +202,20 @@ ssr::mackup::generate_custom_cfg() {
|
|||||||
local declared
|
local declared
|
||||||
declared="$(_ssr_mackup_declared_paths)"
|
declared="$(_ssr_mackup_declared_paths)"
|
||||||
|
|
||||||
# Return true when <path> is already declared in any existing app definition.
|
# Return true when <path> is already declared, OR when any declared path
|
||||||
|
# starts with "<path>/" (i.e. individual files inside <path> are declared,
|
||||||
|
# meaning a built-in covers the directory via selective file entries).
|
||||||
|
# Example: built-in byobu declares .byobu/profile → .byobu is covered.
|
||||||
|
#
|
||||||
|
# Uses `command grep` (bypasses colour aliases) and a bash glob for the
|
||||||
|
# prefix check (avoids regex escaping issues with dots in paths).
|
||||||
_is_declared() {
|
_is_declared() {
|
||||||
printf '%s\n' "$declared" | grep -qxF "$1"
|
local p="$1" decl
|
||||||
|
command grep -qxF "$p" <<< "$declared" && return 0
|
||||||
|
while IFS= read -r decl; do
|
||||||
|
[[ "$decl" == "$p/"* ]] && return 0
|
||||||
|
done <<< "$declared"
|
||||||
|
return 1
|
||||||
}
|
}
|
||||||
# Return true when <path> is already present in the Mackup cloud backup.
|
# Return true when <path> is already present in the Mackup cloud backup.
|
||||||
_in_backup() { [[ -e "$mackup_dir/$1" ]]; }
|
_in_backup() { [[ -e "$mackup_dir/$1" ]]; }
|
||||||
@@ -323,6 +342,21 @@ ssr::mackup::backup() {
|
|||||||
fi
|
fi
|
||||||
[[ -n "$mackup_root" ]] && ssr::mackup::_materialize_icloud "$mackup_root"
|
[[ -n "$mackup_root" ]] && ssr::mackup::_materialize_icloud "$mackup_root"
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Pre-symlink: paths managed by apps that recreate their directory almost
|
||||||
|
# instantly after deletion, making Mackup's delete→symlink sequence lose the
|
||||||
|
# race. We pre-create the symlink → cloud BEFORE running `link install`.
|
||||||
|
# Mackup detects the existing correct symlink and skips the path entirely.
|
||||||
|
#
|
||||||
|
# Format: home-relative path (same as in the Mackup app definition).
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
local -a _presymlink=(
|
||||||
|
# iTerm2 recreates DynamicProfiles within milliseconds of deletion.
|
||||||
|
# Pre-symlinking ensures Mackup skips it; iTerm2 reads/writes through
|
||||||
|
# the symlink normally (creating new profiles goes straight to iCloud).
|
||||||
|
.config/iterm2/AppSupport/DynamicProfiles
|
||||||
|
)
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Light preclean — remove transient directories so their ephemeral contents
|
# Light preclean — remove transient directories so their ephemeral contents
|
||||||
# (debug symlinks, log files) are not included in the cloud copy.
|
# (debug symlinks, log files) are not included in the cloud copy.
|
||||||
@@ -330,6 +364,23 @@ ssr::mackup::backup() {
|
|||||||
# os.path.isfile/isdir guard, so we only need to handle known directories.
|
# os.path.isfile/isdir guard, so we only need to handle known directories.
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
_ssr_mackup_preclean() {
|
_ssr_mackup_preclean() {
|
||||||
|
local mr="$1" # mackup root
|
||||||
|
|
||||||
|
# 1. Pre-symlink race-condition paths.
|
||||||
|
local rel home_p cloud_p
|
||||||
|
for rel in "${_presymlink[@]}"; do
|
||||||
|
home_p="$HOME/$rel"
|
||||||
|
cloud_p="$mr/$rel"
|
||||||
|
[[ -d "$cloud_p" ]] || continue # no cloud copy yet — skip
|
||||||
|
if [[ -L "$home_p" ]] && [[ "$(readlink "$home_p")" == "$cloud_p" ]]; then
|
||||||
|
continue # already correct symlink
|
||||||
|
fi
|
||||||
|
ssr::log " pre-symlink: $rel → cloud (prevents iTerm2-style race)"
|
||||||
|
rm -rf "$home_p" 2>/dev/null || true
|
||||||
|
ln -s "$cloud_p" "$home_p" 2>/dev/null || true
|
||||||
|
done
|
||||||
|
|
||||||
|
# 2. Transient directories whose contents should never go into cloud.
|
||||||
local -a transient_dirs=(
|
local -a transient_dirs=(
|
||||||
.claude/debug # Claude Code debug session links
|
.claude/debug # Claude Code debug session links
|
||||||
.cursor/logs # Cursor IDE logs
|
.cursor/logs # Cursor IDE logs
|
||||||
@@ -348,7 +399,7 @@ ssr::mackup::backup() {
|
|||||||
|
|
||||||
local attempt rc=0
|
local attempt rc=0
|
||||||
for attempt in 1 2 3; do
|
for attempt in 1 2 3; do
|
||||||
_ssr_mackup_preclean
|
_ssr_mackup_preclean "$mackup_root"
|
||||||
if mackup -f link install; then
|
if mackup -f link install; then
|
||||||
ssr::ok "mackup link install complete — symlinks created in ~/"
|
ssr::ok "mackup link install complete — symlinks created in ~/"
|
||||||
return 0
|
return 0
|
||||||
|
|||||||
Reference in New Issue
Block a user