250 lines
6.4 KiB
Bash
250 lines
6.4 KiB
Bash
#!/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 "$@" |