mirror of
https://hubproxy.babadafafafafa.cn/https://github.com/Mapleawaa/PVE-Tools-9.git
synced 2026-09-20 08:03:35 +08:00
Unify all menus on lib/menu.sh (run_menu), harden GPU/GRUB writes with two-tier confirms and markers, fix remote/self-update to Releases assets, and close real CI quality gates with SHA256SUMS. Bump to 11.0.0 / Liino.
486 lines
20 KiB
Bash
486 lines
20 KiB
Bash
#!/bin/bash
|
||
# SPDX-License-Identifier: GPL-3.0-only
|
||
# Copyright (C) 2026 Ciriu Networks
|
||
|
||
garbage_cleanup_sum_sizes() {
|
||
awk -F'|' '{sum += $2} END {print sum + 0}'
|
||
}
|
||
garbage_cleanup_count_records() {
|
||
awk 'NF {count++} END {print count + 0}'
|
||
}
|
||
garbage_cleanup_temp_file_candidates() {
|
||
local age_days="${1:-3}"
|
||
|
||
[[ "$age_days" =~ ^[0-9]+$ ]] || age_days=3
|
||
if [[ -d /tmp ]]; then
|
||
find /tmp -maxdepth 1 -type f \( \
|
||
-name 'pve-tools-*' -o \
|
||
-name 'pve-third-party-*' -o \
|
||
-name 'fastpve-install.*.sh' -o \
|
||
-name 'pve8to9_check.log' -o \
|
||
-name 'pve-qemu-kvm.deb' \
|
||
\) -mtime +"$age_days" -printf '%p|%s|%TY-%Tm-%Td %TH:%TM|临时文件\n' 2>/dev/null || true
|
||
fi
|
||
}
|
||
garbage_cleanup_pve_tools_old_file_candidates() {
|
||
local age_days="${1:-90}"
|
||
local root
|
||
|
||
[[ "$age_days" =~ ^[0-9]+$ ]] || age_days=90
|
||
for root in "/var/backups/pve-tools" "$VM_CONFIG_EXPORT_DIR"; do
|
||
if [[ -d "$root" ]]; then
|
||
find "$root" -type f -mtime +"$age_days" -printf '%p|%s|%TY-%Tm-%Td %TH:%TM|PVE-Tools 旧文件\n' 2>/dev/null || true
|
||
fi
|
||
done
|
||
}
|
||
garbage_cleanup_print_file_records() {
|
||
local records="$1"
|
||
local idx=1 path size mtime reason
|
||
|
||
printf "%-5s %-10s %-16s %-18s %s\n" "序号" "大小" "时间" "类型" "文件"
|
||
echo "$UI_DIVIDER"
|
||
while IFS='|' read -r path size mtime reason; do
|
||
[[ -n "$path" ]] || continue
|
||
printf "%-5s %-10s %-16s %-18s %s\n" "$idx" "$(pve_tools_human_bytes "$size")" "$mtime" "$reason" "$path"
|
||
idx=$((idx + 1))
|
||
done <<< "$records"
|
||
}
|
||
garbage_cleanup_delete_file_records() {
|
||
local records="$1"
|
||
local success=0 failed=0 skipped=0 path size mtime reason mode answer
|
||
|
||
if [[ -z "$records" ]]; then
|
||
log_warn "没有可删除的候选文件。"
|
||
return 0
|
||
fi
|
||
|
||
read -p "删除模式:输入 all 批量删除,输入 item 逐项确认 [item]: " mode
|
||
mode="${mode:-item}"
|
||
[[ "$mode" == "all" || "$mode" == "item" ]] || mode="item"
|
||
|
||
while IFS='|' read -r path size mtime reason; do
|
||
[[ -n "$path" && -f "$path" ]] || {
|
||
skipped=$((skipped + 1))
|
||
continue
|
||
}
|
||
if [[ "$mode" == "item" ]]; then
|
||
read -p "删除 $path ? 输入 yes 确认: " answer
|
||
if [[ "$answer" != "yes" && "$answer" != "YES" ]]; then
|
||
skipped=$((skipped + 1))
|
||
continue
|
||
fi
|
||
fi
|
||
if rm -f -- "$path"; then
|
||
log_success "已删除: $path"
|
||
success=$((success + 1))
|
||
else
|
||
log_error "删除失败: $path"
|
||
failed=$((failed + 1))
|
||
fi
|
||
done <<< "$records"
|
||
|
||
log_info "删除完成:成功 $success,失败 $failed,跳过 $skipped"
|
||
}
|
||
garbage_cleanup_basic() {
|
||
block_non_pve9_destructive "垃圾清理(缓存/日志/临时文件)" || return 1
|
||
|
||
local temp_age journal_days pve_age auto_remove records total_size
|
||
read -p "清理 /tmp 中超过多少天的 PVE-Tools 临时文件 [3]: " temp_age
|
||
temp_age="${temp_age:-3}"
|
||
[[ "$temp_age" =~ ^[0-9]+$ ]] || temp_age=3
|
||
|
||
read -p "systemd-journal 保留天数 [14]: " journal_days
|
||
journal_days="${journal_days:-14}"
|
||
[[ "$journal_days" =~ ^[0-9]+$ ]] || journal_days=14
|
||
|
||
read -p "PVE-Tools 备份/导出文件保留天数 [90]: " pve_age
|
||
pve_age="${pve_age:-90}"
|
||
[[ "$pve_age" =~ ^[0-9]+$ ]] || pve_age=90
|
||
|
||
read -p "是否执行 apt autoremove 清理孤立依赖?输入 yes 启用 [no]: " auto_remove
|
||
auto_remove="${auto_remove:-no}"
|
||
|
||
records="$(
|
||
garbage_cleanup_temp_file_candidates "$temp_age"
|
||
garbage_cleanup_pve_tools_old_file_candidates "$pve_age"
|
||
)"
|
||
total_size="$(echo "$records" | garbage_cleanup_sum_sizes)"
|
||
|
||
clear
|
||
show_menu_header "垃圾清理预览"
|
||
echo -e "${CYAN}将执行:${NC}"
|
||
echo " - apt-get autoclean 清理过期软件包缓存"
|
||
echo " - journalctl --vacuum-time=${journal_days}d 压缩系统日志保留窗口"
|
||
echo " - 删除超过 ${temp_age} 天的 PVE-Tools 临时文件"
|
||
echo " - 删除超过 ${pve_age} 天的 PVE-Tools 备份/导出文件"
|
||
[[ "$auto_remove" == "yes" || "$auto_remove" == "YES" ]] && echo " - apt-get autoremove -y 清理孤立依赖"
|
||
echo "$UI_DIVIDER"
|
||
if [[ -n "$records" ]]; then
|
||
garbage_cleanup_print_file_records "$records"
|
||
echo "$UI_DIVIDER"
|
||
echo -e "${YELLOW}候选文件合计:${NC} $(pve_tools_human_bytes "$total_size")"
|
||
else
|
||
echo " 未发现符合年龄条件的 PVE-Tools 临时/旧文件。"
|
||
fi
|
||
|
||
if ! confirm_high_risk_action "执行垃圾清理" "会删除上方列出的本工具临时/旧文件,并清理 apt 缓存与 journal 日志窗口。" "文件删除不可逆;apt autoremove 如果启用,可能移除系统认为不再需要的依赖包。" "请确认候选列表只包含可丢弃文件,重要备份已另存。" "CLEAN"; then
|
||
return 0
|
||
fi
|
||
|
||
log_step "清理 apt 过期缓存..."
|
||
apt-get autoclean -y || log_warn "apt-get autoclean 执行失败,请检查 apt 状态。"
|
||
|
||
if [[ "$auto_remove" == "yes" || "$auto_remove" == "YES" ]]; then
|
||
log_step "清理孤立依赖..."
|
||
apt-get autoremove -y || log_warn "apt-get autoremove 执行失败,请检查 apt 状态。"
|
||
fi
|
||
|
||
if command -v journalctl >/dev/null 2>&1; then
|
||
log_step "压缩 systemd journal 日志..."
|
||
journalctl --vacuum-time="${journal_days}d" || log_warn "journalctl 日志清理失败。"
|
||
fi
|
||
|
||
garbage_cleanup_delete_file_records "$records"
|
||
}
|
||
garbage_cleanup_backup_candidates() {
|
||
local mode="${1:-both}"
|
||
local age_days="${2:-180}"
|
||
local now cutoff path size mtime type vmid epoch old orphan reason
|
||
|
||
[[ "$age_days" =~ ^[0-9]+$ ]] || age_days=180
|
||
now="$(date +%s)"
|
||
cutoff=$((now - age_days * 86400))
|
||
|
||
while IFS='|' read -r path size mtime; do
|
||
[[ -n "$path" && -f "$path" ]] || continue
|
||
type="$(vm_backup_archive_guest_type "$path")"
|
||
vmid="$(vm_backup_archive_vmid "$path")"
|
||
epoch="$(stat -c '%Y' "$path" 2>/dev/null || echo 0)"
|
||
old=0
|
||
orphan=0
|
||
reason=""
|
||
|
||
if [[ "$epoch" =~ ^[0-9]+$ ]] && (( epoch > 0 && epoch < cutoff )); then
|
||
old=1
|
||
reason="超过 ${age_days} 天"
|
||
fi
|
||
if [[ -n "$vmid" && "$type" != "未知" ]] && ! pve_guest_exists "$type" "$vmid"; then
|
||
orphan=1
|
||
[[ -n "$reason" ]] && reason+=","
|
||
reason+="无对应 ${type} ${vmid}"
|
||
fi
|
||
|
||
case "$mode" in
|
||
old) (( old == 1 )) || continue ;;
|
||
orphan) (( orphan == 1 )) || continue ;;
|
||
both|*) (( old == 1 || orphan == 1 )) || continue ;;
|
||
esac
|
||
printf '%s|%s|%s|%s|%s|%s\n' "$path" "$size" "$mtime" "$type" "${vmid:-?}" "$reason"
|
||
done < <(vm_discover_all_backup_archives)
|
||
}
|
||
garbage_cleanup_print_backup_records() {
|
||
local records="$1"
|
||
local idx=1 path size mtime type vmid reason
|
||
|
||
printf "%-5s %-5s %-7s %-10s %-16s %-24s %s\n" "序号" "类型" "VMID" "大小" "时间" "原因" "文件"
|
||
echo "$UI_DIVIDER"
|
||
while IFS='|' read -r path size mtime type vmid reason; do
|
||
[[ -n "$path" ]] || continue
|
||
printf "%-5s %-5s %-7s %-10s %-16s %-24s %s\n" "$idx" "$type" "$vmid" "$(pve_tools_human_bytes "$size")" "$mtime" "$reason" "$path"
|
||
idx=$((idx + 1))
|
||
done <<< "$records"
|
||
}
|
||
garbage_cleanup_delete_backup_records() {
|
||
local records="$1"
|
||
local success=0 failed=0 skipped=0 path size mtime type vmid reason mode answer
|
||
|
||
if [[ -z "$records" ]]; then
|
||
log_warn "没有可删除的备份候选。"
|
||
return 0
|
||
fi
|
||
|
||
read -p "删除模式:输入 all 批量删除,输入 item 逐项确认 [item]: " mode
|
||
mode="${mode:-item}"
|
||
[[ "$mode" == "all" || "$mode" == "item" ]] || mode="item"
|
||
|
||
while IFS='|' read -r path size mtime type vmid reason; do
|
||
[[ -n "$path" && -f "$path" ]] || {
|
||
skipped=$((skipped + 1))
|
||
continue
|
||
}
|
||
if [[ "$mode" == "item" ]]; then
|
||
echo "备份: ${type:-?} ${vmid:-?} | $(pve_tools_human_bytes "$size") | ${reason:-未标注原因}"
|
||
read -p "删除 $path ? 输入 yes 确认: " answer
|
||
if [[ "$answer" != "yes" && "$answer" != "YES" ]]; then
|
||
skipped=$((skipped + 1))
|
||
continue
|
||
fi
|
||
fi
|
||
if rm -f -- "$path"; then
|
||
log_success "已删除备份: $path"
|
||
success=$((success + 1))
|
||
else
|
||
log_error "备份删除失败: $path"
|
||
failed=$((failed + 1))
|
||
fi
|
||
done <<< "$records"
|
||
|
||
log_info "备份清理完成:成功 $success,失败 $failed,跳过 $skipped"
|
||
}
|
||
garbage_cleanup_prune_backups() {
|
||
block_non_pve9_destructive "清理过期/无主备份文件" || return 1
|
||
vm_require_commands pvesm || return 1
|
||
|
||
local scope age_days records total_size
|
||
clear
|
||
show_menu_header "备份文件清理"
|
||
show_menu_option "1" "只列出无对应 VM/CT 的备份"
|
||
show_menu_option "2" "只列出超过指定天数的备份"
|
||
show_menu_option "3" "同时列出无主备份和过期备份"
|
||
echo "$UI_DIVIDER"
|
||
read -p "请选择筛选范围 [3]: " scope
|
||
scope="${scope:-3}"
|
||
case "$scope" in
|
||
1) scope="orphan" ;;
|
||
2) scope="old" ;;
|
||
*) scope="both" ;;
|
||
esac
|
||
|
||
read -p "过期备份阈值天数 [180]: " age_days
|
||
age_days="${age_days:-180}"
|
||
[[ "$age_days" =~ ^[0-9]+$ ]] || age_days=180
|
||
|
||
records="$(garbage_cleanup_backup_candidates "$scope" "$age_days")"
|
||
clear
|
||
show_menu_header "备份文件清理预览"
|
||
if [[ -z "$records" ]]; then
|
||
log_warn "未发现符合条件的备份文件。"
|
||
return 0
|
||
fi
|
||
|
||
garbage_cleanup_print_backup_records "$records"
|
||
total_size="$(echo "$records" | garbage_cleanup_sum_sizes)"
|
||
echo "$UI_DIVIDER"
|
||
echo -e "${YELLOW}候选备份合计:${NC} $(pve_tools_human_bytes "$total_size")"
|
||
echo -e "${YELLOW}提醒:${NC} NFS/CIFS 共享备份可能仍被其他节点使用,删除前请确认跨机恢复需求。"
|
||
|
||
if ! confirm_high_risk_action "删除上方列出的 vzdump 备份文件" "会永久删除备份文件,删除后无法通过这些备份恢复 VM/CT。" "如果误删最后一个可用备份,后续故障将缺少恢复点。" "请确认已有其他可用备份或确定这些备份不再需要。" "DELETE-BACKUP"; then
|
||
return 0
|
||
fi
|
||
|
||
garbage_cleanup_delete_backup_records "$records"
|
||
}
|
||
garbage_cleanup_snapshot_candidates() {
|
||
local age_days="${1:-90}"
|
||
local cutoff conf vmid guest_type
|
||
|
||
[[ "$age_days" =~ ^[0-9]+$ ]] || age_days=90
|
||
cutoff=$(($(date +%s) - age_days * 86400))
|
||
|
||
for conf in /etc/pve/qemu-server/*.conf /etc/pve/lxc/*.conf; do
|
||
[[ -f "$conf" ]] || continue
|
||
vmid="$(basename "$conf" .conf)"
|
||
guest_type="VM"
|
||
[[ "$conf" == /etc/pve/lxc/* ]] && guest_type="CT"
|
||
awk -v guest_type="$guest_type" -v vmid="$vmid" -v cutoff="$cutoff" '
|
||
/^\[[^]]+\]$/ {
|
||
snap = $0
|
||
gsub(/^\[/, "", snap)
|
||
gsub(/\]$/, "", snap)
|
||
next
|
||
}
|
||
snap != "" && /^snaptime:[[:space:]]*[0-9]+/ {
|
||
ts = $2
|
||
if (ts < cutoff) {
|
||
print guest_type "|" vmid "|" snap "|" ts
|
||
}
|
||
snap = ""
|
||
}
|
||
' "$conf"
|
||
done
|
||
}
|
||
garbage_cleanup_prune_snapshots() {
|
||
block_non_pve9_destructive "清理旧快照" || return 1
|
||
|
||
local age_days records count idx guest_type vmid snapshot_name epoch time_text mode answer success=0 failed=0 skipped=0
|
||
read -p "列出超过多少天的快照 [90]: " age_days
|
||
age_days="${age_days:-90}"
|
||
[[ "$age_days" =~ ^[0-9]+$ ]] || age_days=90
|
||
|
||
records="$(garbage_cleanup_snapshot_candidates "$age_days")"
|
||
clear
|
||
show_menu_header "旧快照清理预览"
|
||
if [[ -z "$records" ]]; then
|
||
log_warn "未发现超过 ${age_days} 天且带 snaptime 的 VM/CT 快照。"
|
||
return 0
|
||
fi
|
||
|
||
printf "%-5s %-5s %-7s %-24s %-20s\n" "序号" "类型" "VMID" "快照名" "创建时间"
|
||
echo "$UI_DIVIDER"
|
||
idx=1
|
||
while IFS='|' read -r guest_type vmid snapshot_name epoch; do
|
||
[[ -n "$snapshot_name" ]] || continue
|
||
time_text="$(date -d "@$epoch" '+%F %T' 2>/dev/null || echo "$epoch")"
|
||
printf "%-5s %-5s %-7s %-24s %-20s\n" "$idx" "$guest_type" "$vmid" "$snapshot_name" "$time_text"
|
||
idx=$((idx + 1))
|
||
done <<< "$records"
|
||
count="$(echo "$records" | garbage_cleanup_count_records)"
|
||
echo "$UI_DIVIDER"
|
||
echo -e "${YELLOW}候选快照数量:${NC} $count"
|
||
|
||
if ! confirm_high_risk_action "删除上方列出的旧快照" "删除快照后将失去对应时间点的快速回滚能力。" "如果快照是业务回退基线,误删后只能依赖外部备份恢复。" "请确认这些快照已经过期,且已有必要的外部备份。" "DELETE-SNAP"; then
|
||
return 0
|
||
fi
|
||
|
||
read -p "删除模式:输入 all 批量删除,输入 item 逐项确认 [item]: " mode
|
||
mode="${mode:-item}"
|
||
[[ "$mode" == "all" || "$mode" == "item" ]] || mode="item"
|
||
|
||
while IFS='|' read -r guest_type vmid snapshot_name epoch; do
|
||
[[ -n "$snapshot_name" ]] || continue
|
||
if [[ "$mode" == "item" ]]; then
|
||
read -p "删除 ${guest_type} ${vmid} 快照 ${snapshot_name} ? 输入 yes 确认: " answer
|
||
if [[ "$answer" != "yes" && "$answer" != "YES" ]]; then
|
||
skipped=$((skipped + 1))
|
||
continue
|
||
fi
|
||
fi
|
||
|
||
if [[ "$guest_type" == "VM" ]]; then
|
||
if qm delsnapshot "$vmid" "$snapshot_name" >/dev/null 2>&1; then
|
||
log_success "已删除 VM $vmid 快照: $snapshot_name"
|
||
success=$((success + 1))
|
||
else
|
||
log_error "删除 VM $vmid 快照失败: $snapshot_name"
|
||
failed=$((failed + 1))
|
||
fi
|
||
else
|
||
if pct delsnapshot "$vmid" "$snapshot_name" >/dev/null 2>&1; then
|
||
log_success "已删除 CT $vmid 快照: $snapshot_name"
|
||
success=$((success + 1))
|
||
else
|
||
log_error "删除 CT $vmid 快照失败: $snapshot_name"
|
||
failed=$((failed + 1))
|
||
fi
|
||
fi
|
||
done <<< "$records"
|
||
|
||
log_info "快照清理完成:成功 $success,失败 $failed,跳过 $skipped"
|
||
}
|
||
garbage_cleanup_collect_referenced_volumes() {
|
||
{
|
||
grep -hoE '[A-Za-z0-9_.-]+:(vm|base|subvol)-[0-9]+-[^,[:space:]]+' /etc/pve/qemu-server/*.conf 2>/dev/null || true
|
||
grep -hoE '[A-Za-z0-9_.-]+:(vm|base|subvol)-[0-9]+-[^,[:space:]]+' /etc/pve/lxc/*.conf 2>/dev/null || true
|
||
} | sort -u
|
||
}
|
||
garbage_cleanup_orphan_disk_report() {
|
||
vm_require_commands pvesm || return 1
|
||
|
||
local refs store type status total used avail percent content line volid size vmid owner_id records=""
|
||
refs="$(garbage_cleanup_collect_referenced_volumes)"
|
||
|
||
while IFS='|' read -r store type status total used avail percent; do
|
||
[[ -n "$store" ]] || continue
|
||
for content in images rootdir; do
|
||
vm_storage_supports_content "$store" "$content" || continue
|
||
while read -r line; do
|
||
[[ -n "$line" ]] || continue
|
||
volid="$(echo "$line" | awk '{print $1}')"
|
||
size="$(echo "$line" | awk '{print $4}')"
|
||
vmid="$(echo "$line" | awk '{print $5}')"
|
||
[[ "$volid" =~ :((vm|base|subvol)-[0-9]+-) ]] || continue
|
||
if echo "$refs" | grep -Fxq "$volid"; then
|
||
continue
|
||
fi
|
||
owner_id="$(echo "$volid" | sed -nE 's/.*:(vm|base|subvol)-([0-9]+)-.*/\2/p')"
|
||
if [[ -n "$owner_id" ]] && pve_guest_exists any "$owner_id"; then
|
||
continue
|
||
fi
|
||
records+="${volid}|${size:-0}|${vmid:-${owner_id:-?}}|${content}|${store}"$'\n'
|
||
done < <(pvesm list "$store" --content "$content" 2>/dev/null | awk 'NR>1')
|
||
done
|
||
done < <(pve_storage_status_records)
|
||
|
||
clear
|
||
show_menu_header "疑似孤立磁盘扫描(只读)"
|
||
echo -e "${YELLOW}说明:${NC} 该功能只扫描,不删除。底层卷误删风险很高,请先在 Web UI / VM 配置中二次核对。"
|
||
echo "$UI_DIVIDER"
|
||
if [[ -z "$records" ]]; then
|
||
log_success "未发现明显的孤立磁盘候选。"
|
||
return 0
|
||
fi
|
||
|
||
printf "%-5s %-18s %-8s %-10s %-8s %s\n" "序号" "存储" "VMID" "内容" "大小" "卷"
|
||
echo "$UI_DIVIDER"
|
||
local idx=1
|
||
while IFS='|' read -r volid size vmid content store; do
|
||
[[ -n "$volid" ]] || continue
|
||
printf "%-5s %-18s %-8s %-10s %-8s %s\n" "$idx" "$store" "$vmid" "$content" "$(pve_tools_human_bytes "$size")" "$volid"
|
||
echo " 人工核对后可用命令: pvesm free \"$volid\""
|
||
idx=$((idx + 1))
|
||
done <<< "$records"
|
||
echo "$UI_DIVIDER"
|
||
echo -e "${RED}未在脚本中提供自动删除孤立磁盘入口。${NC}"
|
||
}
|
||
garbage_cleanup_scan_report() {
|
||
local temp_records old_records backup_records snap_records
|
||
local temp_count old_count backup_count snap_count apt_cache_size pve_backup_size pve_export_size
|
||
|
||
temp_records="$(garbage_cleanup_temp_file_candidates 3)"
|
||
old_records="$(garbage_cleanup_pve_tools_old_file_candidates 90)"
|
||
backup_records="$(garbage_cleanup_backup_candidates both 180)"
|
||
snap_records="$(garbage_cleanup_snapshot_candidates 90)"
|
||
temp_count="$(echo "$temp_records" | garbage_cleanup_count_records)"
|
||
old_count="$(echo "$old_records" | garbage_cleanup_count_records)"
|
||
backup_count="$(echo "$backup_records" | garbage_cleanup_count_records)"
|
||
snap_count="$(echo "$snap_records" | garbage_cleanup_count_records)"
|
||
apt_cache_size="$(du -sh /var/cache/apt/archives 2>/dev/null | awk '{print $1}')"
|
||
pve_backup_size="$(du -sh /var/backups/pve-tools 2>/dev/null | awk '{print $1}')"
|
||
pve_export_size="$(du -sh "$VM_CONFIG_EXPORT_DIR" 2>/dev/null | awk '{print $1}')"
|
||
|
||
clear
|
||
show_menu_header "垃圾清理扫描报告"
|
||
echo -e "${CYAN}缓存与日志:${NC}"
|
||
echo " apt 缓存目录: ${apt_cache_size:-未发现}"
|
||
if command -v journalctl >/dev/null 2>&1; then
|
||
echo -n " systemd journal: "
|
||
journalctl --disk-usage 2>/dev/null || echo "无法读取"
|
||
fi
|
||
echo "$UI_DIVIDER"
|
||
echo -e "${CYAN}本工具旧文件:${NC}"
|
||
echo " /tmp 中超过 3 天的 PVE-Tools 临时文件: $temp_count"
|
||
echo " /var/backups/pve-tools 总大小: ${pve_backup_size:-未发现}"
|
||
echo " $VM_CONFIG_EXPORT_DIR 总大小: ${pve_export_size:-未发现}"
|
||
echo " 超过 90 天的 PVE-Tools 备份/导出文件: $old_count"
|
||
echo "$UI_DIVIDER"
|
||
echo -e "${CYAN}PVE 资源候选:${NC}"
|
||
echo " 无主或超过 180 天的 vzdump 备份: $backup_count"
|
||
echo " 超过 90 天且带 snaptime 的 VM/CT 快照: $snap_count"
|
||
echo " 疑似孤立磁盘请使用菜单中的只读扫描单独查看。"
|
||
}
|
||
garbage_cleanup_menu() {
|
||
run_menu "垃圾清理" garbage_cleanup_menu_render garbage_cleanup_menu_dispatch "0-5"
|
||
}
|
||
|
||
garbage_cleanup_menu_render() {
|
||
show_menu_option "1" "一键扫描报告 ${CYAN}(只读)${NC}"
|
||
show_menu_option "2" "清理缓存、日志与本工具旧文件"
|
||
show_menu_option "3" "清理过期/无主 vzdump 备份"
|
||
show_menu_option "4" "清理旧 VM/CT 快照"
|
||
show_menu_option "5" "扫描疑似孤立磁盘 ${YELLOW}(只读,不删除)${NC}"
|
||
echo "$UI_DIVIDER"
|
||
echo -e "${YELLOW}说明:${NC} 清理前会列出候选项;备份和快照删除均需要高风险确认。"
|
||
}
|
||
|
||
garbage_cleanup_menu_dispatch() {
|
||
case "$1" in
|
||
1) garbage_cleanup_scan_report ;;
|
||
2) garbage_cleanup_basic ;;
|
||
3) garbage_cleanup_prune_backups ;;
|
||
4) garbage_cleanup_prune_snapshots ;;
|
||
5) garbage_cleanup_orphan_disk_report ;;
|
||
*) return 1 ;;
|
||
esac
|
||
return 0
|
||
}
|