fix(claude): settings.json копируется вместо симлинка через stow

- Зачем:
  - stow tree-folding создавал симлинк ~/.claude → dotfiles/claude/.claude/,
    из-за чего Claude Code модифицировал settings.json прямо в репозитории,
    а рантайм-данные (sessions, credentials) попадали в каталог dotfiles.
- Что:
  - claude убран из stow-пакетов, установка через ручную секцию в install.sh.
  - settings.json копируется в ~/.claude/ (оригинал защищён chmod 444).
  - добавлена автомиграция: симлинк → директория с переносом рантайм-данных.
  - грязный settings.json в репо автоматически сбрасывается через git checkout.
  - добавлен .stow-local-ignore для защиты от случайного stow claude.
  - check.sh дополнен проверками: ~/.claude — директория, settings.json — копия.
- Проверка:
  - bash install.sh (на машине с симлинком — мигрирует, иначе — идемпотентно).
  - bash agents/.agents/skills/dotfiles-check/check.sh.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dmitry DementievandClaude Opus 4.6 committed 2026-03-18 15:01:34 +03:00
1 parent 769ef95096
commit e00611015a
3 files changed
+79 -10

No files matched your search

+28 -1
View File
@@ -28,6 +28,8 @@ echo "════════════════════════
for pkg_dir in "$DOTFILES_DIR"/*/; do for pkg_dir in "$DOTFILES_DIR"/*/; do
pkg=$(basename "$pkg_dir") pkg=$(basename "$pkg_dir")
[[ "$pkg" == .* ]] && continue [[ "$pkg" == .* ]] && continue
# claude управляется вручную (копирование settings.json), не через stow
[[ "$pkg" == "claude" ]] && continue
# Точки входа — элементы первого уровня внутри пакета # Точки входа — элементы первого уровня внутри пакета
entries=() entries=()
@@ -99,7 +101,25 @@ if [ -f "$install_script" ]; then
echo "" echo ""
echo "Доп. проверки (install.sh):" echo "Доп. проверки (install.sh):"
# chmod 444 на settings.json # Claude Code: ~/.claude должна быть директорией, не симлинком
if [ -L "$HOME/.claude" ]; then
err "~/.claude — симлинк (должна быть директория). Запустите install.sh"
elif [ -d "$HOME/.claude" ]; then
pass "~/.claude — директория (не симлинк)"
else
err "~/.claude — не найден"
fi
# Claude Code: settings.json должен быть копией, не симлинком
if [ -L "$HOME/.claude/settings.json" ]; then
err "~/.claude/settings.json — симлинк (должна быть копия)"
elif [ -f "$HOME/.claude/settings.json" ]; then
pass "~/.claude/settings.json — файл (копия)"
else
err "~/.claude/settings.json — не найден"
fi
# chmod 444 на settings.json в репозитории
if [ -f "$DOTFILES_DIR/claude/.claude/settings.json" ]; then if [ -f "$DOTFILES_DIR/claude/.claude/settings.json" ]; then
perms=$(stat -c %a "$DOTFILES_DIR/claude/.claude/settings.json" 2>/dev/null || true) perms=$(stat -c %a "$DOTFILES_DIR/claude/.claude/settings.json" 2>/dev/null || true)
if [ "$perms" = "444" ]; then if [ "$perms" = "444" ]; then
@@ -116,6 +136,13 @@ if [ -f "$install_script" ]; then
skip "~/.claude/settings.local.json — не найден (создайте через install.sh)" skip "~/.claude/settings.local.json — не найден (создайте через install.sh)"
fi fi
# skills симлинк
if [ -L "$HOME/.claude/skills" ]; then
pass "~/.claude/skills — симлинк"
else
skip "~/.claude/skills — не найден"
fi
# bashrc # bashrc
if grep -q 'bash_custom' "$HOME/.bashrc" 2>/dev/null; then if grep -q 'bash_custom' "$HOME/.bashrc" 2>/dev/null; then
pass "~/.bashrc содержит подключение bash_custom" pass "~/.bashrc содержит подключение bash_custom"
+3
View File
@@ -0,0 +1,3 @@
# Claude Code управляется вручную через install.sh (копирование, не симлинк).
# Этот файл не даёт stow случайно создать симлинк ~/.claude.
\.claude
+48 -9
View File
@@ -20,7 +20,7 @@ if ! command -v stow &>/dev/null; then
fi fi
# --- Stow пакеты --- # --- Stow пакеты ---
PACKAGES=(bash tmux vim zed vscode agents opencode codex claude gemini) PACKAGES=(bash tmux vim zed vscode agents opencode codex gemini)
info "Применяю stow пакеты..." info "Применяю stow пакеты..."
cd "$DOTFILES_DIR" cd "$DOTFILES_DIR"
@@ -32,7 +32,53 @@ for pkg in "${PACKAGES[@]}"; do
fi fi
done done
# --- Claude: settings.local.json --- # --- Claude Code: установка без stow ---
# settings.json КОПИРУЕТСЯ (не линкуется), чтобы Claude Code не модифицировал
# файл в репозитории. Локальные настройки — в settings.local.json.
# Миграция: если ~/.claude — stow-симлинк, раскрываем в реальную директорию
if [ -L "$HOME/.claude" ]; then
info "Миграция: ~/.claude — симлинк, преобразую в директорию..."
CLAUDE_STOW_DIR=$(readlink -f "$HOME/.claude")
rm "$HOME/.claude"
mkdir -p "$HOME/.claude"
# Переносим рантайм-данные из stow-директории
for item in "$CLAUDE_STOW_DIR"/* "$CLAUDE_STOW_DIR"/.[!.]*; do
[ -e "$item" ] || continue
base=$(basename "$item")
# Пропускаем git-tracked файлы — они останутся в репозитории
case "$base" in .gitignore|settings.json|skills) continue ;; esac
mv "$item" "$HOME/.claude/" 2>/dev/null || true
done
info "Рантайм-данные перенесены в ~/.claude/"
fi
mkdir -p "$HOME/.claude"
# Skills (симлинк)
SKILLS_SRC="$DOTFILES_DIR/agents/.agents/skills"
if [ -d "$SKILLS_SRC" ]; then
ln -sfn "$SKILLS_SRC" "$HOME/.claude/skills"
fi
# settings.json — копия из репозитория (не симлинк!)
CLAUDE_SETTINGS_SRC="$DOTFILES_DIR/claude/.claude/settings.json"
CLAUDE_SETTINGS_DST="$HOME/.claude/settings.json"
if [ -f "$CLAUDE_SETTINGS_SRC" ]; then
# Если settings.json в репо был изменён (например, Claude Code писал через симлинк) — сбрасываем
if cd "$DOTFILES_DIR" && ! git diff --quiet claude/.claude/settings.json 2>/dev/null; then
warn "claude/.claude/settings.json имеет локальные изменения — сбрасываю"
git checkout claude/.claude/settings.json
fi
# Защищаем оригинал в репозитории
chmod 444 "$CLAUDE_SETTINGS_SRC"
# Копируем в ~/.claude/ (перезаписывает при повторном запуске)
cp -f "$CLAUDE_SETTINGS_SRC" "$CLAUDE_SETTINGS_DST"
chmod 644 "$CLAUDE_SETTINGS_DST"
info "settings.json скопирован (оригинал защищён chmod 444)"
fi
# settings.local.json — локальные переопределения (не в git)
CLAUDE_LOCAL="$HOME/.claude/settings.local.json" CLAUDE_LOCAL="$HOME/.claude/settings.local.json"
if [ ! -f "$CLAUDE_LOCAL" ]; then if [ ! -f "$CLAUDE_LOCAL" ]; then
info "Создаю $CLAUDE_LOCAL (локальные настройки Claude)..." info "Создаю $CLAUDE_LOCAL (локальные настройки Claude)..."
@@ -46,13 +92,6 @@ else
warn "$CLAUDE_LOCAL уже существует, пропускаю" warn "$CLAUDE_LOCAL уже существует, пропускаю"
fi fi
# --- Claude: защита settings.json от перезаписи ---
CLAUDE_SETTINGS="$DOTFILES_DIR/claude/.claude/settings.json"
if [ -f "$CLAUDE_SETTINGS" ]; then
chmod 444 "$CLAUDE_SETTINGS"
info "settings.json защищён от записи (chmod 444)"
fi
# --- Bash: подключение в .bashrc --- # --- Bash: подключение в .bashrc ---
BASHRC="$HOME/.bashrc" BASHRC="$HOME/.bashrc"
BASHRC_LINE='[ -f ~/.bash_custom ] && source ~/.bash_custom' BASHRC_LINE='[ -f ~/.bash_custom ] && source ~/.bash_custom'