Closes the last gap from docs/incidents.md #5. The hourly local snapshots survive `git clean -fdx` because they live outside the repo, but they do not survive the disk. This pushes the irreplaceable-and-small state to a private Gitea repo: .omo plans and evidence ledger, tuned systemd units, opencode plugins, this project's Claude memory, config/config.local.yaml, and a DAILY gzipped router.db. The database is committed daily rather than hourly on purpose: it is binary and ~5MB gzipped, so git cannot delta it. Hourly would grow the repo ~120MB/day instead of ~5MB. SYNC_DB=0 turns it off entirely. .env is deliberately NOT synced. There is no usable secret key on this host to encrypt it to (public keys only), so it would sit in git history in plaintext, and history is forever even in a private repo. An API key is replaceable by regenerating it from the provider; 22,821 energy observations are not. It stays in the local backups only, and the user confirmed that trade. Verified by fresh clone: 35 plan artifacts, 127 evidence files, 9 systemd units, 2 opencode plugins, 11 memory files, the tariff, and a 5.1MB db snapshot. Checked the actual 67-character key VALUE appears in zero off-site files -- an earlier check grepped for the variable NAME and for 'sk-', which matches 'task-', and produced 50 false positives. Grep for the secret, not for its label. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VRQXz5SYZYVWscxS1QqF6U
86 lines
3.9 KiB
Bash
Executable File
86 lines
3.9 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Push the irreplaceable-and-small state to a private off-site git repo.
|
|
#
|
|
# Closes the last gap from docs/incidents.md #5: local snapshots survive
|
|
# `git clean -fdx` because they live outside the repo, but they do NOT survive
|
|
# the disk. This does.
|
|
#
|
|
# WHAT IS SYNCED
|
|
# .omo/ plan artifacts + evidence ledger (13MB, text, versions well)
|
|
# systemd units tuned: graceful-shutdown fix, timers
|
|
# opencode plugins session-registry.js etc. -- hand-written, not from a package
|
|
# claude memory cross-session project memory
|
|
# config.local.yaml operator tariff/overrides (personal, NOT a credential)
|
|
# router.db gzipped, at most once per day -- see SIZE below
|
|
#
|
|
# WHAT IS DELIBERATELY NOT SYNCED
|
|
# .env — the provider API key. There is no usable secret key on this host to
|
|
# encrypt it to (only public keys), so it would land in git history in
|
|
# plaintext, and git history is forever even in a private repo. An API
|
|
# key is REPLACEABLE: regenerate it from the provider. 22,000 energy
|
|
# observations are not. It stays in the local backups only.
|
|
# .venv, node_modules — rebuildable from pinned requirements.
|
|
# ollama models — ~30GB, re-pullable; recipes in docs/local-models.md.
|
|
#
|
|
# SIZE: router.db is ~5MB gzipped and binary, so git cannot delta it. It is
|
|
# committed at most DAILY (not hourly) to keep growth near 5MB/day rather than
|
|
# 120MB/day. Set SYNC_DB=0 to skip it entirely and rely on local snapshots.
|
|
set -euo pipefail
|
|
|
|
REPO="${REPO:-$HOME/Sources/6krrt}"
|
|
REMOTE="${OFFSITE_REMOTE:-ssh://git@git.adlee.work:2222/alee/6kbackups.git}"
|
|
WORK="${OFFSITE_WORKDIR:-$HOME/.local/share/6krrt-offsite}"
|
|
SYNC_DB="${SYNC_DB:-1}"
|
|
|
|
if [ ! -d "$WORK/.git" ]; then
|
|
git clone "$REMOTE" "$WORK" 2>/dev/null || { mkdir -p "$WORK"; git -C "$WORK" init -q; git -C "$WORK" remote add origin "$REMOTE"; }
|
|
fi
|
|
cd "$WORK"
|
|
git fetch -q origin 2>/dev/null || true
|
|
git checkout -q -B main 2>/dev/null || true
|
|
git reset -q --hard origin/main 2>/dev/null || true
|
|
|
|
rsync -a --delete "$REPO/.omo/" ./omo/ 2>/dev/null || true
|
|
mkdir -p home config
|
|
rsync -a --delete "$HOME/.config/systemd/user/" ./home/systemd-user/ 2>/dev/null || true
|
|
rsync -a --delete "$HOME/.config/opencode/" ./home/opencode/ --exclude 'cache/' --exclude 'log/' --exclude '*.log' 2>/dev/null || true
|
|
rsync -a --delete "$HOME/.claude/projects/-home-alee-Sources-6krrt/memory/" ./home/claude-memory/ 2>/dev/null || true
|
|
[ -f "$REPO/config/config.local.yaml" ] && cp -f "$REPO/config/config.local.yaml" ./config/
|
|
|
|
# Daily DB snapshot, same filename so the working tree stays flat.
|
|
if [ "$SYNC_DB" = "1" ]; then
|
|
today=$(date +%Y-%m-%d)
|
|
if [ ! -f .db-stamp ] || [ "$(cat .db-stamp)" != "$today" ]; then
|
|
tmp=$(mktemp)
|
|
sqlite3 "$REPO/router.db" ".backup '$tmp'"
|
|
[ "$(sqlite3 "$tmp" 'SELECT integrity_check FROM pragma_integrity_check LIMIT 1;')" = "ok" ] \
|
|
&& { gzip -c "$tmp" > router.db.gz; echo "$today" > .db-stamp; } \
|
|
|| echo "db snapshot failed integrity_check; not synced" >&2
|
|
rm -f "$tmp"
|
|
fi
|
|
fi
|
|
|
|
cat > README.md <<'INNER'
|
|
# 6krrt workstation backup
|
|
|
|
Off-site copy of state the main repo does not carry. See
|
|
`docs/incidents.md` #5 in the main repo for why this exists.
|
|
|
|
**Not here on purpose:** `.env` (provider API key — regenerate it instead;
|
|
there is no usable secret key on the source host to encrypt it to), `.venv`,
|
|
`node_modules`, and Ollama models.
|
|
|
|
Restore: clone the main repo, rebuild the venv from pinned requirements, then
|
|
copy `omo/` back to `.omo/`, `config/config.local.yaml` into place, and
|
|
`home/*` to their `~/.config` locations. `router.db.gz` is a daily snapshot.
|
|
INNER
|
|
|
|
git add -A
|
|
if git diff --cached --quiet; then
|
|
echo "offsite: no changes"
|
|
else
|
|
git -c user.name="6krrt-backup" -c user.email="backup@localhost" \
|
|
commit -q -m "backup $(date -Iseconds)"
|
|
git push -q -u origin main && echo "offsite: pushed $(git rev-parse --short HEAD)"
|
|
fi
|