Files
bin-scripts/cleanup-server
T
thuanleandClaude ce49f1ce5c Harden step_snap: dedupe Set ids (multi-snap sets)
Local flow verified end-to-end (macOS guards). Remote Ubuntu/Arch
verification deferred to user (interactive, destructive on real hosts).

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-22 00:20:20 +07:00

382 lines
13 KiB
Bash
Executable File

#!/bin/bash
# cleanup-server - Dọn dẹp + nâng cấp server (Ubuntu/Arch), interactive, tuần tự.
# Cách dùng:
# cleanup-server # hiện menu chọn target (local hoặc server alias)
# cleanup-server --run-steps # (nội bộ) chạy thẳng 6 bước trên máy hiện tại
# -----------------------------
# Color helpers (tự tắt khi không phải TTY)
# -----------------------------
if [ -t 1 ]; then
C_RESET=$'\033[0m'
C_TITLE=$'\033[1;36m' # tiêu đề
C_OK=$'\033[1;32m' # thành công
C_DEL=$'\033[1;31m' # sẽ xóa / lỗi
C_WARN=$'\033[1;33m' # cảnh báo
else
C_RESET=""; C_TITLE=""; C_OK=""; C_DEL=""; C_WARN=""
fi
# -----------------------------
# Banner rainbow (giống style `cleanup`)
# -----------------------------
print_banner() {
local use_color=0 reset="" i=0 line
local -a lines=(
' ██████╗██╗ ██╗ ██████╗██╗ ██╗██╗ ██╗██████╗ ███████╗███╗ ██╗ ██████╗██╗ ██╗'
' ██╔════╝██║ ██║██╔════╝██║ ██║██║ ██║██╔══██╗ ██╔════╝████╗ ██║██╔═══██╗╚██╗██╔╝'
' ██║ ██║ ██║██║ ███████║██║ ██║██║ ██║ █████╗ ██╔██╗ ██║██║ ██║ ╚███╔╝ '
' ██║ ██║ ██║██║ ██╔══██║██║ ██║██║ ██║ ██╔══╝ ██║╚██╗██║██║ ██║ ██╔██╗ '
' ╚██████╗╚██████╔╝╚██████╗██║ ██║╚██████╔╝██████╔╝ ███████╗██║ ╚████║╚██████╔╝██╔╝ ██╗'
' ╚═════╝ ╚═════╝ ╚═════╝╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚══════╝╚═╝ ╚═══╝ ╚═════╝ ╚═╝ ╚═╝'
)
local -a colors=()
if [ -t 1 ]; then
use_color=1
reset=$'\033[0m'
colors=(
$'\033[1;38;5;51m' $'\033[1;38;5;87m' $'\033[1;38;5;123m'
$'\033[1;38;5;159m' $'\033[1;38;5;220m' $'\033[1;38;5;214m'
)
fi
echo ""
for line in "${lines[@]}"; do
if [ "$use_color" -eq 1 ]; then
printf '%b%s%b\n' "${colors[$i]}" "$line" "$reset"
else
printf '%s\n' "$line"
fi
i=$((i + 1))
done
echo ""
}
# -----------------------------
# Phát hiện distro qua /etc/os-release → debian | arch | unknown
# -----------------------------
detect_distro() {
[ -r /etc/os-release ] || { echo unknown; return; }
local id id_like
. /etc/os-release 2>/dev/null
id="${ID:-}"; id_like="${ID_LIKE:-}"
case "$id $id_like" in
*arch*) echo arch ;;
*debian*) echo debian ;;
*ubuntu*) echo debian ;;
*) echo unknown ;;
esac
}
# -----------------------------
# Hỏi Y/n (Enter = Yes). Trả về 0 = đồng ý, 1 = hủy.
# -----------------------------
confirm_yn() {
local prompt="$1" confirm
read -r -p " ${prompt} (Y/n): " confirm
if [[ -n "$confirm" && ! "$confirm" =~ ^([yY]|[yY][eE][sS])$ ]]; then
echo " Đã hủy."
return 1
fi
return 0
}
# -----------------------------
# Danh sách target fix cứng (alias từ ~/.ssh/config)
# -----------------------------
TARGETS=("local" "ta" "toc" "oc" "cse" "vrc" "aws" "tk" "od")
TARGET_CURSOR=0
TARGET=""
# Vẽ menu single-select
draw_target_menu() {
if [ "$1" = "redraw" ]; then
printf '\033[%dA' "$(( ${#TARGETS[@]} + 2 ))"
fi
echo " Chọn target (↑↓: di chuyển | Enter: chọn):"
echo ""
local i
for ((i = 0; i < ${#TARGETS[@]}; i++)); do
local prefix=" "
if [ "$i" -eq "$TARGET_CURSOR" ]; then
prefix="${C_TITLE}${C_RESET}"
fi
printf '\r\033[K%s%s\n' "$prefix" "${TARGETS[$i]}"
done
}
# Chạy menu, đặt biến toàn cục TARGET
select_target() {
draw_target_menu
while true; do
IFS= read -rsn1 key
case "$key" in
$'\x1b')
read -rsn2 seq
case "$seq" in
'[A') TARGET_CURSOR=$(( (TARGET_CURSOR - 1 + ${#TARGETS[@]}) % ${#TARGETS[@]} ));;
'[B') TARGET_CURSOR=$(( (TARGET_CURSOR + 1) % ${#TARGETS[@]} ));;
esac
;;
'') # Enter
echo ""
TARGET="${TARGETS[$TARGET_CURSOR]}"
return
;;
esac
draw_target_menu "redraw"
done
}
# -----------------------------
# Bước 1: Nâng cấp hệ thống theo distro
# -----------------------------
step_upgrade() {
echo "${C_TITLE}--- [System Upgrade] ---${C_RESET}"
local distro
distro=$(detect_distro)
case "$distro" in
debian)
echo " Distro: Debian/Ubuntu (apt)."
sudo apt-get update || { echo "${C_DEL} ✗ apt-get update thất bại.${C_RESET}"; echo ""; return; }
local upgradable
upgradable=$(apt list --upgradable 2>/dev/null | grep -c 'upgradable')
echo " ${C_WARN}${upgradable} package có thể nâng cấp.${C_RESET}"
if confirm_yn "Upgrade + autoremove?"; then
sudo apt-get upgrade -y
sudo apt-get autoremove --purge -y
echo "${C_OK} ✓ Đã nâng cấp xong.${C_RESET}"
fi
;;
arch)
echo " Distro: Arch (pacman)."
local helper=""
command -v paru >/dev/null 2>&1 && helper="paru"
[ -z "$helper" ] && command -v yay >/dev/null 2>&1 && helper="yay"
[ -z "$helper" ] && helper="sudo pacman"
echo " Helper: $helper"
if confirm_yn "Chạy $helper -Syu?"; then
$helper -Syu
echo "${C_OK} ✓ Đã nâng cấp xong.${C_RESET}"
fi
;;
*)
echo "${C_WARN} ⚠ Distro không hỗ trợ upgrade, bỏ qua.${C_RESET}"
;;
esac
echo ""
}
# -----------------------------
# Chọn 'docker' hoặc 'sudo docker' tùy permission
# Trả về chuỗi lệnh qua echo (gán: DC=$(docker_cmd))
# -----------------------------
docker_cmd() {
if docker info >/dev/null 2>&1; then
echo "docker"
elif sudo -n docker info >/dev/null 2>&1; then
echo "sudo docker"
else
echo ""
fi
}
# -----------------------------
# Bước 2: Docker — df rồi prune từng loại
# -----------------------------
step_docker() {
echo "${C_TITLE}--- [Docker] ---${C_RESET}"
if ! command -v docker >/dev/null 2>&1; then
echo "${C_WARN} ⚠ docker command not found.${C_RESET}"
echo ""
return
fi
local DC
DC=$(docker_cmd)
if [ -z "$DC" ]; then
echo "${C_WARN} ⚠ Không có quyền truy cập docker (cần group docker hoặc sudo).${C_RESET}"
echo ""
return
fi
echo " Lệnh: $DC"
$DC system df
echo ""
confirm_yn "Prune images (-a)?" && $DC image prune -a -f
confirm_yn "Prune containers?" && $DC container prune -f
confirm_yn "Prune volumes?" && $DC volume prune -f
confirm_yn "Prune build cache?" && $DC builder prune -f
confirm_yn "Prune networks?" && $DC network prune -f
echo "${C_OK} ✓ Docker cleanup xong.${C_RESET}"
echo ""
}
# -----------------------------
# Bước 3: Journal logs — vacuum log cũ hơn 7 ngày
# -----------------------------
step_journal() {
echo "${C_TITLE}--- [Journal Logs] ---${C_RESET}"
if ! command -v journalctl >/dev/null 2>&1; then
echo "${C_WARN} ⚠ journalctl not found.${C_RESET}"
echo ""
return
fi
sudo journalctl --disk-usage
if confirm_yn "Vacuum log cũ hơn 7 ngày (--vacuum-time=7d)?"; then
sudo journalctl --vacuum-time=7d
echo "${C_OK} ✓ Đã dọn journal.${C_RESET}"
fi
echo ""
}
# -----------------------------
# Bước 4: Package cache — apt clean+autoremove hoặc paccache/pacman -Sc
# -----------------------------
step_pkg_cache() {
echo "${C_TITLE}--- [Package Cache] ---${C_RESET}"
local distro
distro=$(detect_distro)
case "$distro" in
debian)
local d="/var/cache/apt/archives"
[ -d "$d" ] && sudo du -sh "$d"
if confirm_yn "apt clean + autoremove?"; then
sudo apt-get clean
sudo apt-get autoremove -y
echo "${C_OK} ✓ Đã dọn apt cache.${C_RESET}"
fi
;;
arch)
local d="/var/cache/pacman/pkg"
[ -d "$d" ] && sudo du -sh "$d"
if command -v paccache >/dev/null 2>&1; then
sudo paccache -dk2 # dry-run xem trước
if confirm_yn "paccache -rk2 (giữ 2 bản)?"; then
sudo paccache -rk2
echo "${C_OK} ✓ Đã dọn pacman cache.${C_RESET}"
fi
else
if confirm_yn "Không có paccache, chạy pacman -Sc?"; then
sudo pacman -Sc --noconfirm
echo "${C_OK} ✓ Đã dọn pacman cache.${C_RESET}"
fi
fi
;;
*)
echo "${C_WARN} ⚠ Distro không hỗ trợ dọn package cache.${C_RESET}"
;;
esac
echo ""
}
# -----------------------------
# Bước 5: Snap — forget từng snapshot
# -----------------------------
step_snap() {
echo "${C_TITLE}--- [Snap] ---${C_RESET}"
if ! command -v snap >/dev/null 2>&1; then
echo "${C_WARN} ⚠ snap not found.${C_RESET}"
echo ""
return
fi
local saved
# Cột đầu (Set) là id cần forget; một Set có thể có nhiều Snap → dedupe.
saved=$(sudo snap saved 2>/dev/null | awk 'NR>1{print $1}' | awk '!seen[$0]++')
if [ -z "$saved" ]; then
echo "${C_OK} ✓ Không có snap snapshot nào.${C_RESET}"
echo ""
return
fi
local id
for id in $saved; do
if confirm_yn "Forget snap snapshot (Set $id)?"; then
sudo snap forget "$id"
fi
done
echo "${C_OK} ✓ Snap cleanup xong.${C_RESET}"
echo ""
}
# -----------------------------
# Bước 6: /tmp & cache cũ — liệt kê file >7 ngày, xác nhận rồi xóa
# -----------------------------
step_tmp_cache() {
echo "${C_TITLE}--- [/tmp & Cache cũ] ---${C_RESET}"
local days=7
echo " Tìm file cũ hơn ${days} ngày trong /tmp /var/tmp:"
local old_files
old_files=$(sudo find /tmp /var/tmp -type f -mtime +"$days" 2>/dev/null)
if [ -z "$old_files" ]; then
echo "${C_OK} ✓ Không có file cũ >${days} ngày.${C_RESET}"
echo ""
return
fi
local count total_kb total_mib
count=$(printf '%s\n' "$old_files" | grep -c .)
total_kb=$(printf '%s\n' "$old_files" | xargs sudo du -ck 2>/dev/null | tail -1 | cut -f1)
total_mib=$(( total_kb / 1024 ))
echo "${C_DEL}${count} file, ~${total_mib} MiB.${C_RESET}"
printf '%s\n' "$old_files" | head -20 | sed 's/^/ - /'
[ "$count" -gt 20 ] && echo " ... (và $((count - 20)) file nữa)"
if confirm_yn "Xóa các file cũ >${days} ngày?"; then
printf '%s\n' "$old_files" | xargs sudo rm -f --
echo "${C_OK} ✓ Đã xóa.${C_RESET}"
fi
echo ""
}
# -----------------------------
# Chạy tuần tự 6 bước (stub — các bước thêm ở Task 4-9)
# -----------------------------
run_all_steps() {
step_upgrade
step_docker
step_journal
step_pkg_cache
step_snap
step_tmp_cache
}
# -----------------------------
# Chạy trên target đã chọn.
# local → chạy tại chỗ.
# alias → self-bootstrap: base64 script qua argv, giữ stdin=terminal
# để read/màu tương tác hoạt động qua PTY của `ssh -t`.
# -----------------------------
run_on_target() {
local target="$1"
if [ "$target" = "local" ]; then
run_all_steps
return $?
fi
local b64
b64=$(base64 < "$0" | tr -d '\n') || {
echo "${C_DEL} ✗ Không mã hóa được script.${C_RESET}"
return 1
}
# Giải mã ra file tạm trên remote rồi chạy; stdin không bị redirect → read OK.
ssh -t "$target" \
"umask 077; t=\$(mktemp); printf '%s' '$b64' | base64 -d > \"\$t\" && bash \"\$t\" --run-steps; r=\$?; rm -f \"\$t\"; exit \$r" \
|| { echo "${C_DEL} ✗ ssh tới $target thất bại.${C_RESET}"; return 1; }
}
# -----------------------------
# Main (phân nhánh theo argv)
# -----------------------------
main() {
if [ "${1:-}" = "--run-steps" ]; then
echo "${C_TITLE}===== 🧹 Cleanup-server (--run-steps) =====${C_RESET}"
echo ""
run_all_steps
echo ""
echo "${C_OK}===== ✅ Cleanup-server hoàn tất =====${C_RESET}"
return
fi
print_banner
echo "${C_TITLE}===== 🧹 Cleanup-server =====${C_RESET}"
echo ""
select_target
echo ""
run_on_target "$TARGET"
}
main "$@"