- Зачем: прокси для агента — выбор конкретной машины. Автоматическая правка ~/.codex/.env и ~/.claude/settings.json оказалась слишком навязчивой. - Что: шаблоны перенесены в proxy-client/.config/proxy-client/examples/ (codex.env, claude-settings.json) и попадают на машину вместе с остальными примерами. install.sh больше не трогает настройки Codex и Claude, его тесты на это удалены. Инструкция по копированию — в README proxy-client. - Проверка: tests/test_install.sh и tests/test_proxy_client_install.sh проходят. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
532 lines
22 KiB
Bash
Executable File
532 lines
22 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
|
||
# ── Переменные ────────────────────────────────────────────────────────
|
||
|
||
DOTFILES_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||
STOW_BIN="${STOW_BIN:-stow}"
|
||
GIT_BIN="${GIT_BIN:-git}"
|
||
SUDO_BIN="${SUDO_BIN:-sudo}"
|
||
WSL_CONF_PATH="${WSL_CONF_PATH:-/etc/wsl.conf}"
|
||
INSTALL_INTERACTIVE_MODE="${INSTALL_INTERACTIVE_MODE:-auto}"
|
||
|
||
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} $*"; }
|
||
|
||
# Интерактивный yes/no промпт (принимает английскую y и русскую у).
|
||
ask_yes_no() {
|
||
local answer=""
|
||
echo -ne "${YELLOW}[?]${NC} $1 [y/N] "
|
||
read -r answer || answer=""
|
||
[[ "$answer" =~ ^[YyУу]$ ]]
|
||
}
|
||
|
||
is_interactive() {
|
||
case "$INSTALL_INTERACTIVE_MODE" in
|
||
always) return 0 ;;
|
||
never) return 1 ;;
|
||
auto) [ -t 0 ] ;;
|
||
*)
|
||
warn "Неизвестное значение INSTALL_INTERACTIVE_MODE=$INSTALL_INTERACTIVE_MODE, использую auto"
|
||
[ -t 0 ]
|
||
;;
|
||
esac
|
||
}
|
||
|
||
# Вывести stow-лог с отступом.
|
||
stow_package() {
|
||
"$STOW_BIN" --target="$HOME" -v "$@" 2>&1 | sed "s/^/ /"
|
||
}
|
||
|
||
# Создать симлинк src → dst. Обрабатывает миграцию с директории на симлинк.
|
||
ensure_symlink() {
|
||
local src="$1"
|
||
local dst="$2"
|
||
|
||
if [ -L "$dst" ]; then
|
||
ln -sfnT "$src" "$dst"
|
||
return 0
|
||
fi
|
||
|
||
if [ -d "$dst" ]; then
|
||
# Миграция: директория с единственным симлинком skills → заменяем целиком
|
||
if [ -L "$dst/skills" ] && [ "$(find "$dst" -mindepth 1 -maxdepth 1 | wc -l)" -eq 1 ]; then
|
||
rm -rf "$dst"
|
||
ln -s "$src" "$dst"
|
||
return 0
|
||
fi
|
||
|
||
# Пустая директория → заменяем на симлинк
|
||
if [ -z "$(find "$dst" -mindepth 1 -maxdepth 1 -print -quit)" ]; then
|
||
rmdir "$dst"
|
||
ln -s "$src" "$dst"
|
||
return 0
|
||
fi
|
||
|
||
warn "$dst уже существует как директория, пропускаю замену"
|
||
return 0
|
||
fi
|
||
|
||
if [ -e "$dst" ]; then
|
||
warn "$dst уже существует и не является симлинком, пропускаю замену"
|
||
return 0
|
||
fi
|
||
|
||
ln -s "$src" "$dst"
|
||
}
|
||
|
||
# Сохранить бэкап с временной меткой (и PID при коллизии).
|
||
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"
|
||
}
|
||
|
||
# Парсит [boot] секцию wsl.conf, проверяет наличие command = ...
|
||
wsl_conf_has_boot_command() {
|
||
awk '
|
||
/^\[boot\]$/ { in_boot=1; next }
|
||
/^\[/ { in_boot=0 }
|
||
in_boot && /^[[:space:]]*command[[:space:]]*=/ { found=1 }
|
||
END { exit !found }
|
||
' "$1" 2>/dev/null
|
||
}
|
||
|
||
# Миграция: если $1 — stow-симлинк (пакет был свёрнут в репозиторий),
|
||
# раскрыть в настоящий каталог и перенести содержимое. Остальные аргументы —
|
||
# имена git-tracked файлов, которые остаются в репозитории.
|
||
migrate_folded_symlink() {
|
||
local link="$1"
|
||
shift
|
||
[ -L "$link" ] || return 0
|
||
|
||
info "Миграция: $link — симлинк, преобразую в директорию..."
|
||
local stow_dir
|
||
stow_dir="$(readlink -f "$link" 2>/dev/null || true)"
|
||
rm "$link"
|
||
mkdir -p "$link"
|
||
|
||
if [ -n "$stow_dir" ] && [ -d "$stow_dir" ]; then
|
||
for item in "$stow_dir"/* "$stow_dir"/.[!.]*; do
|
||
[ -e "$item" ] || [ -L "$item" ] || continue
|
||
local base skip s
|
||
base=$(basename "$item")
|
||
# git-tracked файлы остаются в репозитории
|
||
skip=""
|
||
for s in "$@"; do
|
||
[ "$base" = "$s" ] && skip=1
|
||
done
|
||
[ -n "$skip" ] && continue
|
||
mv "$item" "$link/" 2>/dev/null || true
|
||
done
|
||
info "Рантайм-данные перенесены в $link/"
|
||
else
|
||
warn "Не удалось прочитать источник старого симлинка $link, продолжаю без миграции данных"
|
||
fi
|
||
}
|
||
|
||
# Убрать с пути чужие файлы, которые конфликтуют со stow-пакетами.
|
||
#
|
||
# Программы создают свои конфиги сами (kitty.conf, .wezterm.lua), и при
|
||
# конфликте stow не просто пропускает пакет, а печатает "All operations
|
||
# aborted" и откатывает весь набор: install.sh падает по pipefail, не дойдя
|
||
# до настройки claude, opencode и .bashrc. Раньше это чинилось руками.
|
||
#
|
||
# Обход повторяет логику stow, а не перебирает файлы подряд: если каталога в
|
||
# HOME нет, конфликта быть не может и всё поддерево пропускается. Это заодно
|
||
# отсекает свёрнутые пакеты (~/.vscode-server) с их гигабайтами
|
||
# рантайм-данных.
|
||
backup_conflicting_stow_targets() {
|
||
local pkg=""
|
||
|
||
for pkg in "$@"; do
|
||
backup_conflicting_tree "$DOTFILES_DIR/$pkg" "$HOME" ""
|
||
done
|
||
}
|
||
|
||
# Рекурсивный обход одного уровня: src_dir — каталог пакета, dst_dir — его
|
||
# соответствие в HOME, rel — путь для сообщений.
|
||
backup_conflicting_tree() {
|
||
local src_dir="$1"
|
||
local dst_dir="$2"
|
||
local rel="$3"
|
||
local src=""
|
||
local dst=""
|
||
local base=""
|
||
local src_real=""
|
||
local dst_real=""
|
||
|
||
[ -d "$src_dir" ] || return 0
|
||
|
||
for src in "$src_dir"/* "$src_dir"/.[!.]*; do
|
||
[ -e "$src" ] || [ -L "$src" ] || continue
|
||
base="$(basename "$src")"
|
||
dst="$dst_dir/$base"
|
||
|
||
# Цели нет — stow создаст симлинк или свернёт каталог, конфликта нет.
|
||
[ -e "$dst" ] || [ -L "$dst" ] || continue
|
||
|
||
if [ -d "$src" ] && [ ! -L "$src" ]; then
|
||
# Каталог: симлинк в цели трогать нельзя — stow спускается внутрь,
|
||
# так работают и свёрнутые пакеты, и посторонний симлинк ~/.config.
|
||
[ ! -L "$dst" ] || continue
|
||
# Два настоящих каталога stow объединяет, конфликт может быть глубже.
|
||
if [ -d "$dst" ]; then
|
||
backup_conflicting_tree "$src" "$dst" "$rel/$base"
|
||
continue
|
||
fi
|
||
# Файл на месте каталога — stow каталог поверх него не создаст.
|
||
backup_existing_path "$dst" "~$rel/$base"
|
||
continue
|
||
fi
|
||
|
||
# Файл: симлинк на наш же файл — результат прошлого прогона.
|
||
if [ -L "$dst" ]; then
|
||
src_real="$(readlink -f "$src" 2>/dev/null || true)"
|
||
dst_real="$(readlink -f "$dst" 2>/dev/null || true)"
|
||
if [ -n "$src_real" ] && [ "$src_real" = "$dst_real" ]; then
|
||
continue
|
||
fi
|
||
fi
|
||
|
||
backup_existing_path "$dst" "~$rel/$base"
|
||
done
|
||
}
|
||
|
||
# Миграция: раскрыть старую stow-ссылку ~/.config -> zed/.config.
|
||
# В пакете zed остаётся только управляемый каталог zed; остальные настройки
|
||
# возвращаются в домашний каталог и больше не попадают в репозиторий.
|
||
migrate_zed_config_stow_symlink() {
|
||
local config_path="$HOME/.config"
|
||
local zed_config="$DOTFILES_DIR/zed/.config"
|
||
local config_real=""
|
||
local zed_config_real=""
|
||
local replacement=""
|
||
local items_file=""
|
||
local item=""
|
||
local base=""
|
||
local moved_items=()
|
||
|
||
[ -L "$config_path" ] || return 0
|
||
|
||
config_real="$(readlink -f "$config_path" 2>/dev/null || true)"
|
||
zed_config_real="$(readlink -f "$zed_config" 2>/dev/null || true)"
|
||
if [ -z "$zed_config_real" ] || [ "$config_real" != "$zed_config_real" ]; then
|
||
return 0
|
||
fi
|
||
|
||
info "Миграция: ~/.config больше не должен принадлежать пакету zed..."
|
||
replacement="$(mktemp -d "$HOME/.config.migrate.XXXXXX")"
|
||
chmod --reference="$zed_config_real" "$replacement"
|
||
|
||
items_file="$(mktemp "$HOME/.config.migrate-items.XXXXXX")"
|
||
if ! find "$zed_config_real" -mindepth 1 -maxdepth 1 ! -name zed -print0 > "$items_file"; then
|
||
error "Не удалось получить полный список старого ~/.config"
|
||
rm -f -- "$items_file"
|
||
rmdir "$replacement" 2>/dev/null || true
|
||
return 1
|
||
fi
|
||
|
||
while IFS= read -r -d '' item; do
|
||
base="${item##*/}"
|
||
if ! mv -- "$item" "$replacement/$base"; then
|
||
error "Не удалось перенести ~/.config/$base"
|
||
for base in "${moved_items[@]}"; do
|
||
mv -- "$replacement/$base" "$zed_config_real/$base" 2>/dev/null || true
|
||
done
|
||
rm -f -- "$items_file"
|
||
rmdir "$replacement" 2>/dev/null || true
|
||
return 1
|
||
fi
|
||
moved_items+=("$base")
|
||
done < "$items_file"
|
||
rm -f -- "$items_file"
|
||
|
||
if ! rm -- "$config_path"; then
|
||
error "Не удалось удалить старую ссылку ~/.config"
|
||
for base in "${moved_items[@]}"; do
|
||
mv -- "$replacement/$base" "$zed_config_real/$base" 2>/dev/null || true
|
||
done
|
||
rmdir "$replacement" 2>/dev/null || true
|
||
return 1
|
||
fi
|
||
|
||
if ! mv -- "$replacement" "$config_path"; then
|
||
error "Не удалось создать новый каталог ~/.config"
|
||
ln -s "$zed_config_real" "$config_path" 2>/dev/null || true
|
||
for base in "${moved_items[@]}"; do
|
||
mv -- "$replacement/$base" "$zed_config_real/$base" 2>/dev/null || true
|
||
done
|
||
rmdir "$replacement" 2>/dev/null || true
|
||
return 1
|
||
fi
|
||
|
||
info "Настройки перенесены из пакета zed в ~/.config"
|
||
}
|
||
|
||
# Настроить boot command для hproxy.local в /etc/wsl.conf.
|
||
setup_wsl_hproxy() {
|
||
[ -n "${WSL_DISTRO_NAME:-}" ] || return 0
|
||
|
||
local script="$DOTFILES_DIR/scripts/setup-hproxy.sh"
|
||
local boot_cmd="command = \"$script\""
|
||
local legacy_cmd="command = \"ip route show default | awk '{print \$3 \\\" hproxy.local\\\"}' >> /etc/hosts\""
|
||
|
||
# Уже прописан наш скрипт
|
||
if grep -Fq "$boot_cmd" "$WSL_CONF_PATH" 2>/dev/null; then
|
||
warn "/etc/wsl.conf уже содержит boot command для setup-hproxy.sh, пропускаю"
|
||
return 0
|
||
fi
|
||
|
||
# Legacy inline command → предложить замену
|
||
if grep -Fq "$legacy_cmd" "$WSL_CONF_PATH" 2>/dev/null; then
|
||
warn "/etc/wsl.conf содержит legacy boot command для hproxy.local"
|
||
info "Рекомендуется заменить на: $boot_cmd"
|
||
if is_interactive && ask_yes_no "Заменить legacy command на setup-hproxy.sh?"; then
|
||
"$SUDO_BIN" sed -i '/ip route.*hproxy\.local/d' "$WSL_CONF_PATH"
|
||
"$SUDO_BIN" sed -i "/^\[boot\]/a command = \"$script\"" "$WSL_CONF_PATH"
|
||
info "Заменено. Перезапустите WSL: wsl --shutdown"
|
||
fi
|
||
return 0
|
||
fi
|
||
|
||
# Есть какой-то другой boot command
|
||
if wsl_conf_has_boot_command "$WSL_CONF_PATH"; then
|
||
warn "/etc/wsl.conf уже содержит [boot] command. Добавьте setup-hproxy.sh вручную"
|
||
info "Нужная строка: $boot_cmd"
|
||
return 0
|
||
fi
|
||
|
||
# Не интерактивный режим → только подсказка
|
||
if ! is_interactive; then
|
||
warn "WSL обнаружен, но stdin не интерактивный. Пропускаю настройку /etc/wsl.conf"
|
||
info "Добавьте вручную: $boot_cmd"
|
||
return 0
|
||
fi
|
||
|
||
# Интерактивный режим → спросить
|
||
echo ""
|
||
info "Обнаружен WSL. Скрипт $script может автоматически"
|
||
info "прописывать hproxy.local в /etc/hosts при каждом старте WSL (NAT mode)."
|
||
if ask_yes_no "Добавить boot command в /etc/wsl.conf?"; then
|
||
if ! grep -q '^\[boot\]' "$WSL_CONF_PATH" 2>/dev/null; then
|
||
printf '\n[boot]\n' | "$SUDO_BIN" tee -a "$WSL_CONF_PATH" > /dev/null
|
||
fi
|
||
"$SUDO_BIN" sed -i "/^\[boot\]/a command = \"$script\"" "$WSL_CONF_PATH"
|
||
info "Добавлено в /etc/wsl.conf. Перезапустите WSL: wsl --shutdown"
|
||
else
|
||
warn "Пропущено. Можно настроить вручную — см. README.md"
|
||
fi
|
||
}
|
||
|
||
# ── Stow пакеты ──────────────────────────────────────────────────────
|
||
|
||
if ! command -v "$STOW_BIN" &>/dev/null; then
|
||
error "stow не найден. Установите: sudo apt install stow"
|
||
exit 1
|
||
fi
|
||
|
||
PACKAGES=(bash tmux vim wezterm kitty vscode agents gemini proxy-client chatgpt)
|
||
AVAILABLE_PACKAGES=()
|
||
|
||
info "Применяю stow пакеты..."
|
||
cd "$DOTFILES_DIR"
|
||
for pkg in "${PACKAGES[@]}"; do
|
||
if [ -d "$pkg" ]; then
|
||
AVAILABLE_PACKAGES+=("$pkg")
|
||
else
|
||
warn "Пакет $pkg не найден, пропускаю"
|
||
fi
|
||
done
|
||
|
||
migrate_zed_config_stow_symlink
|
||
|
||
if [ ! -L "$HOME/.local" ]; then
|
||
mkdir -p "$HOME/.local"
|
||
fi
|
||
if [ -d "$HOME/.local" ] && [ ! -L "$HOME/.local" ] \
|
||
&& [ ! -L "$HOME/.local/bin" ]; then
|
||
mkdir -p "$HOME/.local/bin"
|
||
fi
|
||
if [ -d "$HOME/.local" ] && [ ! -L "$HOME/.local" ] \
|
||
&& [ ! -L "$HOME/.local/share" ]; then
|
||
mkdir -p "$HOME/.local/share"
|
||
fi
|
||
if [ -d "$HOME/.local/share" ] && [ ! -L "$HOME/.local/share" ] \
|
||
&& [ ! -L "$HOME/.local/share/applications" ]; then
|
||
mkdir -p "$HOME/.local/share/applications"
|
||
fi
|
||
|
||
if [ ! -L "$HOME/.config" ]; then
|
||
mkdir -p "$HOME/.config"
|
||
fi
|
||
if [ -d "$HOME/.config" ] && [ ! -L "$HOME/.config" ] \
|
||
&& [ ! -L "$HOME/.config/proxy-client" ]; then
|
||
mkdir -p "$HOME/.config/proxy-client"
|
||
fi
|
||
|
||
# ~/.config/kitty создаётся заранее настоящим каталогом, чтобы stow не свернул
|
||
# его в один симлинк: kitty пишет туда свои файлы (kitty.conf.bak от
|
||
# `kitten themes`), и при свёрнутом каталоге они попадали бы в репозиторий.
|
||
if [ -d "$HOME/.config" ] && [ ! -L "$HOME/.config" ] \
|
||
&& [ ! -L "$HOME/.config/kitty" ]; then
|
||
mkdir -p "$HOME/.config/kitty"
|
||
fi
|
||
|
||
if [ "${#AVAILABLE_PACKAGES[@]}" -gt 0 ]; then
|
||
backup_conflicting_stow_targets "${AVAILABLE_PACKAGES[@]}"
|
||
stow_package "${AVAILABLE_PACKAGES[@]}"
|
||
fi
|
||
|
||
SKILLS_SRC="$DOTFILES_DIR/agents/.agents/skills"
|
||
|
||
# ── Opencode ─────────────────────────────────────────────────────────
|
||
|
||
OPENCODE_DIR="$HOME/.config/opencode"
|
||
OPENCODE_SRC_DIR="$DOTFILES_DIR/opencode/.config/opencode"
|
||
OPENCODE_JSON_SRC="$OPENCODE_SRC_DIR/opencode.json"
|
||
OPENCODE_REVIEWER_SRC="$OPENCODE_SRC_DIR/permission-reviewer.jsonc"
|
||
|
||
mkdir -p "$OPENCODE_DIR"
|
||
|
||
# Профили safe-build/safe-plan удалены: агенты встроенные (build/plan),
|
||
# решения об "ask" принимает permission-reviewer. Старый симлинк чистим.
|
||
if [ -L "$OPENCODE_DIR/agents" ]; then
|
||
case "$(readlink "$OPENCODE_DIR/agents" 2>/dev/null || true)" in
|
||
"$OPENCODE_SRC_DIR"*)
|
||
rm "$OPENCODE_DIR/agents"
|
||
info "Удалён устаревший симлинк ~/.config/opencode/agents"
|
||
;;
|
||
esac
|
||
fi
|
||
|
||
# Скиллы opencode сам берёт из ~/.agents/skills и ~/.claude/skills. Прежняя
|
||
# ссылка ~/.config/opencode/skills загружала тот же набор ещё раз, и opencode
|
||
# сыпал предупреждениями о дублях. Чистим её.
|
||
if [ -L "$OPENCODE_DIR/skills" ]; then
|
||
case "$(readlink "$OPENCODE_DIR/skills" 2>/dev/null || true)" in
|
||
"$SKILLS_SRC"*)
|
||
rm "$OPENCODE_DIR/skills"
|
||
info "Удалён лишний симлинк ~/.config/opencode/skills"
|
||
;;
|
||
esac
|
||
fi
|
||
|
||
if [ -f "$OPENCODE_JSON_SRC" ]; then
|
||
copy_managed_file "$OPENCODE_JSON_SRC" "$OPENCODE_DIR/opencode.json" "opencode.json"
|
||
fi
|
||
|
||
# Настройки permission-reviewer: плагин читает их только из глобального
|
||
# ~/.config/opencode, из репозитория они не подхватываются — копируем.
|
||
if [ -f "$OPENCODE_REVIEWER_SRC" ]; then
|
||
copy_managed_file "$OPENCODE_REVIEWER_SRC" "$OPENCODE_DIR/permission-reviewer.jsonc" "permission-reviewer.jsonc"
|
||
fi
|
||
|
||
# ── Claude Code ──────────────────────────────────────────────────────
|
||
# Из репозитория линкуются только skills. Настройки (settings.json,
|
||
# settings.local.json) принадлежат Claude Code и живут только на машине.
|
||
|
||
migrate_folded_symlink "$HOME/.claude" .gitignore skills
|
||
mkdir -p "$HOME/.claude"
|
||
|
||
# Уборка артефактов прежних версий установщика: битые симлинки на удалённые
|
||
# из репозитория файлы и забытая копия statusline (наша — по строке-маркеру).
|
||
for stale in CLAUDE.md settings.local.json; do
|
||
stale_path="$HOME/.claude/$stale"
|
||
if [ -L "$stale_path" ]; then
|
||
case "$(readlink "$stale_path" 2>/dev/null || true)" in
|
||
"$DOTFILES_DIR"/*)
|
||
rm "$stale_path"
|
||
info "Удалён устаревший симлинк ~/.claude/$stale"
|
||
;;
|
||
esac
|
||
fi
|
||
done
|
||
if [ -f "$HOME/.claude/statusline-command.sh" ] && [ ! -L "$HOME/.claude/statusline-command.sh" ] \
|
||
&& grep -q "Статусная строка Claude Code" "$HOME/.claude/statusline-command.sh" 2>/dev/null; then
|
||
rm "$HOME/.claude/statusline-command.sh"
|
||
info "Удалена устаревшая копия ~/.claude/statusline-command.sh"
|
||
fi
|
||
|
||
# Skills (симлинк)
|
||
if [ -d "$SKILLS_SRC" ]; then
|
||
ensure_symlink "$SKILLS_SRC" "$HOME/.claude/skills"
|
||
fi
|
||
|
||
# ── Codex ────────────────────────────────────────────────────────────
|
||
# Пакета в репозитории нет: ~/.codex — обычный каталог, его заполняет Codex.
|
||
# Миграция раскрывает старый свёрнутый симлинк и переносит рантайм-данные.
|
||
|
||
migrate_folded_symlink "$HOME/.codex" .gitkeep
|
||
|
||
# ── 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 ────────────────────────────────────────────────
|
||
|
||
setup_wsl_hproxy
|
||
|
||
info "Готово!"
|