backup: 2026-07-11T07:54:39-05:00
This commit is contained in:
+68
-64
@@ -1,50 +1,41 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Backup NexusOS to the router. All modes rsync into the canonical nexus-core
|
||||
# folder over the normal SSH daemon (host: router, port 9001) — the SAME folder
|
||||
# restore.sh pulls from, so backup and restore stay in sync.
|
||||
# Backup NexusOS via git → Gitea (enderofwings/NexusOS). Replaces the old
|
||||
# rsync-to-router backup. Commits the tracked tree (source, config, playbooks,
|
||||
# branding, docs) plus a SQL dump of the memory DB, then pushes to the remote —
|
||||
# so a fresh clone + restore.sh fully rebuilds the machine.
|
||||
#
|
||||
# backup.sh Fast incremental backup.
|
||||
# backup.sh full Same, but first snapshots the live desktop wiring
|
||||
# and retains runtime/ (minus logs) for a fuller copy.
|
||||
# backup.sh -c | --claude Dry-run: show what a backup WOULD push (content-
|
||||
# level), change nothing. For deciding whether an
|
||||
# actual backup is needed.
|
||||
# backup.sh Commit changed files + push.
|
||||
# backup.sh full | -f Also snapshot live desktop wiring + Claude notes first.
|
||||
# backup.sh -c | --claude Dry-run: show what a backup WOULD commit/push. No changes.
|
||||
#
|
||||
# The regenerable heavies (Promethean venv, ollama binary, models/, node_modules,
|
||||
# dist, runtime logs) are intentionally NOT backed up — restore.sh rebuilds them
|
||||
# from requirements-*.txt / package-lock.json / `ollama pull`.
|
||||
|
||||
NEXUS_ROOT="$HOME/nexus-core"
|
||||
DEST="router:/tmp/mnt/Wingdrive2/nexus-core/"
|
||||
mode="$1"
|
||||
cd "$NEXUS_ROOT" || { echo "No $NEXUS_ROOT"; exit 1; }
|
||||
mode="${1:-}"
|
||||
|
||||
# Standard (incremental) excludes — shared by the default backup AND the
|
||||
# -c/--claude check, so the dry-run accurately previews a real backup.
|
||||
EXCLUDES=(
|
||||
--exclude='.git/'
|
||||
--exclude='Promethean/'
|
||||
--exclude='models/blobs/'
|
||||
--exclude='ollama/'
|
||||
--exclude='interface/web/node_modules/'
|
||||
--exclude='interface/web/dist/'
|
||||
--exclude='runtime/'
|
||||
--exclude='__pycache__/'
|
||||
--exclude='*.pyc'
|
||||
)
|
||||
DB="synapse/memory/memory.db"
|
||||
DB_SQL="synapse/memory/memory.db.sql"
|
||||
|
||||
# Check mode (-c|--claude|check): content-level dry-run, no transfer. Lists what
|
||||
# a standard backup would push to the router (and delete there). --checksum
|
||||
# compares by content (not size/mtime), so only genuine differences show.
|
||||
if [ "$mode" = "-c" ] || [ "$mode" = "--claude" ] || [ "$mode" = "check" ]; then
|
||||
echo "Checking backup diff vs router (dry-run, content-level, no changes)..."
|
||||
rsync -rlni --checksum --delete "${EXCLUDES[@]}" -e ssh "$NEXUS_ROOT/" "$DEST"
|
||||
echo ""
|
||||
echo "(dry-run only — nothing pushed. Apply with: backup or backup full)"
|
||||
exit 0
|
||||
fi
|
||||
# Dump the (gitignored, binary, WAL) memory DB to a diff-friendly SQL file so git
|
||||
# backs up the assistant's memory + conversation history. sqlite3 reads through
|
||||
# the WAL, so the dump includes the latest committed data without a checkpoint.
|
||||
dump_db() {
|
||||
[ -f "$DB" ] || return 0
|
||||
if sqlite3 "$DB" ".dump" > "$DB_SQL" 2>/dev/null; then
|
||||
echo "Dumped memory DB → $DB_SQL"
|
||||
else
|
||||
echo "Warning: could not dump $DB (sqlite3 missing?) — DB not captured"
|
||||
fi
|
||||
}
|
||||
|
||||
if [ "$mode" = "full" ] || [ "$mode" = "-f" ]; then
|
||||
# Snapshot the live NexusOS desktop wiring into the repo before backing up,
|
||||
# so the backup captures the ACTUAL current state (not just the canonical
|
||||
# defaults baked into assets/themes/install-theme.sh). This is a reference
|
||||
# copy for recovery/diffing; install-theme.sh remains the applier on restore.
|
||||
# full mode: snapshot the LIVE desktop wiring into the repo (reference copy for
|
||||
# recovery/diffing; install-theme.sh remains the applier on restore), and copy
|
||||
# the Claude memory notes in so git commits their content.
|
||||
snapshot_desktop() {
|
||||
snap="$NEXUS_ROOT/assets/themes/restore-snapshot"
|
||||
mkdir -p "$snap"
|
||||
{
|
||||
@@ -61,33 +52,46 @@ if [ "$mode" = "full" ] || [ "$mode" = "-f" ]; then
|
||||
cp -f "$HOME/.config/xfce4/panel/genmon-13.rc" \
|
||||
"$NEXUS_ROOT/management/panel/genmon-13.rc" 2>/dev/null || true
|
||||
|
||||
# Symlink the Claude memory notes (machine knowledge that lives outside the
|
||||
# repo under ~/.claude) into assets/notes/ so the backup below captures them.
|
||||
# rsync runs with -L (copy-links), so the links resolve to real content on
|
||||
# the backup target. Relative links + a loop so new notes are picked up too.
|
||||
# Some of these notes aren't Nexus-specific (Fusion 360, distro/Wine).
|
||||
# Copy the Claude memory notes (machine knowledge under ~/.claude) into the
|
||||
# repo so they're versioned too. Some aren't Nexus-specific (Fusion 360, etc.).
|
||||
notes_src="$HOME/.claude/projects/-home-jon-nexus-core/memory"
|
||||
notes_dst="$NEXUS_ROOT/assets/notes"
|
||||
mkdir -p "$notes_dst"
|
||||
for f in "$notes_src"/*.md; do
|
||||
[ -e "$f" ] || continue
|
||||
ln -sfr "$f" "$notes_dst/$(basename "$f")"
|
||||
done
|
||||
cp -f "$notes_src"/*.md "$notes_dst/" 2>/dev/null || true
|
||||
}
|
||||
|
||||
rsync -avzL --no-owner --no-group --delete \
|
||||
--exclude='.git/' \
|
||||
--exclude='Promethean/' \
|
||||
--exclude='models/blobs/' \
|
||||
--exclude='ollama/' \
|
||||
--exclude='interface/web/node_modules/' \
|
||||
--exclude='interface/web/dist/' \
|
||||
--exclude='runtime/logs/' \
|
||||
--exclude='runtime/backend.log' \
|
||||
--exclude='runtime/frontend.log' \
|
||||
--exclude='__pycache__/' \
|
||||
--exclude='*.pyc' \
|
||||
-e ssh \
|
||||
"$NEXUS_ROOT/" "$DEST"
|
||||
else
|
||||
rsync -avz --no-owner --no-group --delete "${EXCLUDES[@]}" -e ssh "$NEXUS_ROOT/" "$DEST"
|
||||
# Check mode: dry-run. Show what a real backup would commit and push.
|
||||
if [ "$mode" = "-c" ] || [ "$mode" = "--claude" ] || [ "$mode" = "check" ]; then
|
||||
dump_db
|
||||
echo ""
|
||||
echo "Files a backup would commit:"
|
||||
git status --short
|
||||
echo ""
|
||||
echo "Local commits not yet pushed:"
|
||||
git log --oneline @{u}.. 2>/dev/null || echo "(none / no upstream)"
|
||||
echo ""
|
||||
echo "(dry-run only — nothing committed or pushed. Apply with: backup)"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "$mode" = "full" ] || [ "$mode" = "-f" ]; then
|
||||
snapshot_desktop
|
||||
fi
|
||||
|
||||
dump_db
|
||||
git add -A
|
||||
if git diff --cached --quiet; then
|
||||
echo "No changes to commit."
|
||||
else
|
||||
git commit -q -m "backup: $(date -Is)"
|
||||
echo "Committed backup snapshot."
|
||||
fi
|
||||
|
||||
echo "Pushing to Gitea..."
|
||||
if git push origin main; then
|
||||
echo "Backup complete."
|
||||
else
|
||||
echo "Push failed. Set up credentials once with:"
|
||||
echo " git config credential.helper store # then push once and enter your Gitea token"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user