Stop the backup report depending on listing the Drive folder
Under launchd, macOS allows a named path on ~/Library/CloudStorage to be stat'd but blocks enumerating the directory. A glob therefore expands to nothing, and the reporting block read that as zero: the first real LaunchAgent run after the TCC fix logged "0 copies off-site" and emailed "no off-site copies at all" while 57 files sat in the folder. The copy loop was unaffected, because it uses filenames taken from the NAS rather than a glob. Freshness is now derived by testing explicit filenames for the last 60 days, which works in both contexts. Enumeration is still used for the total count, but a count of zero is reported as unknown rather than alerted on — an unreadable folder and an empty one are different things, and only one of them is an emergency. Preferred over granting Full Disk Access: a privacy grant is exactly what an OS update silently reset here in the first place. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
4429ae70b7
commit
2ee1b18dc3
+29
-21
@@ -150,11 +150,24 @@ fi
|
|||||||
# out. That is backwards: the whole value of the off-site copy is that it outlives the NAS's
|
# out. That is backwards: the whole value of the off-site copy is that it outlives the NAS's
|
||||||
# 30-day window. Each file is ~260 KB, so a decade of them is under a gigabyte. If it ever
|
# 30-day window. Each file is ~260 KB, so a decade of them is under a gigabyte. If it ever
|
||||||
# genuinely needs trimming, do it by hand, and never below what the NAS still holds.
|
# genuinely needs trimming, do it by hand, and never below what the NAS still holds.
|
||||||
cd "$DRIVE_DIR" || die "cannot enter $DRIVE_DIR"
|
# Reporting and freshness.
|
||||||
TOTAL="$(ls -1 checklists-????-??-??.json 2>/dev/null | wc -l | tr -d ' ')"
|
#
|
||||||
OLDEST="$(ls -1 checklists-????-??-??.json 2>/dev/null | sort | head -1)"
|
# This deliberately does NOT rely on listing the Drive folder. Under launchd, macOS lets a
|
||||||
NEWEST="$(ls -1 checklists-????-??-??.json 2>/dev/null | sort | tail -1)"
|
# named path be stat'd but blocks enumerating ~/Library/CloudStorage, so a glob quietly
|
||||||
say "✓ done — $TOTAL copies off-site, ${OLDEST:-none} → ${NEWEST:-none}"
|
# expands to nothing — which on 2026-09-24 made this script report "0 copies off-site" and
|
||||||
|
# email an alert while 57 files sat there. Checking explicit filenames works in both
|
||||||
|
# contexts, so freshness is derived that way and enumeration is treated as a bonus.
|
||||||
|
NEWEST=""
|
||||||
|
for i in $(seq 0 60); do
|
||||||
|
d="$(date -v-${i}d +%Y-%m-%d 2>/dev/null)" || break
|
||||||
|
if [ -s "$DRIVE_DIR/checklists-$d.json" ]; then NEWEST="checklists-$d.json"; NEWEST_DATE="$d"; AGE_DAYS=$i; break; fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Enumeration is allowed when run by hand; when it is blocked the count is unknown rather
|
||||||
|
# than zero, and an unknown count is never treated as a missing backup.
|
||||||
|
TOTAL="$(ls -1 "$DRIVE_DIR"/checklists-????-??-??.json 2>/dev/null | wc -l | tr -d ' ')"
|
||||||
|
if [ "$TOTAL" = "0" ]; then TOTAL="unknown (cannot list the folder from here)"; fi
|
||||||
|
say "✓ done — $TOTAL copies off-site, newest ${NEWEST:-none}"
|
||||||
|
|
||||||
# Some files were fetched but were not usable. The run "succeeded" in the sense that it did
|
# Some files were fetched but were not usable. The run "succeeded" in the sense that it did
|
||||||
# not crash, which is exactly the kind of success that hides a broken backup.
|
# not crash, which is exactly the kind of success that hides a broken backup.
|
||||||
@@ -164,24 +177,19 @@ if [ "${FAILED:-0}" -gt 0 ]; then
|
|||||||
were skipped. See the log for which ones."
|
were skipped. See the log for which ones."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# The staleness check is the one that would have caught both of the incidents in the header
|
# The check that would have caught both of the incidents in the header comment. Every
|
||||||
# comment. Every failure path above assumes this script RAN; if the Mac is asleep, or the
|
# failure path above assumes this script RAN; if the Mac is asleep or the LaunchAgent is
|
||||||
# LaunchAgent is unloaded, nothing above ever fires. This at least turns a quietly ageing
|
# unloaded, nothing fires at all.
|
||||||
# off-site copy into mail the next time it does run.
|
|
||||||
if [ -z "$NEWEST" ]; then
|
if [ -z "$NEWEST" ]; then
|
||||||
alert "no off-site copies at all" "$DRIVE_DIR contains no checklists-<date>.json files."
|
alert "no recent off-site copy" \
|
||||||
else
|
"No checklists-<date>.json for any of the last 60 days was found in
|
||||||
NEWEST_DATE="$(printf '%s' "$NEWEST" | sed -E 's/^checklists-([0-9]{4}-[0-9]{2}-[0-9]{2})\.json$/\1/')"
|
$DRIVE_DIR
|
||||||
NEWEST_EPOCH="$(date -j -f "%Y-%m-%d" "$NEWEST_DATE" +%s 2>/dev/null || echo 0)"
|
Either copying has stopped, or this machine cannot read that folder."
|
||||||
if [ "$NEWEST_EPOCH" -gt 0 ]; then
|
elif [ "$AGE_DAYS" -gt "$STALE_DAYS" ]; then
|
||||||
AGE_DAYS=$(( ( $(date +%s) - NEWEST_EPOCH ) / 86400 ))
|
|
||||||
if [ "$AGE_DAYS" -gt "$STALE_DAYS" ]; then
|
|
||||||
alert "off-site copy is $AGE_DAYS days old" \
|
alert "off-site copy is $AGE_DAYS days old" \
|
||||||
"Newest file off-site is $NEWEST ($AGE_DAYS days old); anything more than
|
"Newest file off-site is $NEWEST ($AGE_DAYS days old); more than $STALE_DAYS days
|
||||||
$STALE_DAYS days means the chain from the NAS to Drive is not working, even though
|
means the chain from the NAS to Drive is not working, even though this run did
|
||||||
this run did not error."
|
not error."
|
||||||
else
|
else
|
||||||
say "freshness OK — newest off-site copy is $AGE_DAYS day(s) old"
|
say "freshness OK — newest off-site copy is $AGE_DAYS day(s) old"
|
||||||
fi
|
fi
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|||||||
Reference in New Issue
Block a user