Files
System-Sync-Restore/lib/mackup.sh
T
Oliver Surke a4465f37c6 Improve schedule and mackup reliability
- bin/ssr-schedule: use `launchctl bootstrap`/`bootout` (modern API) with
  legacy `load -w` fallback. The previous `load -w`-only flow silently
  no-op'd on macOS 10.15+ in some configurations, leaving `ssr status`
  reporting `inactive` after a successful `ssr schedule on`.
- bin/ssr-status: query the service via `launchctl print`/`list <label>`
  for consistent active/inactive detection.
- lib/mackup.sh: pre-materialize iCloud placeholders via `brctl download`
  and retry `mackup -f backup` up to three times to dodge the recurring
  `OSError [Errno 66] Directory not empty` race between mackup's rmtree
  and iCloud's filesystem provider. Soft-fails so the rest of `ssr sync`
  still runs even if mackup never settles; logs actionable hints.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-11 18:02:31 +02:00

98 lines
3.8 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` is the documented way to request file materialization.
# Available on macOS 10.12+; silently skip if missing on older systems.
if command -v brctl >/dev/null 2>&1; then
brctl download "$mackup_root" >/dev/null 2>&1 || true
fi
# Belt-and-braces: prune any stub files that survived the download
# request (sometimes left over from interrupted prior runs).
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"
mackup restore
}