#!/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 nhưng đọc được: thanh gradient + tiêu đề rõ # ----------------------------- repeat_char() { # $1 = số lượng, $2 = ký tự printf "%${1}s" "" | tr ' ' "$2" } # In 1 thanh block gradient rainbow (TTY) hoặc '#' (non-TTY) rainbow_bar() { local width="$1" if [ -t 1 ]; then local -a codes=(51 87 123 159 220 214 209 204) local n=${#codes[@]} seg i line="" for ((i = 0; i < n; i++)); do seg=$(( (width + n - 1 - i) / n )) line+="$(printf '\033[1;38;5;%dm' "${codes[$i]}")""$(repeat_char "$seg" "█")" done printf '%s%s\n' "$line" "$C_RESET" else repeat_char "$width" "#" echo fi } print_banner() { echo "" rainbow_bar 56 echo "" printf ' %s🧹 CLEANUP-SERVER%s\n' "$C_TITLE" "$C_RESET" printf ' %sDọn dẹp + nâng cấp server (Ubuntu / Arch)%s\n' "$C_WARN" "$C_RESET" echo "" rainbow_bar 56 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 "$@"