Skill: little7unifai/little7-bcdr (version 0.1.1)

--- name: little7-bcdr description: Create, maintain, and verify Little7 business continuity and disaster recovery backups for identity, memory, learnings, scripts, and local skills. Use when setting up silent daily or weekly backup automation, pruning retention, preparing Google Drive continuity archives, running a weekly backup health check, testing restore readiness, or updating the Little7 BC/DR workflow. --- # Little7 BC/DR Preserve Little7 continuity with quiet, repeatable backups. ## Core workflow 1. Run `scripts/little7_backup.sh daily` for routine snapshots. 2. Run `scripts/little7_backup.sh weekly` for the slower weekly snapshot. 3. Run `scripts/little7_backup_healthcheck.sh` weekly to verify the latest daily and weekly archives are still present and structurally sane. 4. Verify archive contents before claiming restore readiness. 5. Keep status updates short and factual. ## Automation stance - For cron-driven backups, prefer silent success. - Notify the user only on failure, blocked destination access, missing archives, or when the user explicitly asks for confirmation. - A weekly health check is the preferred visibility point for ongoing backup confidence. ## Backup targets Default inclusion is defined in `scripts/little7_backup.sh`. Preserve: - identity files - curated memory - learnings - local scripts - local skills - selected ledgers/docs Exclude unless explicitly allowlisted: - secrets - tokens - browser profiles - caches - dependency trees - disposable temp artifacts ## Destination model Primary destination defaults to `${LITTLE7_GDRIVE_BASE:-$HOME/Google Drive/Little7}` with: - `daily/` - `weekly/` - `latest/` - `restore-notes/` Do not assume the exact path when automating on another machine; check env overrides first. ## Retention - Daily: keep 180 copies - Weekly: keep 312 copies Prune only after a new archive is successfully created. ## Restore discipline When verifying BC/DR readiness, do not stop at filenames. At minimum: 1. list archive contents 2. confirm critical files are present 3. report missing identity or memory files clearly 4. confirm the separate secrets archive exists when a secrets allowlist is configured ## Resources - Backup implementation: `scripts/little7_backup.sh` - Weekly verification: `scripts/little7_backup_healthcheck.sh`

path: _meta.json
content: ownerId: kn78k9g7d3nt0gfs6wwtvgkwd984sbwm
slug: little7-bcdr
version: 0.1.0
publishedAt: 1778453487188

path: scripts/little7_backup.sh
content: #!/usr/bin/env bash
set -euo pipefail
shopt -s nullglob globstar

MODE="${1:-daily}"
case "$MODE" in
  daily) KEEP=180 ;;
  weekly) KEEP=312 ;;
  *)
    echo "Usage: $0 daily|weekly" >&2
    exit 1
    ;;
esac

ROOT="${LITTLE7_WORKSPACE_ROOT:-$HOME/.openclaw/workspace}"
DRIVE_BASE="${LITTLE7_GDRIVE_BASE:-$HOME/Google Drive/Little7}"
DEFAULT_SECRET_PATHS_FILE="$ROOT/docs/LITTLE7_SECRET_PATHS.txt"
SECRET_PATHS_FILE="${LITTLE7_SECRET_PATHS_FILE:-$DEFAULT_SECRET_PATHS_FILE}"
DATE="$(date +%F)"
TMP_BASE="${TMPDIR:-/tmp}/little7-backup-${MODE}-${DATE}-$$"
STAGE_STATE="$TMP_BASE/state"
STAGE_SECRETS="$TMP_BASE/secrets"
OUT_DIR="$DRIVE_BASE/$MODE"
LATEST_DIR="$DRIVE_BASE/latest"
RESTORE_DIR="$DRIVE_BASE/restore-notes"
STATE_NAME="little7-${MODE}-${DATE}.tar.gz"
SECRETS_NAME="little7-${MODE}-secrets-${DATE}.tar.gz"
STATE_OUT="$OUT_DIR/$STATE_NAME"
SECRETS_OUT="$OUT_DIR/$SECRETS_NAME"
LATEST_STATE="$LATEST_DIR/little7-${MODE}-latest.tar.gz"
LATEST_SECRETS="$LATEST_DIR/little7-${MODE}-secrets-latest.tar.gz"
MANIFEST="$TMP_BASE/manifest.txt"

mkdir -p "$STAGE_STATE" "$STAGE_SECRETS" "$OUT_DIR" "$LATEST_DIR" "$RESTORE_DIR"

cleanup() { rm -rf "$TMP_BASE"; }
trap cleanup EXIT

copy_rel() {
  local rel="$1"
  local src="$ROOT/$rel"
  [ -e "$src" ] || return 0
  mkdir -p "$STAGE_STATE/$(dirname "$rel")"
  cp -a "$src" "$STAGE_STATE/$rel"
}

should_skip_state_path() {
  local path="$1"
  case "$path" in
    */.git/*|*/node_modules/*|*/.npm/*|*/.cache/*|*/.runtime/*)
      return 0
      ;;
    *)
      return 1
      ;;
  esac
}

copy_abs_secret() {
  local src="$1"
  [ -e "$src" ] || return 0
  local rel="${src#/}"
  mkdir -p "$STAGE_SECRETS/$(dirname "$rel")"
  cp -a "$src" "$STAGE_SECRETS/$rel"
}

STATE_FILES=(
  AGENTS.md
  SOUL.md
  USER.md
  IDENTITY.md
  MEMORY.md
  TOOLS.md
  HEARTBEAT.md
)

OPTIONAL_FILES=(
  TASK_LEDGER.md
  WORK_LEDGER.md
  BACKLOG_LEDGER.md
  AGILE_LEDGER.md
  DOUBT_LEDGER.md
  DISASTER_RECOVERY.md
)

for rel in "${STATE_FILES[@]}"; do copy_rel "$rel"; done
for rel in "${OPTIONAL_FILES[@]}"; do copy_rel "$rel"; done
for path in "$ROOT"/memory/**/*.md "$ROOT"/memory/**/*.json; do
  [ -e "$path" ] || continue
  copy_rel "${path#$ROOT/}"
done
for path in "$ROOT"/.learnings/LEARNINGS.md "$ROOT"/.learnings/ERRORS.md "$ROOT"/.learnings/FEATURE_REQUESTS.md; do
  [ -e "$path" ] || continue
  copy_rel "${path#$ROOT/}"
done
for path in $(find "$ROOT/scripts" "$ROOT/skills" -type f 2>/dev/null | sort); do
  should_skip_state_path "$path" && continue
  copy_rel "${path#$ROOT/}"
done

if [ -n "$SECRET_PATHS_FILE" ] && [ -f "$SECRET_PATHS_FILE" ]; then
  while IFS= read -r line; do
    line="${line#${line%%[![:space:]]*}}"
    line="${line%${line##*[![:space:]]}}"
    [ -n "$line" ] || continue
    case "$line" in
      \#*) continue ;;
    esac
    copy_abs_secret "$line"
  done < "$SECRET_PATHS_FILE"
fi

cat > "$STAGE_STATE/RESTORE_NOTES.txt" <<RESTORE
Little7 backup ($MODE) created on $DATE.

State archive:
- identity, memory, learnings, scripts, skills, and selected docs
- excludes caches, browser profiles, git metadata, node_modules, and raw session histories

Secrets archive:
- built from the explicit allowlist in LITTLE7_SECRET_PATHS_FILE
- defaults locally to docs/LITTLE7_SECRET_PATHS.txt when present
- keep secret recovery material separate and tightly permissioned

Restore order:
1. Restore core files into the target workspace
2. Restore secrets only onto a trusted machine and set strict permissions
3. Reconnect provider/tooling paths as needed
RESTORE

cat > "$STAGE_SECRETS/README.txt" <<'SECRETS'
Sensitive Little7 continuity material.
Restore only onto a trusted machine.
Recommended permissions after restore:
- private keys: 600
- ~/.ssh directory: 700
SECRETS

( cd "$STAGE_STATE" && find . -type f | sort ) > "$MANIFEST"
{
  echo "Mode: $MODE"
  echo "Date: $DATE"
  echo "State files:"
  sed 's#^#  #g' "$MANIFEST"
  echo "Secrets files:"
  ( cd "$STAGE_SECRETS" && find . -type f | sort | sed 's#^#  #g' )
} > "$RESTORE_DIR/little7-${MODE}-${DATE}-manifest.txt"

( cd "$STAGE_STATE" && tar czf "$STATE_OUT" . )
( cd "$STAGE_SECRETS" && tar czf "$SECRETS_OUT" . )
cp -f "$STATE_OUT" "$LATEST_STATE"
cp -f "$SECRETS_OUT" "$LATEST_SECRETS"
cp -f "$RESTORE_DIR/little7-${MODE}-${DATE}-manifest.txt" "$RESTORE_DIR/little7-${MODE}-latest-manifest.txt"

verify_contains() {
  local archive="$1"
  local needle="$2"
  tar tzf "$archive" | awk -v needle="$needle" '$0 == needle { found = 1 } END { exit found ? 0 : 1 }'
}

verify_contains "$STATE_OUT" "./AGENTS.md"
verify_contains "$STATE_OUT" "./MEMORY.md"
verify_contains "$STATE_OUT" "./scripts/little7_backup.sh"
verify_contains "$SECRETS_OUT" "./README.txt"

prune_keep() {
  local dir="$1"
  local pattern="$2"
  local keep="$3"
  mapfile -t files < <(find "$dir" -maxdepth 1 -type f -name "$pattern" | sort)
  local count="${#files[@]}"
  if (( count <= keep )); then
    return 0
  fi
  local remove_count=$((count - keep))
  for ((i=0; i<remove_count; i++)); do
    rm -f "${files[$i]}"
  done
}

prune_keep "$OUT_DIR" "little7-${MODE}-????-??-??.tar.gz" "$KEEP"
prune_keep "$OUT_DIR" "little7-${MODE}-secrets-????-??-??.tar.gz" "$KEEP"
prune_keep "$RESTORE_DIR" "little7-${MODE}-????-??-??-manifest.txt" "$KEEP"

printf 'Created:\n- %s\n- %s\n' "$STATE_OUT" "$SECRETS_OUT"


path: scripts/little7_backup_healthcheck.sh
content: #!/usr/bin/env bash
set -euo pipefail

DRIVE_BASE="${LITTLE7_GDRIVE_BASE:-$HOME/Google Drive/Little7}"
LATEST_DIR="$DRIVE_BASE/latest"
RESTORE_DIR="$DRIVE_BASE/restore-notes"

DAILY_STATE="$LATEST_DIR/little7-daily-latest.tar.gz"
DAILY_SECRETS="$LATEST_DIR/little7-daily-secrets-latest.tar.gz"
WEEKLY_STATE="$LATEST_DIR/little7-weekly-latest.tar.gz"
WEEKLY_SECRETS="$LATEST_DIR/little7-weekly-secrets-latest.tar.gz"
DAILY_MANIFEST="$RESTORE_DIR/little7-daily-latest-manifest.txt"
WEEKLY_MANIFEST="$RESTORE_DIR/little7-weekly-latest-manifest.txt"

require_file() {
  local path="$1"
  [ -f "$path" ] || { echo "Missing required file: $path" >&2; exit 1; }
}

verify_member() {
  local archive="$1"
  local needle="$2"
  tar tzf "$archive" | grep -Fx "$needle" >/dev/null
}

for path in   "$DAILY_STATE"   "$DAILY_SECRETS"   "$WEEKLY_STATE"   "$WEEKLY_SECRETS"   "$DAILY_MANIFEST"   "$WEEKLY_MANIFEST"
do
  require_file "$path"
done

verify_member "$DAILY_STATE" './AGENTS.md'
verify_member "$DAILY_STATE" './MEMORY.md'
verify_member "$DAILY_STATE" './scripts/little7_backup.sh'
verify_member "$WEEKLY_STATE" './AGENTS.md'
verify_member "$WEEKLY_STATE" './MEMORY.md'
verify_member "$WEEKLY_STATE" './scripts/little7_backup.sh'
verify_member "$DAILY_SECRETS" './README.txt'
verify_member "$WEEKLY_SECRETS" './README.txt'

grep -F 'Mode: daily' "$DAILY_MANIFEST" >/dev/null
grep -F 'Mode: weekly' "$WEEKLY_MANIFEST" >/dev/null

printf 'backup healthcheck ok
'
printf 'daily state: %s bytes
' "$(stat -c %s "$DAILY_STATE")"
printf 'weekly state: %s bytes
' "$(stat -c %s "$WEEKLY_STATE")"


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (MEDIUM)