Files
dotfiles/install.sh
T
Dmitry DementievandClaude Opus 4.6 e00611015a 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>
2026-03-18 15:01:34 +03:00

132 lines
5.3 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
DOTFILES_DIR="$(cd "$(dirname "$0")" && pwd)"
# --- Цвета ---
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
info() { echo -e "${GREEN}[+]${NC} $*"; }
warn() { echo -e "${YELLOW}[!]${NC} $*"; }
error() { echo -e "${RED}[✗]${NC} $*"; }
# --- Проверка stow ---
if ! command -v stow &>/dev/null; then
error "stow не найден. Установите: sudo apt install stow"
exit 1
fi
# --- Stow пакеты ---
PACKAGES=(bash tmux vim zed vscode agents opencode codex gemini)
info "Применяю stow пакеты..."
cd "$DOTFILES_DIR"
for pkg in "${PACKAGES[@]}"; do
if [ -d "$pkg" ]; then
stow -v --restow "$pkg" 2>&1 | sed "s/^/ /"
else
warn "Пакет $pkg не найден, пропускаю"
fi
done
# --- 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"
if [ ! -f "$CLAUDE_LOCAL" ]; then
info "Создаю $CLAUDE_LOCAL (локальные настройки Claude)..."
cat > "$CLAUDE_LOCAL" <<'EOF'
{
"model": "opus",
"effortLevel": "high"
}
EOF
else
warn "$CLAUDE_LOCAL уже существует, пропускаю"
fi
# --- Bash: подключение в .bashrc ---
BASHRC="$HOME/.bashrc"
BASHRC_LINE='[ -f ~/.bash_custom ] && source ~/.bash_custom'
if grep -q 'bash_custom' "$BASHRC" 2>/dev/null; then
warn "~/.bashrc уже содержит bash_custom, пропускаю"
else
echo "" >> "$BASHRC"
echo "# dotfiles" >> "$BASHRC"
echo "$BASHRC_LINE" >> "$BASHRC"
info "Добавлено в ~/.bashrc: $BASHRC_LINE"
fi
# --- WSL: hproxy.local (автоматический резолв при старте) ---
if [ -n "${WSL_DISTRO_NAME:-}" ]; then
HPROXY_SCRIPT="$DOTFILES_DIR/scripts/setup-hproxy.sh"
if grep -q "hproxy.local" /etc/wsl.conf 2>/dev/null; then
warn "/etc/wsl.conf уже содержит hproxy.local, пропускаю"
else
echo ""
info "Обнаружен WSL. Скрипт $HPROXY_SCRIPT может автоматически"
info "прописывать hproxy.local в /etc/hosts при каждом старте WSL (NAT mode)."
echo -ne "${YELLOW}[?]${NC} Добавить boot command в /etc/wsl.conf? [y/N] "
read -r answer
if [[ "$answer" =~ ^[Yy]$ ]]; then
# Добавляем [boot] секцию, если её нет
if ! grep -q '^\[boot\]' /etc/wsl.conf 2>/dev/null; then
printf '\n[boot]\n' | sudo tee -a /etc/wsl.conf > /dev/null
fi
sudo sed -i "/^\[boot\]/a command = \"$HPROXY_SCRIPT\"" /etc/wsl.conf
info "Добавлено в /etc/wsl.conf. Перезапустите WSL: wsl --shutdown"
else
warn "Пропущено. Можно настроить вручную — см. README.md"
fi
fi
fi
info "Готово!"