#!/usr/bin/env bash set -euo pipefail DISK="/dev/sdb" PART="${DISK}1" NEW_VG="docker_vg" NEW_LV="docker_lv" MOUNT_POINT="/var/lib/docker" FS_TYPE="ext4" # 可改 ext4 LV_SIZE="100%FREE" log() { echo -e "\n[INFO] $*" } warn() { echo -e "\n[WARN] $*" >&2 } die() { echo -e "\n[ERROR] $*" >&2 exit 1 } require_cmd() { command -v "$1" >/dev/null 2>&1 || die "缺少命令: $1" } cleanup_mounts_on_disk() { log "检查 ${DISK} 相关挂载点" mapfile -t mps < <(lsblk -nrpo NAME,MOUNTPOINT "${DISK}" | awk '$2 != "" {print $2}' | sort -u) if [[ ${#mps[@]} -gt 0 ]]; then warn "发现 ${DISK} 上存在挂载点: ${mps[*]}" for mp in "${mps[@]}"; do if mountpoint -q "$mp"; then log "卸载挂载点: $mp" umount -f "$mp" || die "无法卸载 $mp" fi done else log "未发现 ${DISK} 的活动挂载点" fi } stop_docker() { log "停止 Docker 相关服务" systemctl stop docker 2>/dev/null || true systemctl stop docker.socket 2>/dev/null || true systemctl stop containerd 2>/dev/null || true } deactivate_old_lvm() { log "检查旧 LVM 信息" # 尝试识别 /dev/sdb 或 /dev/sdb1 所属 VG local old_vg="" old_vg="$(pvs --noheadings -o vg_name "${DISK}" 2>/dev/null | awk '{$1=$1;print}' | head -n1 || true)" if [[ -z "${old_vg}" && -b "${PART}" ]]; then old_vg="$(pvs --noheadings -o vg_name "${PART}" 2>/dev/null | awk '{$1=$1;print}' | head -n1 || true)" fi if [[ -n "${old_vg}" ]]; then warn "发现旧 VG: ${old_vg}" log "列出旧 LV" lvs --noheadings -o lv_name "${old_vg}" 2>/dev/null | awk '{$1=$1;print}' || true # 尝试卸载该 VG 下所有 LV 的挂载 while read -r lv; do [[ -z "${lv}" ]] && continue local lv_path="/dev/${old_vg}/${lv}" if [[ -e "${lv_path}" ]]; then local mp mp="$(lsblk -nrpo MOUNTPOINT "${lv_path}" 2>/dev/null | awk 'NF{print; exit}' || true)" if [[ -n "${mp}" && "${mp}" != "[SWAP]" ]]; then log "卸载 LV 挂载点: ${mp}" umount -f "${mp}" || true fi fi done < <(lvs --noheadings -o lv_name "${old_vg}" 2>/dev/null | awk '{$1=$1;print}') log "停用 VG: ${old_vg}" vgchange -an "${old_vg}" || true # 再次激活以便删除 LV,部分系统需要先处于可见状态 vgchange -ay "${old_vg}" || true # 删除该 VG 下所有 LV while read -r lv; do [[ -z "${lv}" ]] && continue local lv_path="/dev/${old_vg}/${lv}" if [[ -e "${lv_path}" ]]; then log "删除旧 LV: ${lv_path}" lvremove -ff -y "${lv_path}" || true fi done < <(lvs --noheadings -o lv_name "${old_vg}" 2>/dev/null | awk '{$1=$1;print}') log "停用并删除旧 VG: ${old_vg}" vgchange -an "${old_vg}" || true vgremove -ff -y "${old_vg}" || true else log "未发现 ${DISK} / ${PART} 上关联的 VG" fi # 删除旧 PV if pvs "${PART}" >/dev/null 2>&1; then log "删除旧 PV: ${PART}" pvremove -ff -y "${PART}" || true fi if pvs "${DISK}" >/dev/null 2>&1; then log "删除旧 PV: ${DISK}" pvremove -ff -y "${DISK}" || true fi # 额外清理 device-mapper 残留 log "清理可能遗留的 device-mapper 映射" dmsetup remove_all 2>/dev/null || true udevadm settle || true } wipe_disk() { log "清理磁盘签名和分区表: ${DISK}" swapoff -a 2>/dev/null || true wipefs -a "${PART}" 2>/dev/null || true wipefs -a "${DISK}" 2>/dev/null || true sgdisk --zap-all "${DISK}" || true dd if=/dev/zero of="${DISK}" bs=1M count=20 conv=fsync status=none || true sync partprobe "${DISK}" || true blockdev --rereadpt "${DISK}" || true udevadm settle || true # 如果分区节点还在,尝试删除 if [[ -b "${PART}" ]]; then warn "${PART} 仍然存在,尝试删除分区表项" parted -s "${DISK}" rm 1 2>/dev/null || true partprobe "${DISK}" || true udevadm settle || true fi } prepare_mountpoint() { log "准备挂载目录: ${MOUNT_POINT}" if mountpoint -q "${MOUNT_POINT}"; then umount -f "${MOUNT_POINT}" || die "无法卸载 ${MOUNT_POINT}" fi mkdir -p "${MOUNT_POINT}" if [[ -n "$(ls -A "${MOUNT_POINT}" 2>/dev/null || true)" ]]; then local backup_dir="${MOUNT_POINT}.bak.$(date +%F_%H%M%S)" warn "${MOUNT_POINT} 非空,备份到 ${backup_dir}" mv "${MOUNT_POINT}" "${backup_dir}" mkdir -p "${MOUNT_POINT}" fi } create_new_lvm() { log "在整块磁盘 ${DISK} 上创建新的 LVM" pvcreate -ff -y "${DISK}" vgcreate "${NEW_VG}" "${DISK}" lvcreate -n "${NEW_LV}" -l "${LV_SIZE}" "${NEW_VG}" local lv_path="/dev/${NEW_VG}/${NEW_LV}" log "格式化文件系统: ${FS_TYPE}" if [[ "${FS_TYPE}" == "xfs" ]]; then mkfs.xfs -f "${lv_path}" elif [[ "${FS_TYPE}" == "ext4" ]]; then mkfs.ext4 -F "${lv_path}" else die "不支持的文件系统: ${FS_TYPE}" fi local uuid uuid="$(blkid -s UUID -o value "${lv_path}")" [[ -n "${uuid}" ]] || die "获取 UUID 失败" log "写入 /etc/fstab" cp /etc/fstab "/etc/fstab.bak.$(date +%F_%H%M%S)" sed -i "\|[[:space:]]${MOUNT_POINT}[[:space:]]|d" /etc/fstab echo "UUID=${uuid} ${MOUNT_POINT} ${FS_TYPE} defaults 0 0" >> /etc/fstab log "挂载 ${MOUNT_POINT}" mount -a log "挂载结果校验" df -TH "${MOUNT_POINT}" lsblk } start_docker() { log "启动 Docker" systemctl daemon-reload systemctl start containerd 2>/dev/null || true systemctl start docker systemctl enable docker 2>/dev/null || true } main() { require_cmd lsblk require_cmd pvs require_cmd vgs require_cmd lvs require_cmd pvcreate require_cmd vgcreate require_cmd lvcreate require_cmd wipefs require_cmd sgdisk require_cmd partprobe require_cmd blkid require_cmd dmsetup require_cmd parted [[ "$(id -u)" -eq 0 ]] || die "请使用 root 执行" [[ -b "${DISK}" ]] || die "磁盘不存在: ${DISK}" echo "======================================================" echo "即将彻底清空 ${DISK} 并挂载到 ${MOUNT_POINT}" echo "目标 VG: ${NEW_VG}" echo "目标 LV: ${NEW_LV}" echo "文件系统: ${FS_TYPE}" echo "警告: 此操作会销毁 ${DISK} 上所有数据" echo "======================================================" read -r -p "确认执行请输入 YES: " ans [[ "${ans}" == "YES" ]] || die "用户取消" stop_docker cleanup_mounts_on_disk deactivate_old_lvm wipe_disk prepare_mountpoint create_new_lvm start_docker log "完成: ${DISK} -> /dev/${NEW_VG}/${NEW_LV} -> ${MOUNT_POINT}" } main "$@"