Files
2026-05-19 14:28:44 +08:00

250 lines
6.4 KiB
Bash
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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 "$@"