From 953c213ded42e60aa09d60f180c2722619e0d10d Mon Sep 17 00:00:00 2001 From: "Oliver Surke (Gitea)" Date: Tue, 12 May 2026 15:37:43 +0200 Subject: [PATCH] fix: use mackup link install/link instead of backup/restore MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In Mackup 0.10.2, `mackup backup` only copies files to cloud — it never creates symlinks. Symlinks require `mackup link install` (sync) or `mackup link` (restore). This is why no symlinks appeared in ~/ after running ssr sync. Changes: - ssr::mackup::backup() → now calls `mackup -f link install` Moves managed files to cloud and creates symlinks in ~/ Naturally skips sockets and dangling symlinks via isfile/isdir guard, so the pre-cleanup is now a light transient-dir purge only. - ssr::mackup::restore() → now calls `mackup -f link` Creates symlinks ~/file → cloud/file (files must already be in cloud). Keeps files in iCloud for ongoing sync, unlike the copy-based restore. - Added Mackup 0.10.2 command-mapping comment to file header. Co-authored-by: Cursor --- lib/mackup.sh | 97 +++++++++++++++++++++++++-------------------------- 1 file changed, 48 insertions(+), 49 deletions(-) diff --git a/lib/mackup.sh b/lib/mackup.sh index ac5aa42..6b75b3a 100644 --- a/lib/mackup.sh +++ b/lib/mackup.sh @@ -3,6 +3,12 @@ # Note: upstream mackup is in low-maintenance mode; we keep it because it # remains the simplest way to sync dotfiles for a wide range of apps. The # user can swap engines via ~/.mackup.cfg without changing this script. +# +# Mackup 0.10.2 command mapping (importantly different from older versions): +# mackup backup → copy only; NO symlinks created in ~/ +# mackup restore → copy only; NO symlinks created in ~/ +# mackup link install → copy to cloud + create symlinks in ~/ ← SSR uses this +# mackup link → create symlinks in ~/ (cloud files must exist) ← SSR restore # --------------------------------------------------------------------------- # Custom Mackup app definition auto-generator @@ -292,13 +298,21 @@ ssr::mackup::_materialize_icloud() { find "$mackup_root" -name '*.icloud' -type f -delete 2>/dev/null || true } -# Try `mackup -f backup` with a small retry budget. iCloud-induced races are -# transient: a 2-second sleep is usually enough for the provider to settle. -# We deliberately do NOT abort `ssr sync` on persistent failure — the user -# still benefits from brew dump, cleanup, and the unmanaged-apps scan that -# run afterwards. The failure is surfaced loudly via ssr::warn and syslog. +# Run `mackup -f link install` — this is the correct "backup with symlinks" +# command in Mackup 0.10.2. Unlike `mackup backup` (which only copies), +# `link install` moves each managed file to the cloud folder and replaces it +# with a symlink. On subsequent runs it detects already-linked files and +# skips them efficiently. +# +# Sockets and dangling symlinks are naturally skipped by `link install` because +# Python's os.path.isfile/isdir returns False for those types. We still run a +# light preclean for known transient directories so their contents don't appear +# as stale files in the cloud copy. +# +# We do NOT abort `ssr sync` on persistent failure — brew dump, cleanup, and +# the unmanaged-apps scan still run. Failure is surfaced via ssr::warn. ssr::mackup::backup() { - ssr::log "mackup backup" + ssr::log "mackup link install (backup + symlink)" # Derive mackup root from ~/.mackup.cfg (path + directory). local cfg="$HOME/.mackup.cfg" mackup_root="" path dir @@ -310,14 +324,12 @@ ssr::mackup::backup() { [[ -n "$mackup_root" ]] && ssr::mackup::_materialize_icloud "$mackup_root" # --------------------------------------------------------------------------- - # Pre-backup cleanup — remove artefacts that shutil.copytree cannot handle. - # Run this immediately before each mackup attempt so that apps (e.g. Claude - # Code) that recreate debug symlinks very quickly don't sneak back in. + # Light preclean — remove transient directories so their ephemeral contents + # (debug symlinks, log files) are not included in the cloud copy. + # `link install` already skips sockets and dangling symlinks via its own + # os.path.isfile/isdir guard, so we only need to handle known directories. # --------------------------------------------------------------------------- _ssr_mackup_preclean() { - # Known transient subdirectories that apps recreate constantly. - # These contain debug logs / sockets, never real config. - # Extend via SSR_MACKUP_PRECLEAN_PATHS (space-separated $HOME-relative). local -a transient_dirs=( .claude/debug # Claude Code debug session links .cursor/logs # Cursor IDE logs @@ -325,55 +337,42 @@ ssr::mackup::backup() { .vscode-insiders/logs .npm/_logs # npm debug logs ) - local extra dir + local extra d for extra in ${SSR_MACKUP_PRECLEAN_PATHS:-}; do transient_dirs+=("$extra") done - for dir in "${transient_dirs[@]}"; do - [[ -e "$HOME/$dir" ]] && rm -rf "$HOME/$dir" 2>/dev/null || true + for d in "${transient_dirs[@]}"; do + [[ -e "$HOME/$d" ]] && rm -rf "${HOME:?}/$d" 2>/dev/null || true done - - # 1. Unix socket files (-type s) — [Errno 102] Operation not supported. - local n; n="$(find "$HOME" -type s 2>/dev/null | wc -l | tr -d ' ')" - if [[ "$n" -gt 0 ]]; then - ssr::log " pre-clean: removing $n socket file(s)" - find "$HOME" -type s -delete 2>/dev/null || true - fi - - # 2. Dangling symlinks (-type l ! -e) — [Errno 2] No such file. - n="$(find "$HOME" -type l ! -e 2>/dev/null | wc -l | tr -d ' ')" - if [[ "$n" -gt 0 ]]; then - ssr::log " pre-clean: removing $n dangling symlink(s)" - find "$HOME" -type l ! -e -delete 2>/dev/null || true - fi } local attempt rc=0 for attempt in 1 2 3; do - _ssr_mackup_preclean # clean immediately before each attempt - if mackup -f backup; then + _ssr_mackup_preclean + if mackup -f link install; then + ssr::ok "mackup link install complete — symlinks created in ~/" return 0 fi rc=$? - ssr::warn "mackup backup failed (attempt $attempt/3, rc=$rc)" + ssr::warn "mackup link install failed (attempt $attempt/3, rc=$rc)" if [[ $attempt -lt 3 ]]; then [[ -n "$mackup_root" ]] && ssr::mackup::_materialize_icloud "$mackup_root" sleep 2 fi done - ssr::warn "mackup backup did not complete after 3 attempts (rc=$rc)." - ssr::warn "Sync will continue without it; rerun 'mackup -f backup' manually," - ssr::warn "or switch ~/.mackup.cfg to a non-iCloud storage path if this persists." - ssr::syslog "mackup backup failed after retries (rc=$rc)" + ssr::warn "mackup link install did not complete after 3 attempts (rc=$rc)." + ssr::warn "Sync will continue without it; run 'mackup -f link install' manually," + ssr::warn "or check ~/.mackup.cfg if this persists." + ssr::syslog "mackup link install failed after retries (rc=$rc)" return 0 # soft-fail so the rest of ssr sync still runs } ssr::mackup::restore() { - ssr::log "mackup restore" + ssr::log "mackup link (restore via symlinks)" - # Pre-materialize iCloud placeholders so mackup doesn't have to wait for - # each file to download one-by-one during the restore walk. + # Pre-materialize iCloud placeholders so mackup's isfile/isdir checks + # see real files rather than .icloud stubs (which read as non-existent). local cfg="$HOME/.mackup.cfg" mackup_root="" path dir if [[ -f "$cfg" ]]; then path="$(awk -F= '/^path/{gsub(/^[[:space:]]+|[[:space:]]+$/,"",$2); print $2}' "$cfg")" @@ -383,7 +382,6 @@ ssr::mackup::restore() { if [[ -n "$mackup_root" ]]; then ssr::log "Materializing iCloud files in $mackup_root (may take a moment)…" ssr::mackup::_materialize_icloud "$mackup_root" - # Count remaining stubs; if there are many, give iCloud more time. local stub_count stub_count="$(find "$mackup_root" -name '*.icloud' -type f 2>/dev/null | wc -l | tr -d ' ')" if [[ "$stub_count" -gt 0 ]]; then @@ -395,12 +393,11 @@ ssr::mackup::restore() { local stub_total stub_total="$(find "${mackup_root:-/dev/null}" -name '*.icloud' -type f 2>/dev/null | wc -l | tr -d ' ')" if [[ "$stub_total" -gt 0 ]]; then - ssr::warn "$stub_total file(s) still need downloading from iCloud — restore may pause between files." + ssr::warn "$stub_total file(s) still need downloading — symlinks may be skipped for those." fi - ssr::log "Running mackup restore (long pauses are normal while iCloud fetches files)…" + ssr::log "Running mackup link (long pauses normal while iCloud fetches files)…" - # Background watchdog: prints elapsed time every 15s so the user knows the - # process is still alive during iCloud download pauses. + # Background watchdog: prints elapsed time every 15s. local start_ts rc=0 done_flag watchdog_pid start_ts="$(date +%s)" done_flag="$(mktemp)" @@ -408,14 +405,16 @@ ssr::mackup::restore() { while [[ -f "$done_flag" ]]; do sleep 15 [[ -f "$done_flag" ]] || break - printf ' … mackup restore still running (%ds elapsed — waiting for iCloud)\n' \ + printf ' … mackup link still running (%ds elapsed — waiting for iCloud)\n' \ "$(( $(date +%s) - start_ts ))" >&2 done ) & watchdog_pid=$! - # -f: answer "yes" to all prompts (consistent with backup behaviour). - mackup -f restore || rc=$? + # `mackup link` creates symlinks ~/file → cloud/file for all managed apps. + # Unlike `mackup restore` (which copies), this keeps files in iCloud and + # ensures ongoing sync. -f auto-confirms replacement of existing home files. + mackup -f link || rc=$? rm -f "$done_flag" kill "$watchdog_pid" 2>/dev/null || true @@ -423,8 +422,8 @@ ssr::mackup::restore() { local elapsed=$(( $(date +%s) - start_ts )) if [[ $rc -ne 0 ]]; then - ssr::warn "mackup restore finished with errors (rc=$rc, ${elapsed}s elapsed)" + ssr::warn "mackup link finished with errors (rc=$rc, ${elapsed}s elapsed)" else - ssr::ok "mackup restore completed in ${elapsed}s" + ssr::ok "mackup link completed in ${elapsed}s — symlinks created in ~/" fi }