← back to Secrets Manager
postbubbe-rotation-reminder.sh
63 lines
#!/usr/bin/env bash
# postbubbe-rotation-reminder.sh
# Durable daily reminder for the OUTSTANDING post-bubbe-RCE Tier-1 secret rotation.
# Compares each Tier-1 key's current registry digest against the 2026-05-31
# baseline; a key is ROTATED when its digest changes (Steve regenerates in the
# provider console, then `cli.js import-paste` rewrites the digest).
# - any still un-rotated -> macOS notification + log line, exit 0
# - ALL rotated -> success notification, SELF-RETIRE (unload + disable
# the LaunchAgent), drop a RESOLVED marker, exit 0
# Self-contained, no args. Driven by com.steve.postbubbe-rotation-reminder.
set -uo pipefail
SM="$HOME/Projects/secrets-manager"
BASELINE="$SM/postbubbe-tier1-baseline.json"
REGISTRY="$SM/registry.json"
LOG="$SM/postbubbe-rotation-reminder.log"
PLIST="$HOME/Library/LaunchAgents/com.steve.postbubbe-rotation-reminder.plist"
LABEL="com.steve.postbubbe-rotation-reminder"
MARKER="$SM/.postbubbe-rotation-RESOLVED"
STAMP="$(date '+%Y-%m-%d %H:%M:%S')"
note(){ printf '[%s] %s\n' "$STAMP" "$*" >> "$LOG"; }
notify(){ # title, message
/usr/bin/osascript -e "display notification \"$2\" with title \"$1\" sound name \"Glass\"" >/dev/null 2>&1 || true
}
[ -f "$BASELINE" ] || { note "ERROR baseline missing: $BASELINE"; exit 0; }
[ -f "$REGISTRY" ] || { note "ERROR registry missing: $REGISTRY"; exit 0; }
# Compare digests in node (jq may be absent); print "ROTATED a b" / "PENDING x y".
RESULT="$(BASELINE="$BASELINE" REGISTRY="$REGISTRY" /usr/bin/env node -e '
const fs=require("fs");
const base=JSON.parse(fs.readFileSync(process.env.BASELINE,"utf8")).keys||{};
const reg=JSON.parse(fs.readFileSync(process.env.REGISTRY,"utf8")).secrets||{};
const pending=[], rotated=[];
for(const k of Object.keys(base)){
const was=base[k].digest||null;
const now=(reg[k]&®[k].digest)||null;
if(now && was && now!==was) rotated.push(k); else pending.push(k);
}
console.log("PENDING\t"+pending.join(","));
console.log("ROTATED\t"+rotated.join(","));
' 2>>"$LOG")"
PENDING="$(printf '%s\n' "$RESULT" | awk -F'\t' '/^PENDING/{print $2}')"
ROTATED="$(printf '%s\n' "$RESULT" | awk -F'\t' '/^ROTATED/{print $2}')"
PCOUNT=0; [ -n "$PENDING" ] && PCOUNT="$(printf '%s' "$PENDING" | tr ',' '\n' | grep -c .)"
if [ "$PCOUNT" -eq 0 ]; then
note "ALL Tier-1 keys rotated ($ROTATED). Retiring reminder."
notify "✅ Post-bubbe rotation complete" "All Tier-1 keys rotated. Retiring the daily reminder."
printf 'RESOLVED %s\nrotated: %s\n' "$STAMP" "$ROTATED" > "$MARKER"
# Self-retire the LaunchAgent (modern bootout, fall back to legacy unload).
launchctl bootout "gui/$(id -u)/$LABEL" 2>/dev/null || launchctl unload "$PLIST" 2>/dev/null || true
# Disable RunAtLoad/relaunch by renaming the plist aside so it won't reload.
[ -f "$PLIST" ] && mv "$PLIST" "$PLIST.retired-$(date +%Y%m%d)" 2>/dev/null || true
exit 0
fi
note "STILL OUTSTANDING ($PCOUNT): $PENDING"
notify "🔒 Rotate $PCOUNT post-bubbe keys" "Still live & unrotated: ${PENDING}. Regenerate in console → cli.js import-paste. Order: CF/GoDaddy/Stripe/Shopify → OpenAI/Gemini/ElevenLabs."
exit 0