#!/usr/bin/env bash # clean-system — безопасная интерактивная очистка системы set -euo pipefail _size() { du -sh "$1" 2>/dev/null | cut -f1 || echo "0" } _freed() { local before=$1 after=$2 # оба в байтах через numfmt local b a b=$(numfmt --from=iec "$before" 2>/dev/null || echo 0) a=$(numfmt --from=iec "$after" 2>/dev/null || echo 0) local diff=$(( b - a )) (( diff > 0 )) && numfmt --to=iec "$diff" || echo "0" } # ── Задачи очистки ─────────────────────────────────────────── # Формат: "id|описание|команда|путь_для_размера" declare -A TASK_CMD TASK_PATH TASK_DESC _register() { local id=$1 desc=$2 path=$3; shift 3 TASK_DESC[$id]="$desc" TASK_PATH[$id]="$path" TASK_CMD[$id]="$*" } _register pacman-cache \ "Pacman кеш (оставить 2 версии)" \ "/var/cache/pacman/pkg" \ sudo paccache -rk2 _register paru-cache \ "Paru build кеш" \ "$HOME/.cache/paru" \ paru -Sc --noconfirm _register uv-cache \ "uv (Python) кеш" \ "$HOME/.cache/uv" \ uv cache clean _register npm-cache \ "npm кеш" \ "$HOME/.npm" \ npm cache clean --force _register cargo-cache \ "Cargo registry (скачанные крейты)" \ "$HOME/.cargo/registry" \ rm -rf "$HOME/.cargo/registry/cache" "$HOME/.cargo/registry/src" _register restic-cache \ "Restic старые кеши" \ "$HOME/.cache/restic" \ restic cache --cleanup _register thumbnails \ "Кеш миниатюр" \ "$HOME/.cache/thumbnails" \ rm -rf "$HOME/.cache/thumbnails" _register journal \ "Systemd journal (оставить 2 недели)" \ "" \ journalctl --vacuum-time=2weeks _register pip-cache \ "pip кеш" \ "$HOME/.cache/pip" \ pip3 cache purge _register zsh-compdump \ "Zsh completion cache (пересоздастся автоматически)" \ "" \ bash -c 'rm -f "$HOME"/.config/zsh/.zcompdump*' # ── Построение меню ────────────────────────────────────────── main() { if ! command -v fzf &>/dev/null; then echo "fzf not found" exit 1 fi local ORDERED=(pacman-cache paru-cache uv-cache npm-cache cargo-cache restic-cache thumbnails journal pip-cache zsh-compdump) local lines=() for id in "${ORDERED[@]}"; do local size="" local path="${TASK_PATH[$id]}" if [[ -n "$path" && ( -d "$path" || -f "$path" ) ]]; then size=$(_size "$path") else size="—" fi lines+=("$(printf "%-14s %6s %s" "$id" "$size" "${TASK_DESC[$id]}")") done local selected selected=$(printf '%s\n' "${lines[@]}" | fzf \ --multi \ --prompt "clean > " \ --header "Пробел — выбрать | Enter — запустить | Ctrl-A — выбрать всё" \ --bind "ctrl-a:select-all" \ --reverse \ --height=50%) || exit 0 [[ -z "$selected" ]] && exit 0 local total_freed=0 while IFS= read -r line; do local id id=$(echo "$line" | awk '{print $1}') local path="${TASK_PATH[$id]}" local before="" [[ -n "$path" && ( -d "$path" || -f "$path" ) ]] && before=$(_size "$path") echo "" echo "▶ ${TASK_DESC[$id]}" eval "${TASK_CMD[$id]}" || true if [[ -n "$path" && -n "$before" ]]; then local after after=$(_size "$path") local freed freed=$(_freed "$before" "$after") echo " → освобождено: $freed" fi done <<< "$selected" echo "" echo "Готово." } main "$@"