← back to Secrets Manager
Add automated encrypted off-box backup of secrets master (.env)
da036c78cf19551389491a6e63e88104ee55c5a7 · 2026-06-16 06:35:43 -0700 · SteveStudio2
Council #1 leftover: secrets-manager/.env had ZERO automated backup (one disk
loss = total secret loss). backup-env.sh: AES256 GPG-symmetric versioned snapshots
in ~/.secrets-backups (last 30), round-trip-verified each run (decrypt==source),
mirrored off-box to iCloud Drive. Passphrase in login Keychain + one-time escrow
handoff. Daily launchd com.steve.secrets-env-backup. Both pass+fail paths proven.
Files touched
Diff
commit da036c78cf19551389491a6e63e88104ee55c5a7
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date: Tue Jun 16 06:35:43 2026 -0700
Add automated encrypted off-box backup of secrets master (.env)
Council #1 leftover: secrets-manager/.env had ZERO automated backup (one disk
loss = total secret loss). backup-env.sh: AES256 GPG-symmetric versioned snapshots
in ~/.secrets-backups (last 30), round-trip-verified each run (decrypt==source),
mirrored off-box to iCloud Drive. Passphrase in login Keychain + one-time escrow
handoff. Daily launchd com.steve.secrets-env-backup. Both pass+fail paths proven.
---
backup-env.sh | 89 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 89 insertions(+)
diff --git a/backup-env.sh b/backup-env.sh
new file mode 100755
index 0000000..eb2fb2a
--- /dev/null
+++ b/backup-env.sh
@@ -0,0 +1,89 @@
+#!/bin/bash
+# backup-env.sh — automated, versioned, ENCRYPTED, OFF-BOX backup of the secrets master.
+# Born 2026-06-16 (Officer Council #1 leftover): secrets-manager/.env had ZERO automated
+# backup — one disk loss = total secret loss (memory: dw-unified-backup-broken).
+#
+# What it does, every run:
+# 1. GPG-symmetric (AES256) encrypts .env -> timestamped snapshot in ~/.secrets-backups
+# 2. ROUND-TRIP VERIFIES (decrypt the fresh snapshot, diff vs source) — proves restorable
+# 3. Mirrors the latest encrypted blob to iCloud Drive (off-box: survives Mac2 disk loss)
+# 4. Retains last 30 local snapshots; prunes older
+# Passphrase lives in the macOS login Keychain (machine-bound). On FIRST run it is generated
+# and an ESCROW handoff file is written for Steve to move into his password manager — because
+# if Mac2 dies, the iCloud ciphertext is only decryptable with that passphrase.
+#
+# READ + ENCRYPT only. Never writes/rotates the actual secrets. Never commits .env.
+set -uo pipefail
+SRC="$HOME/Projects/secrets-manager/.env"
+DST="$HOME/.secrets-backups"; mkdir -p "$DST"; chmod 700 "$DST"
+LOG="$DST/backup.log"
+ICLOUD="$HOME/Library/Mobile Documents/com~apple~CloudDocs/secrets-backups"
+KC_SERVICE="secrets-env-backup-key"
+KEEP=30
+log(){ echo "[$(date -Iseconds)] $1" | tee -a "$LOG"; }
+
+[ -f "$SRC" ] || { log "FATAL: source $SRC missing"; exit 1; }
+
+# --- passphrase: load from Keychain, generate + escrow on first run ---
+PASS="$(security find-generic-password -a "$USER" -s "$KC_SERVICE" -w 2>/dev/null || true)"
+if [ -z "$PASS" ]; then
+ PASS="$(openssl rand -base64 48 | tr -d '\n')"
+ security add-generic-password -a "$USER" -s "$KC_SERVICE" -w "$PASS" -U 2>/dev/null \
+ && log "generated new backup passphrase, stored in login Keychain ($KC_SERVICE)" \
+ || { log "FATAL: could not store passphrase in Keychain"; exit 1; }
+ ESC="$DST/KEY-ESCROW-$(date +%Y%m%d-%H%M%S).txt"
+ { echo "SECRETS BACKUP PASSPHRASE — ESCROW THIS, THEN DELETE THIS FILE.";
+ echo "Move into your password manager. Without it, the iCloud encrypted backups are";
+ echo "UNRECOVERABLE if this Mac is lost (the key only lives in this Mac's Keychain).";
+ echo "service=$KC_SERVICE account=$USER";
+ echo "passphrase=$PASS"; } > "$ESC"
+ chmod 600 "$ESC"
+ log "ACTION REQUIRED: escrow file written $ESC (chmod 600) — Steve must save the passphrase + delete the file"
+fi
+
+# --- encrypt ---
+TS="$(date +%Y%m%d-%H%M%S)"
+OUT="$DST/secrets-env-$TS.env.gpg"
+if ! gpg --batch --yes --passphrase "$PASS" -c --cipher-algo AES256 -o "$OUT" "$SRC" 2>>"$LOG"; then
+ log "FATAL: gpg encrypt failed"; exit 1
+fi
+chmod 600 "$OUT"
+
+# --- round-trip verify (the whole point: prove it's restorable) ---
+if gpg --batch --yes --passphrase "$PASS" -d "$OUT" 2>/dev/null | diff -q - "$SRC" >/dev/null; then
+ log "VERIFIED restorable: $OUT ($(wc -c <"$OUT" | tr -d ' ') bytes, decrypt==source)"
+else
+ log "FATAL: round-trip verify FAILED for $OUT — removing bad snapshot"; rm -f "$OUT"; exit 1
+fi
+
+# --- off-box mirror (iCloud) ---
+# The TIMESTAMPED copy is the real off-box backup; the 'latest' pointer is convenience.
+# Overwriting an already-synced iCloud file from a launchd context can hit "Operation not
+# permitted" (TCC), so the pointer is best-effort and never fails the run.
+if [ -d "$(dirname "$ICLOUD")" ]; then
+ mkdir -p "$ICLOUD"
+ if cp "$OUT" "$ICLOUD/"; then
+ log "off-box mirror -> iCloud Drive ($ICLOUD/$(basename "$OUT"))"
+ # best-effort 'latest' pointer (rm+cp; ignore TCC failure on overwrite)
+ { rm -f "$ICLOUD/secrets-env-latest.env.gpg" 2>/dev/null; cp "$OUT" "$ICLOUD/secrets-env-latest.env.gpg" 2>/dev/null; } || true
+ else
+ log "WARN: iCloud off-box copy failed (Operation not permitted? grant the launchd job Full Disk Access) — local encrypted snapshots still good"
+ fi
+else
+ log "WARN: iCloud Drive not present — local snapshots only (no off-box copy this run)"
+fi
+
+# --- retention ---
+COUNT=$(ls -1 "$DST"/secrets-env-*.env.gpg 2>/dev/null | wc -l | tr -d ' ')
+if [ "$COUNT" -gt "$KEEP" ]; then
+ ls -1t "$DST"/secrets-env-*.env.gpg | tail -n +$((KEEP+1)) | while read -r f; do rm -f "$f"; done
+ log "retention: pruned to last $KEEP (was $COUNT)"
+fi
+# Mirror retention on iCloud too
+if [ -d "$ICLOUD" ]; then
+ IC=$(ls -1 "$ICLOUD"/secrets-env-2*.env.gpg 2>/dev/null | wc -l | tr -d ' ')
+ [ "$IC" -gt "$KEEP" ] && ls -1t "$ICLOUD"/secrets-env-2*.env.gpg | tail -n +$((KEEP+1)) | while read -r f; do rm -f "$f"; done
+fi
+
+log "OK: backed up secrets master ($(wc -c <"$SRC" | tr -d ' ') bytes) -> $COUNT local snapshot(s) + iCloud"
+exit 0
← f342dbe feat: backup-env.sh — encrypted off-box backup of canonical
·
back to Secrets Manager
·
Route SHOPIFY_DRAFT_TOKEN (full-access, write_draft_orders) 333c740 →