feat(session-tools): disk-guard/bigdisk-mount-guard tools + wa console/lookup
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
79268d4db6
commit
44086ca45e
8 changed files with 316 additions and 5 deletions
|
|
@ -11,11 +11,11 @@ jobs:
|
|||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Publish to forge.black.lan npm registry
|
||||
- name: Publish to cocotte ct-forge (verdaccio) registry
|
||||
run: |
|
||||
chmod +x bin/*
|
||||
printf '//forge.black.lan/api/packages/lilith/npm/:_authToken=%s\n' "$NPM_TOKEN" > .npmrc
|
||||
npm publish --registry http://forge.black.lan/api/packages/lilith/npm/
|
||||
printf '//134.199.243.61:4873/:_authToken=%s\n' "$NPM_TOKEN" > .npmrc
|
||||
npm publish --registry http://134.199.243.61:4873/
|
||||
rm .npmrc
|
||||
env:
|
||||
NPM_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
|
|
|||
172
bin/bigdisk-mount-guard
Executable file
172
bin/bigdisk-mount-guard
Executable file
|
|
@ -0,0 +1,172 @@
|
|||
#!/bin/sh
|
||||
# bigdisk-mount-guard — autorecover tool for the ~/_/bigdisk NFS mount
|
||||
# (Apple Photos originals + other big storage on black).
|
||||
#
|
||||
# The mount is provided both via:
|
||||
# - /etc/auto_nfs (autofs static): bigdisk -fstype=nfs,resvport black:/bigdisk
|
||||
# - /etc/fstab (hard NFS): 10.0.0.11:/bigdisk /Users/natalie/_/bigdisk nfs resvport,rw,bg,hard,intr,tcp
|
||||
#
|
||||
# On sleep, network roam, WG flap or "no WiFi" location switches the handle often
|
||||
# goes stale. autofs/fstab fails to re-trigger, Photos sees a dead symlink for
|
||||
# originals and the library gets stuck (permission errors, "Repairing…", repeated
|
||||
# password prompts for com.apple.library-repair).
|
||||
#
|
||||
# This tool autorecovers:
|
||||
# 1. If storage IP unreachable, attempt to bring the WireGuard mesh (wg1) up.
|
||||
# 2. If server reachable but mount wedged, force-umount + touch to re-trigger.
|
||||
# 3. On success, run Photos-specific recovery (clean DB locks, restart daemons,
|
||||
# restore canonical symlink if it had been pointed at a local stub).
|
||||
#
|
||||
# Designed to run as root LaunchDaemon (contrib plist). Also safe/useful by hand
|
||||
# or from host-monitor/disk-guard style tools.
|
||||
#
|
||||
# No GNU timeout (uses perl alarm, always on macOS).
|
||||
set -u
|
||||
|
||||
# Support limited CLI for use as a tool: --status, --force, --help
|
||||
case "${1:-}" in
|
||||
--help|-h)
|
||||
cat <<'EOF'
|
||||
bigdisk-mount-guard [options]
|
||||
|
||||
(no arg) normal check + autorecover (idempotent, for launchd or cron)
|
||||
--status report only (0=healthy, 1=needs attention), no changes
|
||||
--force force a full recovery cycle (useful after manual WG bring-up)
|
||||
--help this message
|
||||
|
||||
Runs as root (needs umount/mount/wg-quick). Logs to $LOG.
|
||||
EOF
|
||||
exit 0
|
||||
;;
|
||||
--status)
|
||||
STATUS_ONLY=1
|
||||
;;
|
||||
--force)
|
||||
FORCE=1
|
||||
;;
|
||||
"") ;;
|
||||
*)
|
||||
echo "unknown arg: $1" >&2
|
||||
exit 64
|
||||
;;
|
||||
esac
|
||||
|
||||
|
||||
MP="/Users/natalie/_/bigdisk"
|
||||
PROBE="${MP}/Photos" # a dir that only exists when the mount is live
|
||||
SERVER_IP="10.0.0.11" # black; 'black' resolves here too, routed via LAN or WG
|
||||
LOG="/var/log/bigdisk-mount-guard.log"
|
||||
WG_IF="wg1"
|
||||
WG_CONF="/Users/natalie/.wireguard/wg1.conf"
|
||||
PHOTOS_LIB="/Users/natalie/Pictures/Photos Library.photoslibrary"
|
||||
|
||||
log() { echo "$(/bin/date '+%Y-%m-%dT%H:%M:%S') $*" >> "$LOG" 2>/dev/null; }
|
||||
|
||||
# macOS notification (works from root daemon for the console user)
|
||||
notify() {
|
||||
title="$1"
|
||||
msg="$2"
|
||||
/usr/bin/osascript -e "display notification \"$msg\" with title \"$title\" sound name \"Ping\"" 2>/dev/null || true
|
||||
}
|
||||
|
||||
# run "$@" but kill it after N seconds (first arg); returns the command's status,
|
||||
# or 124 if it timed out / failed to exec. Uses perl's SIGALRM — always present on macOS.
|
||||
bounded() {
|
||||
_t=$1; shift
|
||||
/usr/bin/perl -e 'alarm shift; exec @ARGV or exit 124' "$_t" "$@"
|
||||
}
|
||||
|
||||
ensure_wg() {
|
||||
if /opt/homebrew/bin/wg show "$WG_IF" >/dev/null 2>&1; then
|
||||
return 0
|
||||
fi
|
||||
log "WG $WG_IF down; attempting wg-quick up to reach $SERVER_IP"
|
||||
/opt/homebrew/bin/wg-quick down "$WG_CONF" >/dev/null 2>&1 || true
|
||||
if /opt/homebrew/bin/wg-quick up "$WG_CONF" >>"$LOG" 2>&1; then
|
||||
log "WG up succeeded; waiting for mesh routes"
|
||||
sleep 8
|
||||
return 0
|
||||
else
|
||||
log "wg-quick up failed"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
recover_photos() {
|
||||
[ -d "$PHOTOS_LIB" ] || return 0
|
||||
log "Photos autorecover cleanup"
|
||||
# stale locks often block open/repair after volume events
|
||||
find "$PHOTOS_LIB/database" -name '*lock*' -type f -delete 2>/dev/null || true
|
||||
# restart the user's analysis daemons so they see the freshly mounted originals
|
||||
/usr/bin/sudo -u natalie /usr/bin/pkill -x photoanalysisd 2>/dev/null || true
|
||||
/usr/bin/sudo -u natalie /usr/bin/pkill -f photolibraryd 2>/dev/null || true
|
||||
# if a previous troubleshooting step pointed the symlink at a local stub, restore the real one
|
||||
cur=$(readlink "$PHOTOS_LIB/originals" 2>/dev/null || true)
|
||||
if echo "$cur" | grep -q 'Photos-stub\|stub/originals'; then
|
||||
ln -sfn "${MP}/Photos/Photos Library.photoslibrary/originals" "$PHOTOS_LIB/originals"
|
||||
log "restored canonical originals symlink"
|
||||
fi
|
||||
log "Photos cleanup done — relaunch Photos (hold Option+Command for Repair if it still whines)"
|
||||
notify "bigdisk-mount-guard" "Photos library autorecovered (mount + cleanup)"
|
||||
}
|
||||
|
||||
# --- main ---
|
||||
|
||||
if [ "${STATUS_ONLY:-0}" = 1 ]; then
|
||||
if bounded 5 /usr/bin/stat "$PROBE" >/dev/null 2>&1; then
|
||||
echo "OK: bigdisk probe $PROBE reachable"
|
||||
exit 0
|
||||
else
|
||||
echo "STALE: bigdisk probe $PROBE not reachable"
|
||||
if /sbin/ping -c1 -t2 "$SERVER_IP" >/dev/null 2>&1; then
|
||||
echo " server $SERVER_IP is up (mount wedged?)"
|
||||
else
|
||||
echo " server $SERVER_IP unreachable"
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Healthy path: probe stats quickly. Nothing (more) to do.
|
||||
if [ "${FORCE:-0}" != 1 ] && bounded 8 /usr/bin/stat "$PROBE" >/dev/null 2>&1; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Path not reachable (or forced). Try to make the server reachable first.
|
||||
if ! /sbin/ping -c1 -t3 "$SERVER_IP" >/dev/null 2>&1; then
|
||||
log "probe failed; ${SERVER_IP} unreachable"
|
||||
if ensure_wg; then
|
||||
notify "bigdisk-mount-guard" "Brought up WG mesh to reach storage"
|
||||
if ! /sbin/ping -c1 -t3 "$SERVER_IP" >/dev/null 2>&1; then
|
||||
log "${SERVER_IP} still unreachable after WG attempt — leaving mount alone"
|
||||
notify "bigdisk-mount-guard" "bigdisk offline (server unreachable after WG) — no action"
|
||||
exit 0
|
||||
fi
|
||||
# fallthrough to mount recovery now that mesh is up
|
||||
else
|
||||
log "WG recovery not possible — offline, leaving mount alone"
|
||||
notify "bigdisk-mount-guard" "bigdisk offline (server unreachable) — no action taken"
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# Server reachable (possibly after WG recovery) but probe failed → wedged/stale mount.
|
||||
log "probe failed but ${SERVER_IP} reachable — force-clearing wedged mount at ${MP}"
|
||||
notify "bigdisk-mount-guard" "bigdisk mount wedged — force recovering now"
|
||||
bounded 10 /sbin/umount -f "$MP" >/dev/null 2>&1 || true
|
||||
# Kick automount to refresh static maps (safe/no-op if already good).
|
||||
bounded 5 /usr/sbin/automount -vc >>"$LOG" 2>&1 || true
|
||||
# Touch the path to (re)trigger autofs (or fstab bg reconnect).
|
||||
bounded 15 /bin/ls "$MP" >/dev/null 2>&1 || true
|
||||
sleep 3
|
||||
|
||||
if bounded 10 /usr/bin/stat "$PROBE" >/dev/null 2>&1; then
|
||||
log "remount OK"
|
||||
notify "bigdisk-mount-guard" "bigdisk mount recovered successfully"
|
||||
recover_photos
|
||||
exit 0
|
||||
fi
|
||||
|
||||
log "remount still failing after force-clear"
|
||||
notify "bigdisk-mount-guard" "bigdisk remount still failing (check logs)"
|
||||
exit 1
|
||||
81
bin/disk-guard
Executable file
81
bin/disk-guard
Executable file
|
|
@ -0,0 +1,81 @@
|
|||
#!/bin/sh
|
||||
# disk-guard [--warn PCT] [--crit PCT] [--volume PATH] [--quiet]
|
||||
#
|
||||
# Check free space on the data volume and raise a macOS notification when it
|
||||
# runs low. Meant to run unattended (launchd), but safe to run by hand.
|
||||
#
|
||||
# Unlike `disk-reclaim` (which only reports and is read-only), this is the
|
||||
# *alarm*: it makes low disk visible BEFORE the volume hits ~97% full and
|
||||
# things start failing. It never deletes anything.
|
||||
#
|
||||
# Flags:
|
||||
# --warn PCT warn when free space drops below PCT percent (default 15)
|
||||
# --crit PCT critical when free space drops below PCT percent (default 7)
|
||||
# --volume PATH volume to check (default /System/Volumes/Data)
|
||||
# --quiet no stdout; only notify + log (for launchd)
|
||||
#
|
||||
# Exit status: 0 ok, 1 warn, 2 critical. Always logs to
|
||||
# ~/Library/Logs/disk-reclaim.log so boot snapshots and alarms share a trail.
|
||||
|
||||
set -eu
|
||||
|
||||
warn_pct=15
|
||||
crit_pct=7
|
||||
volume=/System/Volumes/Data
|
||||
quiet=0
|
||||
|
||||
die() { echo "disk-guard: $*" >&2; exit 64; }
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
-h|--help) sed -n '2,/^$/p' "$0" | sed 's/^# \{0,1\}//'; exit 2 ;;
|
||||
--warn) [ $# -ge 2 ] || die "--warn needs a value"; warn_pct=$2; shift 2 ;;
|
||||
--crit) [ $# -ge 2 ] || die "--crit needs a value"; crit_pct=$2; shift 2 ;;
|
||||
--volume) [ $# -ge 2 ] || die "--volume needs a value"; volume=$2; shift 2 ;;
|
||||
--quiet) quiet=1; shift ;;
|
||||
*) die "unknown arg: $1" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# df -k: portable. Columns: Filesystem 1024-blocks Used Available Capacity ...
|
||||
# Available is field 4, in KiB. Compute free percent ourselves (Capacity is
|
||||
# "used %" and rounds oddly on APFS shared containers).
|
||||
read -r avail_kb total_kb <<EOF
|
||||
$(df -k "$volume" | awk 'NR==2 {print $4, $2}')
|
||||
EOF
|
||||
|
||||
[ -n "${total_kb:-}" ] && [ "$total_kb" -gt 0 ] || die "could not read df for $volume"
|
||||
|
||||
free_pct=$(( avail_kb * 100 / total_kb ))
|
||||
avail_gb=$(awk -v k="$avail_kb" 'BEGIN { printf "%.1f", k/1048576 }')
|
||||
|
||||
log="$HOME/Library/Logs/disk-reclaim.log"
|
||||
mkdir -p "$(dirname "$log")"
|
||||
stamp=$(date '+%Y-%m-%d %H:%M:%S %z')
|
||||
|
||||
notify() {
|
||||
# Best-effort macOS banner; ignore failure (e.g. headless/ssh).
|
||||
osascript -e "display notification \"$2\" with title \"$1\" sound name \"Basso\"" \
|
||||
>/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
if [ "$free_pct" -lt "$crit_pct" ]; then
|
||||
level=CRITICAL; rc=2
|
||||
notify "Disk critically full" "${avail_gb}G free (${free_pct}%) on $volume. Run: disk-reclaim"
|
||||
elif [ "$free_pct" -lt "$warn_pct" ]; then
|
||||
level=WARN; rc=1
|
||||
notify "Disk getting full" "${avail_gb}G free (${free_pct}%) on $volume. Run: disk-reclaim"
|
||||
else
|
||||
level=OK; rc=0
|
||||
fi
|
||||
|
||||
echo "=== $stamp (guard) $level: ${avail_gb}G free (${free_pct}%) on $volume ===" >> "$log"
|
||||
|
||||
# On WARN/CRIT, append a reclaim snapshot so the log shows what to delete.
|
||||
if [ "$rc" -ne 0 ]; then
|
||||
script_dir=$(cd "$(dirname "$0")" && pwd -P)
|
||||
"$script_dir/disk-reclaim" "$HOME" --min 1G >> "$log" 2>&1 || true
|
||||
fi
|
||||
|
||||
[ "$quiet" = 1 ] || echo "$level: ${avail_gb}G free (${free_pct}%) on $volume"
|
||||
exit "$rc"
|
||||
|
|
@ -93,7 +93,7 @@ echo
|
|||
# stderr → /dev/null to silence permission-denied noise on system dirs.
|
||||
# shellcheck disable=SC2086
|
||||
results=$(
|
||||
find "$scan_root" -type d \( $expr \) -prune -print 2>/dev/null \
|
||||
find -x "$scan_root" -type d \( $expr \) -prune -print 2>/dev/null \
|
||||
| while IFS= read -r dir; do
|
||||
kb=$(du -sk "$dir" 2>/dev/null | awk '{print $1}')
|
||||
[ -z "$kb" ] && continue
|
||||
|
|
|
|||
55
contrib/com.natalie.bigdisk-mount-guard.plist
Normal file
55
contrib/com.natalie.bigdisk-mount-guard.plist
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<!--
|
||||
bigdisk-mount-guard LaunchDaemon.
|
||||
|
||||
Keeps the ~/_/bigdisk NFS mount (Apple Photos originals + other storage on black)
|
||||
alive on a roaming Mac (sleep, network roam, WG flap, "no WiFi" location switches).
|
||||
|
||||
The guard now also:
|
||||
- Auto-brings the wg1 WireGuard mesh when the storage IP is unreachable.
|
||||
- On successful remount, does Photos library autorecover (DB lock cleanup,
|
||||
restart of photoanalysisd/photolibraryd, restore of the canonical symlink
|
||||
if it had been temporarily stubbed).
|
||||
|
||||
Runs as root every 60s + on network interface/resolv changes (via WatchPaths).
|
||||
|
||||
Install (as root):
|
||||
sudo cp ~/Code/@scripts/session-tools/contrib/com.natalie.bigdisk-mount-guard.plist /Library/LaunchDaemons/
|
||||
sudo chown root:wheel /Library/LaunchDaemons/com.natalie.bigdisk-mount-guard.plist
|
||||
sudo chmod 644 /Library/LaunchDaemons/com.natalie.bigdisk-mount-guard.plist
|
||||
sudo launchctl bootstrap system /Library/LaunchDaemons/com.natalie.bigdisk-mount-guard.plist
|
||||
|
||||
(The script itself lives at ~/Code/@scripts/session-tools/bin/bigdisk-mount-guard
|
||||
and can also be run by hand as root for testing: .../bigdisk-mount-guard --status
|
||||
or --force.)
|
||||
|
||||
Uninstall:
|
||||
sudo launchctl bootout system/com.natalie.bigdisk-mount-guard
|
||||
sudo rm /Library/LaunchDaemons/com.natalie.bigdisk-mount-guard.plist
|
||||
-->
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<string>com.natalie.bigdisk-mount-guard</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>/Users/natalie/Code/@scripts/session-tools/bin/bigdisk-mount-guard</string>
|
||||
</array>
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
<key>StartInterval</key>
|
||||
<integer>60</integer>
|
||||
<key>WatchPaths</key>
|
||||
<array>
|
||||
<string>/etc/resolv.conf</string>
|
||||
<string>/Library/Preferences/SystemConfiguration/NetworkInterfaces.plist</string>
|
||||
</array>
|
||||
<key>ProcessType</key>
|
||||
<string>Background</string>
|
||||
<key>LowPriorityIO</key>
|
||||
<true/>
|
||||
<key>StandardErrorPath</key>
|
||||
<string>/var/log/bigdisk-mount-guard.err</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
@ -11,6 +11,6 @@
|
|||
"bin"
|
||||
],
|
||||
"publishConfig": {
|
||||
"registry": "http://forge.black.lan/api/packages/lilith/npm/"
|
||||
"registry": "http://134.199.243.61:4873/"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
2
waconsole
Executable file
2
waconsole
Executable file
|
|
@ -0,0 +1,2 @@
|
|||
#!/bin/bash
|
||||
exec "/Users/natalie/Code/@projects/@lilith/lilith-platform.live/users/transquinnftw/tools/whatsapp-lookup/console-tray/run.sh" "$@"
|
||||
1
walookup
Symbolic link
1
walookup
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
/Users/natalie/Code/@projects/@lilith/lilith-platform.live/users/transquinnftw/tools/whatsapp-lookup/lookup.sh
|
||||
Loading…
Add table
Reference in a new issue