feat: selective sub-path backup for volatile dot-dirs
Mackup with engine=file_system already uses symlinks for all backed-up apps (/Users/osurke/.docker → <cloud>/Mackup/mackup/.docker). The copy PHASE fails when the source dir contains sockets or dangling symlinks. For dirs like .claude that have a volatile debug/ subdir, the clean fix is to list only the stable sub-paths in the Mackup cfg rather than the whole directory — debug/ is never touched. _SSR_MACKUP_SELECTIVE: built-in overrides for .claude, .cursor, .codex, .vscode, .factory — lists only config files (settings.json, CLAUDE.md, skills, plugins, agents, …), skipping logs/, debug/, etc. _ssr_mackup_resolve_paths(): resolves the [configuration_files] entries for a given item using the selective table, falling back to the whole dir. ssr mackup scan now shows '6 sub-path(s) of .claude/' in the plan instead of '.claude/' to make the selective behaviour visible. Configurable via SSR_MACKUP_SELECTIVE_PATHS in ssr.conf. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+14
-4
@@ -90,11 +90,12 @@ cmd_scan() {
|
||||
local -a labels=()
|
||||
local -a cfg_paths=()
|
||||
|
||||
local it app_name desired existing cfg_file
|
||||
local it app_name desired existing cfg_file paths_block
|
||||
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")"
|
||||
paths_block="$(_ssr_mackup_resolve_paths "$it")"
|
||||
desired="$(printf '[application]\nname = %s\n\n[configuration_files]\n%s\n' "$app_name" "$paths_block")"
|
||||
if [[ -f "$cfg_file" ]]; then
|
||||
existing="$(cat "$cfg_file")"
|
||||
if [[ "$existing" == "$desired" ]]; then
|
||||
@@ -123,8 +124,17 @@ cmd_scan() {
|
||||
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]}"
|
||||
# Show resolved paths (selective 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]}/" # whole directory
|
||||
else
|
||||
paths_hint="$line_count sub-path(s) of ${to_write[$i]}/"
|
||||
fi
|
||||
printf " ${colour}%s\033[0m %-38s %s\n" \
|
||||
"$label" "~/.mackup/$path" "$paths_hint"
|
||||
done
|
||||
fi
|
||||
|
||||
|
||||
@@ -26,6 +26,11 @@ SSR_MACKUP_AUTODISCOVER=true
|
||||
# Built-in defaults: .claude/debug .cursor/logs .vscode/logs .npm/_logs
|
||||
# SSR_MACKUP_PRECLEAN_PATHS=".my-app/tmp .other-app/run"
|
||||
|
||||
# Selective sub-path overrides — back up only listed sub-paths for a dir
|
||||
# instead of the whole directory (avoids sockets/debug symlinks in volatile dirs).
|
||||
# Format: ".dir=subpath1,subpath2" Built-in defaults cover .claude, .cursor, etc.
|
||||
# SSR_MACKUP_SELECTIVE_PATHS=".my-app=config.json,themes"
|
||||
|
||||
# Extra paths (relative to $HOME) to always include, space-separated.
|
||||
# Example: ".easymcp .my-tool .config/my-cli"
|
||||
# SSR_MACKUP_EXTRA_PATHS=""
|
||||
|
||||
+59
-3
@@ -24,6 +24,23 @@ _SSR_MACKUP_CURATED=(
|
||||
bin # personal ~/bin scripts
|
||||
)
|
||||
|
||||
# Selective sub-path overrides.
|
||||
# When a directory contains volatile/problematic files (sockets, debug
|
||||
# symlinks), list only the STABLE sub-paths here instead of the whole dir.
|
||||
# Format: "dir=subpath1,subpath2,subpath3" (paths relative to the dir)
|
||||
#
|
||||
# Example: .claude has a debug/ dir with dangling symlinks → list only the
|
||||
# config files we actually need. Extend via SSR_MACKUP_SELECTIVE_PATHS
|
||||
# (same format, space-separated entries) in ssr.conf.
|
||||
_SSR_MACKUP_SELECTIVE=(
|
||||
".claude=settings.json,CLAUDE.md,skills,plugins,agents,commands"
|
||||
".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"
|
||||
)
|
||||
|
||||
# Dot-dirs/files to never auto-include.
|
||||
# Covers: caches, generated state, credentials, large build artefacts.
|
||||
_SSR_MACKUP_SKIP=(
|
||||
@@ -52,17 +69,56 @@ _ssr_mackup_cfg_name() {
|
||||
printf 'ssr-%s' "$name"
|
||||
}
|
||||
|
||||
# Resolve the list of [configuration_files] entries for an item.
|
||||
# If a selective override exists for the item, expand sub-paths that are
|
||||
# present on disk. Falls back to the item itself (whole directory).
|
||||
#
|
||||
# Outputs one path per line.
|
||||
_ssr_mackup_resolve_paths() {
|
||||
local item="$1"
|
||||
local -a all_selective=("${_SSR_MACKUP_SELECTIVE[@]}")
|
||||
# Merge user-defined selectives from ssr.conf.
|
||||
local entry
|
||||
for entry in ${SSR_MACKUP_SELECTIVE_PATHS:-}; do
|
||||
all_selective+=("$entry")
|
||||
done
|
||||
|
||||
for entry in "${all_selective[@]}"; do
|
||||
local key="${entry%%=*}"
|
||||
local val="${entry#*=}"
|
||||
if [[ "$key" == "$item" ]]; then
|
||||
# Split comma-separated sub-paths; only include those that exist.
|
||||
local sub
|
||||
local IFS=','
|
||||
for sub in $val; do
|
||||
IFS=' '
|
||||
local full="$item/$sub"
|
||||
# List the path even if it doesn't exist yet — it may be created later.
|
||||
printf '%s\n' "$full"
|
||||
done
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
# No selective entry — back up the whole directory.
|
||||
printf '%s\n' "$item"
|
||||
}
|
||||
|
||||
# Write a single ~/.mackup/<name>.cfg for one dot-path.
|
||||
# Skips writing if the file already exists and its content matches
|
||||
# (idempotent on repeated syncs).
|
||||
# Uses selective sub-paths when a matching entry exists in _SSR_MACKUP_SELECTIVE.
|
||||
# Skips writing if content is unchanged (idempotent).
|
||||
_ssr_mackup_write_cfg() {
|
||||
local cfg_dir="$1" item="$2"
|
||||
local app_name; app_name="$(_ssr_mackup_cfg_name "$item")"
|
||||
local cfg_file="$cfg_dir/${app_name}.cfg"
|
||||
|
||||
# Build [configuration_files] block.
|
||||
local paths_block
|
||||
paths_block="$(_ssr_mackup_resolve_paths "$item")"
|
||||
|
||||
local desired
|
||||
desired="$(printf '[application]\nname = %s\n\n[configuration_files]\n%s\n' \
|
||||
"$app_name" "$item")"
|
||||
"$app_name" "$paths_block")"
|
||||
|
||||
if [[ -f "$cfg_file" ]] && [[ "$(cat "$cfg_file")" == "$desired" ]]; then
|
||||
return 0 # already up-to-date
|
||||
|
||||
Reference in New Issue
Block a user