#!/usr/bin/env bash # Tarball everything about THIS workstation that git does not have. # # The repo itself is pushed to Gitea and needs no backup. What has no other # copy is the ignored/untracked state around it -- and docs/incidents.md #5 # records what happens when that is lost: `git clean -fdx` took router.db, # .env, .venv and config/config.local.yaml in one command, and recovery was # luck. # # INCLUDED (irreplaceable or expensive to recreate): # router.db measurement history; cannot be regenerated # .env provider API key # config/config.local.yaml operator tariff and local overrides # .omo/ plans, evidence ledger, boulder state # ~/.config/systemd/user tuned units (the graceful-shutdown fix etc.) # ~/.config/opencode agent plugins incl. session-registry.js # ~/.claude/.../memory cross-session memory for this project # # EXCLUDED on purpose: # .venv rebuildable: python -m venv .venv && pip install -r requirements.txt # node_modules rebuildable # ollama models ~30GB and re-pullable; Modelfile recipes are in docs/local-models.md # .git the remote has it set -euo pipefail REPO="${REPO:-$HOME/Sources/6krrt}" DEST="${LLM_ROUTER_BACKUP_DIR:-$HOME/.local/share/6krrt-backups}" KEEP="${WORKSTATION_BACKUP_KEEP:-7}" mkdir -p "$DEST" stamp=$(date +%Y%m%d-%H%M%S) out="$DEST/workstation-$stamp.tar.gz" staging=$(mktemp -d) trap 'rm -rf "$staging"' EXIT # Snapshot the DB properly rather than tarring a live file: `cp`/`tar` on an # open SQLite database can capture a torn page set that still looks valid. sqlite3 "$REPO/router.db" ".backup '$staging/router.db'" mkdir -p "$staging/repo/config" [ -f "$REPO/.env" ] && cp -f "$REPO/.env" "$staging/repo/.env" [ -f "$REPO/config/config.local.yaml" ] && cp -f "$REPO/config/config.local.yaml" "$staging/repo/config/" [ -d "$REPO/.omo" ] && cp -a "$REPO/.omo" "$staging/repo/.omo" mkdir -p "$staging/home" [ -d "$HOME/.config/systemd/user" ] && cp -a "$HOME/.config/systemd/user" "$staging/home/systemd-user" [ -d "$HOME/.config/opencode" ] && cp -a "$HOME/.config/opencode" "$staging/home/opencode" mem="$HOME/.claude/projects/-home-alee-Sources-6krrt/memory" [ -d "$mem" ] && cp -a "$mem" "$staging/home/claude-memory" cat > "$staging/RESTORE.md" <<'INNER' # Restoring this workstation The repo comes from Gitea; this tarball has only what git does not. git clone ssh://git@git.adlee.work:2222/alee/6krrt.git cd 6krrt python3 -m venv .venv && .venv/bin/pip install -r requirements.txt Then from this archive: cp router.db /router.db cp repo/.env /.env && chmod 600 /.env cp repo/config/config.local.yaml /config/ cp -a repo/.omo /.omo cp -a home/systemd-user/* ~/.config/systemd/user/ && systemctl --user daemon-reload cp -a home/opencode ~/.config/opencode Ollama models are not here. Re-pull and re-tag per docs/local-models.md: ollama pull qwen2.5-coder:14b && ollama pull qwen3-vl:4b (then the Modelfile num_ctx tags documented there) Verify: sqlite3 /router.db "select integrity_check from pragma_integrity_check limit 1;" systemctl --user restart llm-router.service && curl -s localhost:8080/health INNER tar -czf "$out" -C "$staging" . # Verify the archive reads back before rotating anything away. tar -tzf "$out" >/dev/null || { rm -f "$out"; echo "archive FAILED to verify; discarded" >&2; exit 1; } # shellcheck disable=SC2012 ls -1t "$DEST"/workstation-*.tar.gz 2>/dev/null | tail -n +$((KEEP + 1)) | xargs -r rm -f echo "workstation backup ok: $out ($(du -h "$out" | cut -f1)), keeping $KEEP"