From 22d38f80e56f818919e3aa1f50458fe91b912e2f Mon Sep 17 00:00:00 2001 From: Dmitrii Date: Sun, 22 Mar 2026 01:37:54 +0300 Subject: [PATCH] =?UTF-8?q?fix(install):=20=D1=83=D1=81=D1=82=D1=80=D0=B0?= =?UTF-8?q?=D0=BD=D0=B5=D0=BD=D1=8B=20=D0=BA=D0=BE=D0=BD=D1=84=D0=BB=D0=B8?= =?UTF-8?q?=D0=BA=D1=82=D1=8B=20stow=20=D0=B2=20WSL=20=D0=B8=20opencode?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Зачем: - нужно было убрать падение install.sh на абсолютных WSL-симлинках и существующей runtime-директории opencode. - Что: - убран `--restow` из вызова stow, чтобы install не падал на ссылках `~/.aws` и `~/.azure`. - настройка `~/.config/opencode` переведена на ручную установку с backup существующего `opencode.json` и сохранением runtime-файлов. - в tests/test_install.sh добавлены сценарии для WSL-симлинков и существующей директории opencode. - Проверка: - bash -n install.sh - bash -n tests/test_install.sh - ./tests/test_install.sh --- install.sh | 73 +++++++++++++++++++++++++++++++++++++++++-- tests/test_install.sh | 41 ++++++++++++++++++++++++ 2 files changed, 111 insertions(+), 3 deletions(-) diff --git a/install.sh b/install.sh index 07bd4d6..1e9f263 100755 --- a/install.sh +++ b/install.sh @@ -19,7 +19,7 @@ warn() { echo -e "${YELLOW}[!]${NC} $*"; } error() { echo -e "${RED}[✗]${NC} $*"; } stow_package() { - "$STOW_BIN" --target="$HOME" -v --restow "$@" 2>&1 | sed "s/^/ /" + "$STOW_BIN" --target="$HOME" -v "$@" 2>&1 | sed "s/^/ /" } ensure_symlink() { @@ -56,6 +56,53 @@ ensure_symlink() { ln -s "$src" "$dst" } +backup_existing_path() { + local path="$1" + local label="$2" + local backup_path + + backup_path="${path}.bak.$(date +%Y%m%d%H%M%S)" + if [ -e "$backup_path" ] || [ -L "$backup_path" ]; then + backup_path="${backup_path}.$$" + fi + + mv "$path" "$backup_path" + warn "$label уже существовал, сохранил резервную копию: $backup_path" +} + +copy_managed_file() { + local src="$1" + local dst="$2" + local label="$3" + local src_real="" + local dst_real="" + + mkdir -p "$(dirname "$dst")" + src_real="$(readlink -f "$src" 2>/dev/null || printf '%s' "$src")" + + if [ -L "$dst" ]; then + dst_real="$(readlink -f "$dst" 2>/dev/null || true)" + if [ "$dst_real" = "$src_real" ]; then + rm "$dst" + else + backup_existing_path "$dst" "$label" + fi + elif [ -f "$dst" ]; then + if cmp -s "$src" "$dst"; then + return 0 + fi + backup_existing_path "$dst" "$label" + elif [ -d "$dst" ]; then + warn "$dst уже существует как директория, пропускаю замену" + return 0 + elif [ -e "$dst" ]; then + warn "$dst уже существует и не является обычным файлом, пропускаю замену" + return 0 + fi + + cp -f "$src" "$dst" +} + is_interactive() { case "$INSTALL_INTERACTIVE_MODE" in always) return 0 ;; @@ -75,7 +122,7 @@ if ! command -v "$STOW_BIN" &>/dev/null; then fi # --- Stow пакеты --- -PACKAGES=(bash tmux vim zed vscode agents opencode codex gemini) +PACKAGES=(bash tmux vim zed vscode agents codex gemini) AVAILABLE_PACKAGES=() info "Применяю stow пакеты..." @@ -92,6 +139,27 @@ if [ "${#AVAILABLE_PACKAGES[@]}" -gt 0 ]; then stow_package "${AVAILABLE_PACKAGES[@]}" fi +SKILLS_SRC="$DOTFILES_DIR/agents/.agents/skills" + +# --- Opencode: установка без stow --- +OPENCODE_DIR="$HOME/.config/opencode" +OPENCODE_SRC_DIR="$DOTFILES_DIR/opencode/.config/opencode" +OPENCODE_JSON_SRC="$OPENCODE_SRC_DIR/opencode.json" + +mkdir -p "$OPENCODE_DIR" + +if [ -d "$OPENCODE_SRC_DIR/agents" ]; then + ensure_symlink "$OPENCODE_SRC_DIR/agents" "$OPENCODE_DIR/agents" +fi + +if [ -d "$SKILLS_SRC" ]; then + ensure_symlink "$SKILLS_SRC" "$OPENCODE_DIR/skills" +fi + +if [ -f "$OPENCODE_JSON_SRC" ]; then + copy_managed_file "$OPENCODE_JSON_SRC" "$OPENCODE_DIR/opencode.json" "opencode.json" +fi + # --- Claude Code: установка без stow --- # settings.json КОПИРУЕТСЯ (не линкуется), чтобы Claude Code не модифицировал # файл в репозитории. Локальные настройки — в settings.local.json. @@ -120,7 +188,6 @@ fi mkdir -p "$HOME/.claude" # Skills (симлинк) -SKILLS_SRC="$DOTFILES_DIR/agents/.agents/skills" if [ -d "$SKILLS_SRC" ]; then ensure_symlink "$SKILLS_SRC" "$HOME/.claude/skills" fi diff --git a/tests/test_install.sh b/tests/test_install.sh index 87bb6e6..fdad563 100755 --- a/tests/test_install.sh +++ b/tests/test_install.sh @@ -162,6 +162,29 @@ test_preserves_nonempty_skills_directory() { [ -f "$home_dir/.claude/skills/local.txt" ] || fail "Expected local skill data to be preserved" } +test_opencode_runtime_dir_is_preserved_and_config_is_managed() { + local fixture repo_root home_dir backup_path + fixture="$(setup_fixture)" + repo_root="$fixture/repo" + home_dir="$fixture/home" + mkdir -p "$home_dir/.config/opencode" + echo runtime > "$home_dir/.config/opencode/package.json" + cat > "$home_dir/.config/opencode/opencode.json" <<'EOF' +{"source":"local"} +EOF + + run_install "$repo_root" "$home_dir" env WSL_DISTRO_NAME= INSTALL_INTERACTIVE_MODE=never >/tmp/test-install.out + + [ -f "$home_dir/.config/opencode/package.json" ] || fail "Expected opencode runtime files to be preserved" + [ -L "$home_dir/.config/opencode/agents" ] || fail "Expected ~/.config/opencode/agents symlink" + [ -L "$home_dir/.config/opencode/skills" ] || fail "Expected ~/.config/opencode/skills symlink" + assert_file_contains "$home_dir/.config/opencode/opencode.json" '"$schema": "https://opencode.ai/config.json"' + + backup_path="$(find "$home_dir/.config/opencode" -maxdepth 1 -name 'opencode.json.bak.*' -print -quit)" + [ -n "$backup_path" ] || fail "Expected backup for existing opencode.json" + assert_file_contains "$backup_path" '"source":"local"' +} + test_broken_claude_symlink_does_not_fail() { local fixture repo_root home_dir fixture="$(setup_fixture)" @@ -176,6 +199,22 @@ test_broken_claude_symlink_does_not_fail() { [ ! -L "$home_dir/.claude" ] || fail "Expected ~/.claude to stop being a symlink" } +test_absolute_symlinks_in_home_do_not_break_install() { + local fixture repo_root home_dir + fixture="$(setup_fixture)" + repo_root="$fixture/repo" + home_dir="$fixture/home" + mkdir -p "$home_dir" "$fixture/windows/.aws" "$fixture/windows/.azure" + ln -s "$fixture/windows/.aws" "$home_dir/.aws" + ln -s "$fixture/windows/.azure" "$home_dir/.azure" + + run_install "$repo_root" "$home_dir" env WSL_DISTRO_NAME= INSTALL_INTERACTIVE_MODE=never >/tmp/test-install.out + run_install "$repo_root" "$home_dir" env WSL_DISTRO_NAME= INSTALL_INTERACTIVE_MODE=never >/tmp/test-install.out + + [ -L "$home_dir/.aws" ] || fail "Expected ~/.aws symlink to survive install" + [ -L "$home_dir/.azure" ] || fail "Expected ~/.azure symlink to survive install" +} + test_wsl_noninteractive_skips_file_changes() { local fixture repo_root home_dir wsl_conf output fixture="$(setup_fixture)" @@ -246,7 +285,9 @@ main() { test_rerun_does_not_duplicate_bashrc_line test_repairs_nested_skills_directory_bug test_preserves_nonempty_skills_directory + test_opencode_runtime_dir_is_preserved_and_config_is_managed test_broken_claude_symlink_does_not_fail + test_absolute_symlinks_in_home_do_not_break_install test_wsl_noninteractive_skips_file_changes test_wsl_interactive_adds_boot_command test_wsl_existing_boot_command_is_preserved