- Зачем: - расширить набор переиспользуемых скиллов для повседневных задач: ревью PR, работа с GitHub, проверка dotfiles, диагностика сети, ревью кода менти. - Что: - добавлен differential-review (Trail of Bits) — security-focused ревью PR. - добавлен github (Callstack) — workflow через gh CLI, stacked PRs. - создан dotfiles-check — bash-скрипт проверки целостности stow-симлинков. - создан proxy-debug — диагностика сети и прокси hproxy.local. - создан review-homework — ревью кода менти в обучающем формате. - Проверка: - bash ~/.agents/skills/dotfiles-check/check.sh ~/dotfiles
151 lines
6.1 KiB
Bash
Executable File
151 lines
6.1 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Проверка целостности dotfiles-установки через stow.
|
|
# Использование: bash check.sh [путь-к-dotfiles]
|
|
|
|
set -euo pipefail
|
|
|
|
DOTFILES_DIR="${1:-$(cd "$(dirname "$0")/../../../.." && pwd)}"
|
|
|
|
GREEN='\033[0;32m'
|
|
RED='\033[0;31m'
|
|
YELLOW='\033[1;33m'
|
|
NC='\033[0m'
|
|
|
|
ok=0; warn=0; fail=0
|
|
|
|
pass() { echo -e " ${GREEN}✅${NC} $*"; ok=$((ok + 1)); }
|
|
skip() { echo -e " ${YELLOW}⚠️${NC} $*"; warn=$((warn + 1)); }
|
|
err() { echo -e " ${RED}❌${NC} $*"; fail=$((fail + 1)); }
|
|
|
|
echo "Проверка dotfiles ($DOTFILES_DIR)"
|
|
echo "═══════════════════════════════════════"
|
|
|
|
# --- 1. Stow-пакеты: проверка симлинков ---
|
|
# Stow использует "tree folding": если вся директория принадлежит одному пакету,
|
|
# создаётся один симлинк на директорию, а не на каждый файл внутри.
|
|
# Поэтому проверяем точки входа верхнего уровня каждого пакета.
|
|
|
|
for pkg_dir in "$DOTFILES_DIR"/*/; do
|
|
pkg=$(basename "$pkg_dir")
|
|
[[ "$pkg" == .* ]] && continue
|
|
|
|
# Точки входа — элементы первого уровня внутри пакета
|
|
entries=()
|
|
for entry in "$pkg_dir".* "$pkg_dir"*; do
|
|
[ -e "$entry" ] || continue
|
|
base=$(basename "$entry")
|
|
[[ "$base" == "." || "$base" == ".." ]] && continue
|
|
# Берём только dot-файлы/директории (то, что stow раскладывает в ~)
|
|
[[ "$base" == .* ]] && entries+=("$base")
|
|
done
|
|
|
|
[ ${#entries[@]} -eq 0 ] && continue
|
|
|
|
echo ""
|
|
echo "$pkg (${#entries[@]} точек входа):"
|
|
|
|
for entry in "${entries[@]}"; do
|
|
home_path="$HOME/$entry"
|
|
expected="$DOTFILES_DIR/$pkg/$entry"
|
|
|
|
# Если это директория в пакете — stow мог создать симлинк на неё целиком
|
|
# или "раскрыть" её и создать симлинки внутри. Проверяем оба варианта.
|
|
if [ -L "$home_path" ]; then
|
|
# Симлинк на верхнем уровне (tree folding)
|
|
actual=$(readlink -f "$home_path" 2>/dev/null || true)
|
|
expected_resolved=$(readlink -f "$expected" 2>/dev/null || true)
|
|
if [ "$actual" = "$expected_resolved" ]; then
|
|
pass "~/$entry"
|
|
else
|
|
err "~/$entry → $(readlink "$home_path") (ожидался $pkg/$entry)"
|
|
fi
|
|
elif [ -d "$home_path" ] && [ -d "$expected" ]; then
|
|
# Директория раскрыта — проверяем файлы внутри (один уровень)
|
|
inner_ok=0; inner_fail=0
|
|
while IFS= read -r -d '' f; do
|
|
fname=$(basename "$f")
|
|
inner_home="$home_path/$fname"
|
|
if [ -L "$inner_home" ]; then
|
|
inner_actual=$(readlink -f "$inner_home" 2>/dev/null || true)
|
|
inner_expected=$(readlink -f "$f" 2>/dev/null || true)
|
|
if [ "$inner_actual" = "$inner_expected" ]; then
|
|
inner_ok=$((inner_ok + 1))
|
|
else
|
|
inner_fail=$((inner_fail + 1))
|
|
fi
|
|
elif [ -e "$inner_home" ]; then
|
|
inner_ok=$((inner_ok + 1)) # файл есть, но не симлинк — ок для stow unfolded
|
|
else
|
|
inner_fail=$((inner_fail + 1))
|
|
fi
|
|
done < <(find "$expected" -maxdepth 1 -not -path "$expected" -print0)
|
|
|
|
if [ "$inner_fail" -eq 0 ]; then
|
|
pass "~/$entry/ ($inner_ok элементов)"
|
|
else
|
|
err "~/$entry/ — $inner_fail из $((inner_ok + inner_fail)) элементов отсутствуют"
|
|
fi
|
|
elif [ -e "$home_path" ]; then
|
|
skip "~/$entry — обычный файл (не симлинк)"
|
|
else
|
|
err "~/$entry — не найден"
|
|
fi
|
|
done
|
|
done
|
|
|
|
# --- 2. Дополнительные проверки из install.sh ---
|
|
install_script="$DOTFILES_DIR/install.sh"
|
|
if [ -f "$install_script" ]; then
|
|
echo ""
|
|
echo "Доп. проверки (install.sh):"
|
|
|
|
# chmod 444 на settings.json
|
|
if [ -f "$DOTFILES_DIR/claude/.claude/settings.json" ]; then
|
|
perms=$(stat -c %a "$DOTFILES_DIR/claude/.claude/settings.json" 2>/dev/null || true)
|
|
if [ "$perms" = "444" ]; then
|
|
pass "claude/settings.json — chmod 444"
|
|
else
|
|
skip "claude/settings.json — chmod $perms (ожидался 444)"
|
|
fi
|
|
fi
|
|
|
|
# settings.local.json
|
|
if [ -f "$HOME/.claude/settings.local.json" ]; then
|
|
pass "~/.claude/settings.local.json существует"
|
|
else
|
|
skip "~/.claude/settings.local.json — не найден (создайте через install.sh)"
|
|
fi
|
|
|
|
# bashrc
|
|
if grep -q 'bash_custom' "$HOME/.bashrc" 2>/dev/null; then
|
|
pass "~/.bashrc содержит подключение bash_custom"
|
|
else
|
|
skip "~/.bashrc не содержит bash_custom"
|
|
fi
|
|
fi
|
|
|
|
# --- 3. Битые симлинки ---
|
|
broken=$(find "$HOME" -maxdepth 3 -xtype l 2>/dev/null | grep -v '.cache\|.local/share\|node_modules\|.npm' | head -10)
|
|
if [ -n "$broken" ]; then
|
|
echo ""
|
|
echo "Битые симлинки в ~:"
|
|
while IFS= read -r link; do
|
|
err "$link → $(readlink "$link") (цель не существует)"
|
|
done <<< "$broken"
|
|
fi
|
|
|
|
# --- 4. Секреты ---
|
|
if [ -f "$HOME/.env.secrets" ]; then
|
|
echo ""
|
|
echo "Секреты:"
|
|
pass "~/.env.secrets существует"
|
|
fi
|
|
|
|
# --- Итого ---
|
|
total=$((ok + warn + fail))
|
|
echo ""
|
|
echo "═══════════════════════════════════════"
|
|
echo -e "Итого: ${GREEN}$ok${NC}/${total} ✅, ${YELLOW}$warn${NC} ⚠️, ${RED}$fail${NC} ❌"
|
|
|
|
[ "$fail" -eq 0 ] && exit 0 || exit 1
|