Auto-memory (~/.claude/projects/*/memory, 774 files across 49 projects) goes to
gitea vh/claude-memory, not this repo. The git dir is ~/claude-memory.git and its
work tree is ~/.claude/projects, and exclude rules admit only memory/ folders, so
transcripts stay local and nothing is added inside ~/.claude.
- home_root/.local/bin/claude-memory-sync: commit, rebase onto origin, push, under
a flock; --status; --init for a fresh machine. Every run records its result
for the drift check.
- home_config/systemd/user/claude-memory-sync.{service,timer}: hourly with
Persistent=true. Linked but not yet enabled.
- link-dotfiles --check now also reports uncommitted changes (a stale session
can write old content back through an intact link) and a failed or stale
(>3h) memory sync.
- CLAUDE.md and README document the backup and the never-put-credentials-in-memory rule.
114 lines
4.5 KiB
Bash
Executable File
114 lines
4.5 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# One-shot, host-aware dotfile linker for Linux & macOS.
|
|
#
|
|
# home_root/* -> ~/<name> (e.g. ~/.zshrc) via `stow home_root`
|
|
# home_config/* -> ~/.config/<name> (e.g. ~/.config/nvim) via `stow home_config`
|
|
#
|
|
# Ensures GNU stow is present first (Homebrew on macOS; apt/dnf/pacman on Linux),
|
|
# so a fresh box needs nothing pre-installed. Re-running is safe — stow is
|
|
# idempotent and refuses to clobber unrelated real files.
|
|
#
|
|
# Windows / PowerShell: run ./link-dotfiles.ps1 instead (stow has no native
|
|
# Windows port; the .ps1 mirrors this with junctions + symlinks).
|
|
#
|
|
# `--check`: report drift and change nothing. Two kinds are caught. (1) A tracked
|
|
# file whose live copy is no longer a link into this repo: stow would refuse to
|
|
# stow over it, so the repo copy has silently gone stale. `sed -i` and any other
|
|
# write-temp-then-rename editor does this, and so can an app that rewrites its
|
|
# own config atomically. (2) A link into this repo whose target has been removed.
|
|
# Exits 1 on drift.
|
|
set -euo pipefail
|
|
|
|
repo="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
cd "$repo"
|
|
|
|
if [ "${1:-}" = "--check" ]; then
|
|
command -v stow >/dev/null 2>&1 || { echo "error: GNU stow not installed; run ./link-dotfiles once first." >&2; exit 2; }
|
|
drift=0
|
|
for spec in "home_root:$HOME" "home_config:$HOME/.config"; do
|
|
pkg="${spec%%:*}"; target="${spec#*:}"
|
|
out="$(stow -n -v --target="$target" "$pkg" 2>&1 | grep -v 'simulation mode' || true)"
|
|
if [ -n "$out" ]; then
|
|
drift=1
|
|
echo "✗ $pkg -> $target is not fully linked:"
|
|
printf '%s\n' "$out" | sed 's/^/ /'
|
|
fi
|
|
done
|
|
dangling="$(find "$HOME" "$HOME/.config" "$HOME/.claude" "$HOME/.claude/bin" "$HOME/.claude/skills" "$HOME/.local/bin" \
|
|
-maxdepth 1 -xtype l -lname "*dotfiles/*" 2>/dev/null || true)"
|
|
if [ -n "$dangling" ]; then
|
|
drift=1
|
|
echo "✗ links into dotfiles whose target is gone:"
|
|
printf '%s\n' "$dangling" | sed 's/^/ /'
|
|
fi
|
|
# A link can stay intact while its content moves: an app that holds a stale copy
|
|
# in memory (a long-running Claude Code session with settings.json) writes it
|
|
# straight back through the link. That shows up only as an uncommitted change.
|
|
dirty="$(git -C "$repo" status --porcelain -- home_root home_config 2>/dev/null || true)"
|
|
if [ -n "$dirty" ]; then
|
|
drift=1
|
|
echo "✗ live config differs from the last commit (not backed up yet):"
|
|
printf '%s\n' "$dirty" | sed 's/^/ /'
|
|
fi
|
|
# Auto-memory backup (claude-memory-sync), checked only on machines that have it.
|
|
if [ -d "$HOME/claude-memory.git" ]; then
|
|
last="$HOME/.claude/state/claude-memory-sync.last"
|
|
if [ ! -f "$last" ]; then
|
|
drift=1; echo "✗ claude-memory-sync has never completed a run on this machine"
|
|
else
|
|
read -r ts result detail < "$last"
|
|
age=$(( ($(date +%s) - ts) / 60 ))
|
|
if [ "$result" != ok ] || [ "$age" -gt 180 ]; then
|
|
drift=1; echo "✗ claude-memory-sync: last run ${age} min ago — $result${detail:+ ($detail)}"
|
|
fi
|
|
fi
|
|
fi
|
|
[ "$drift" -eq 0 ] && echo "✓ no drift: every tracked file is linked and committed, no dotfiles link dangles, auto-memory backup is current."
|
|
exit "$drift"
|
|
fi
|
|
|
|
# `--apps`: install all host CLI tools first, then link. Without it, this only
|
|
# links (fast + idempotent). App install is delegated to the host-aware
|
|
# lk-installers/install-apps.
|
|
if [ "${1:-}" = "--apps" ]; then
|
|
echo "Installing apps first (--apps)…"
|
|
"$repo/lk-installers/install-apps"
|
|
fi
|
|
|
|
ensure_stow() {
|
|
command -v stow >/dev/null 2>&1 && return
|
|
|
|
echo "GNU stow not found — installing it…"
|
|
case "$(uname -s)" in
|
|
Darwin)
|
|
if ! command -v brew >/dev/null 2>&1; then
|
|
echo "error: Homebrew is required on macOS. Run ./lk-installers/install-apps (or ./link-dotfiles --apps) first." >&2
|
|
exit 1
|
|
fi
|
|
brew install stow
|
|
;;
|
|
Linux)
|
|
if command -v apt >/dev/null 2>&1; then sudo apt update && sudo apt install -y stow
|
|
elif command -v dnf >/dev/null 2>&1; then sudo dnf install -y stow
|
|
elif command -v pacman >/dev/null 2>&1; then sudo pacman -S --noconfirm stow
|
|
else
|
|
echo "error: no supported package manager (apt/dnf/pacman). Install GNU stow manually." >&2
|
|
exit 1
|
|
fi
|
|
;;
|
|
*)
|
|
echo "error: unsupported OS '$(uname -s)'. On Windows run ./link-dotfiles.ps1 instead." >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
ensure_stow
|
|
|
|
echo "Linking home_root -> ~"
|
|
stow -v home_root
|
|
echo "Linking home_config -> ~/.config"
|
|
stow -v --target="$HOME/.config" home_config
|
|
|
|
echo "Done."
|