873cb3d51f
brctl download was removed in macOS Sonoma — the remaining subcommands are diagnose/log/dump/status/accounts/quota/monitor. Replace the dead call with a find loop that uses 'open -g <real-path>' to request per-file iCloud download from the daemon, followed by a 1-second settle pause and the existing .icloud stub deletion. Also add -f to mackup restore for consistency with the backup call. Co-authored-by: Cursor <cursoragent@cursor.com>
107 lines
4.3 KiB
Bash
107 lines
4.3 KiB
Bash
#!/usr/bin/env bash
|
|
# lib/mackup.sh — Mackup install + backup/restore.
|
|
# 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.
|
|
|
|
ssr::mackup::ensure_installed() {
|
|
if command -v mackup >/dev/null 2>&1; then
|
|
return 0
|
|
fi
|
|
ssr::log "Installing mackup via brew"
|
|
brew install mackup
|
|
}
|
|
|
|
# Writes ~/.mackup.cfg pointing the engine at the cloud directory.
|
|
# Idempotent: rewrites only if content differs.
|
|
ssr::mackup::configure() {
|
|
local mackup_dir="$1"
|
|
local cfg="$HOME/.mackup.cfg"
|
|
local desired
|
|
desired="$(printf '[storage]\nengine = file_system\npath = %s\ndirectory = mackup\n' "$mackup_dir")"
|
|
|
|
if [[ -f "$cfg" ]] && [[ "$(cat "$cfg")" == "$desired" ]]; then
|
|
return 0
|
|
fi
|
|
|
|
if [[ -L "$cfg" || -f "$cfg" ]]; then
|
|
cp -f "$cfg" "$cfg.bak.$(date +%s)" 2>/dev/null || true
|
|
rm -f "$cfg"
|
|
fi
|
|
printf '%s\n' "$desired" > "$cfg"
|
|
ssr::ok "Wrote $cfg"
|
|
}
|
|
|
|
# Force iCloud Drive to materialize placeholder (.icloud) files under the
|
|
# Mackup storage directory before mackup walks them. The most common ssr sync
|
|
# crash is `OSError [Errno 66] Directory not empty` raised by shutil.rmtree
|
|
# inside mackup when iCloud lazily re-creates dataless placeholders between
|
|
# listdir() and rmdir(). Pre-downloading sidesteps the race.
|
|
ssr::mackup::_materialize_icloud() {
|
|
local mackup_root="$1"
|
|
[[ -d "$mackup_root" ]] || return 0
|
|
case "$mackup_root" in
|
|
*Mobile\ Documents*) ;; # only worth doing for iCloud
|
|
*) return 0 ;;
|
|
esac
|
|
# `brctl download` was removed in macOS Sonoma (remaining brctl subcommands:
|
|
# diagnose/log/dump/status/accounts/quota/monitor). The current way to
|
|
# trigger per-file iCloud download from the CLI is `open -g <real-path>`,
|
|
# which asks the iCloud daemon to fetch the file in the background.
|
|
find "$mackup_root" -name '*.icloud' -type f 2>/dev/null | while IFS= read -r stub; do
|
|
local dir base real
|
|
dir="$(dirname "$stub")"
|
|
base="$(basename "$stub")"
|
|
real="${base#.}" # strip leading dot
|
|
real="${real%.icloud}" # strip .icloud suffix
|
|
open -g "$dir/$real" 2>/dev/null || true
|
|
done
|
|
# Brief pause so the iCloud daemon can start fetching before mackup reads.
|
|
sleep 1
|
|
# Belt-and-braces: prune any stubs that couldn't be resolved.
|
|
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.
|
|
ssr::mackup::backup() {
|
|
ssr::log "mackup backup"
|
|
|
|
# Derive mackup root from ~/.mackup.cfg (path + directory).
|
|
local cfg="$HOME/.mackup.cfg" mackup_root="" path dir
|
|
if [[ -f "$cfg" ]]; then
|
|
path="$(awk -F= '/^path/{gsub(/^[[:space:]]+|[[:space:]]+$/,"",$2); print $2}' "$cfg")"
|
|
dir="$( awk -F= '/^directory/{gsub(/^[[:space:]]+|[[:space:]]+$/,"",$2); print $2}' "$cfg")"
|
|
[[ -n "$path" && -n "$dir" ]] && mackup_root="$path/$dir"
|
|
fi
|
|
[[ -n "$mackup_root" ]] && ssr::mackup::_materialize_icloud "$mackup_root"
|
|
|
|
local attempt rc=0
|
|
for attempt in 1 2 3; do
|
|
if mackup -f backup; then
|
|
return 0
|
|
fi
|
|
rc=$?
|
|
ssr::warn "mackup backup 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)"
|
|
return 0 # soft-fail so the rest of ssr sync still runs
|
|
}
|
|
|
|
ssr::mackup::restore() {
|
|
ssr::log "mackup restore"
|
|
# -f: answer "yes" to all prompts (consistent with backup behaviour).
|
|
mackup -f restore
|
|
}
|