diff --git a/reinstall-fbl.sh b/reinstall-fbl.sh index 8669130..72575e3 100644 --- a/reinstall-fbl.sh +++ b/reinstall-fbl.sh @@ -1,20 +1,12 @@ #!/usr/bin/env bash # reinstall-freebsd-linux.sh -# Reinstall system on Linux / FreeBSD (推荐在 ISO/Rescue 环境运行) using DD + cloud-init (NoCloud) with: +# Reinstall system on Linux / FreeBSD using DD + cloud-init (NoCloud) with: # - freebsd # - rocky # - almalinux # - fedora # - redhat # -# 推荐使用方式(RHEL + 面板 ISO 流程示意): -# 1. 在 RHEL 里根据需求决定目标系统和参数(disk / password / ssh-key / frpc 等)。 -# 2. 到面板 / 管理界面把启动介质切换为某个 ISO(Alpine/Rescue/LiveCD 等)。 -# 3. 在 RHEL 里执行 `reboot`(这一步是手动,不由脚本控制)。 -# 4. 机器从 ISO/Rescue 启动后,下载本脚本并用同样参数运行,例如: -# bash reinstall-freebsd-linux.sh freebsd 14 --disk /dev/sda --ssh-key ... -# 接下来脚本会全自动:下载镜像 → 解压 → qemu-img 转换 → cloud-init → DD → 自动重启。 -# # All target systems use cloud-init to inject: # - root password (--password) # - SSH public key(s) (--ssh-key, multiple) @@ -24,7 +16,7 @@ # Requirements: # - Run with bash: bash reinstall-freebsd-linux.sh ... # - Needs dd, xz, qemu-img, mount, and curl or wget or fetch -# - On Linux (RHEL/Rocky/Alma/Fedora) this script will try to install missing deps automatically. +# - This script is typically run in ISO/Rescue environment, not in the original RHEL. set -eE @@ -46,11 +38,11 @@ info() { usage() { cat </dev/null 2>&1; then - pm="dnf" - elif command -v yum >/dev/null 2>&1; then - pm="yum" - fi - - if [ -z "$pm" ]; then - warn "Could not find dnf/yum on a Red Hat like system; please install dependencies manually." - return - fi - - local need_pkgs=() - - if ! command -v qemu-img >/dev/null 2>&1; then - need_pkgs+=("qemu-img") - fi - if ! command -v xz >/dev/null 2>&1; then - need_pkgs+=("xz") - fi - if ! command -v curl >/dev/null 2>&1 && ! command -v wget >/dev/null 2>&1 && ! command -v fetch >/dev/null 2>&1; then - need_pkgs+=("curl") - fi - - if [ "${#need_pkgs[@]}" -gt 0 ]; then - info "Installing missing dependencies with $pm: ${need_pkgs[*]}" - "$pm" -y install "${need_pkgs[@]}" - fi -} +# -------- dependencies (不再区分 RHEL / 非 RHEL,统一只检查,不自动安装) -------- ensure_dependencies_linux_generic() { - # For non-Red-Hat Linux, just check and error with a clear message. local missing=() if ! command -v qemu-img >/dev/null 2>&1; then @@ -209,8 +172,6 @@ Please install them manually with your package manager (e.g. apt, zypper, pacman } ensure_dependencies_freebsd() { - # Placeholder for future automation. - # For now just check and give a hint. local missing=() if ! command -v qemu-img >/dev/null 2>&1; then @@ -232,24 +193,20 @@ Hint: you can install them with: ensure_dependencies() { if [ "$OS" = "Linux" ]; then - if [ -f /etc/redhat-release ]; then - ensure_dependencies_linux_rhel_like - else - ensure_dependencies_linux_generic - fi + ensure_dependencies_linux_generic elif [ "$OS" = "FreeBSD" ]; then ensure_dependencies_freebsd fi } +# -------------------------------------------------------------------- + auto_detect_disk() { - # Only called when DISK is empty. info "Auto-detecting target disk..." if [[ "$OS" == "Linux" ]]; then if command -v lsblk >/dev/null 2>&1; then local best_name="" best_size=0 - # NAME TYPE RM SIZE(bytes) while read -r name type rm size; do [ "$type" = "disk" ] || continue [ "$rm" = "0" ] || continue @@ -267,7 +224,6 @@ auto_detect_disk() { fi error "Unable to auto-detect target disk on Linux. Please specify --disk explicitly." else - # FreeBSD if command -v sysctl >/dev/null 2>&1; then local disks disks=$(sysctl -n kern.disks 2>/dev/null || true) @@ -284,7 +240,6 @@ auto_detect_disk() { fi } -# Show disk partition info at the end (best-effort). show_partition_info() { echo echo "---------------- Disk partition layout ----------------" @@ -297,7 +252,6 @@ show_partition_info() { echo "Could not show partition info (no lsblk/fdisk)." fi else - # FreeBSD if command -v gpart >/dev/null 2>&1; then local d="${DISK#/dev/}" gpart show "$d" 2>/dev/null || gpart show "$DISK" 2>/dev/null || echo "Could not show partition info with gpart." @@ -308,6 +262,20 @@ show_partition_info() { echo "-------------------------------------------------------" } +# 保留 RHEL hook(可选),只是执行另外一个脚本,与依赖无关 +run_rhel_freebsd_hook() { + if [ "$OS" = "Linux" ] && [ -f /etc/redhat-release ]; then + if [ -f "./reinstall-fbll.sh" ]; then + info "RHEL detected, running: bash reinstall-fbll.sh freebsd 14" + if ! bash ./reinstall-fbll.sh freebsd 14; then + warn "reinstall-fbll.sh freebsd 14 failed, continuing anyway." + fi + else + warn "RHEL detected, but ./reinstall-fbll.sh not found; skipping RHEL hook." + fi + fi +} + # Parse ssh-key: supports inline / URL / github / gitlab / file parse_ssh_key() { local val="$1" @@ -353,11 +321,9 @@ Available options: [ -n "$ssh_key" ] || ssh_key_error_and_exit "No valid SSH key found in $key_url" ;; *) - # Reject Windows-style paths explicitly if [[ "$val" =~ ^[A-Za-z]:\\ ]]; then ssh_key_error_and_exit "Windows path is not supported, please copy the key file to local filesystem and use /path/to/public_key" fi - # Inline key or local file if is_valid_ssh_key "$val"; then ssh_key="$val" else @@ -373,7 +339,6 @@ Available options: echo "$ssh_key" } -# Choose default image URL based on target OS, version, and host arch get_default_image_url() { local os="$1" ver="$2" @@ -456,7 +421,6 @@ get_default_image_url() { esac ;; redhat) - # Red Hat image must be supplied by user via --img echo "" ;; *) @@ -466,14 +430,12 @@ get_default_image_url() { } find_efi_partition() { - # Guess EFI partition name from disk path (p1 vs 1) local disk="$1" part case "$disk" in */nvme*|*/*nvd*) part="${disk}p1" ;; *) - # /dev/sda /dev/vda /dev/ada0 etc. if [[ "$(uname -s)" == "FreeBSD" ]]; then part="${disk}p1" else @@ -489,13 +451,11 @@ write_nocloud_seed() { mkdir -p "$(dirname "$meta_path")" - # meta-data cat >"$meta_path" </dev/null 2>&1; then + # 修正:A-Za-z0n9 -> A-Za-z0-9 PASSWORD=$(LC_ALL=C tr -dc 'A-Za-z0-9' /dev/null 2>&1 && file "$IMG_QCOW" | grep -qi 'xz compressed'; then - info "Detected xz compressed image, decompressing with dd (progress should be visible even on tty)..." +if file "$IMG_QCOW" | grep -qi 'xz compressed'; then + info "Detected xz compressed image, decompressing (progress may be shown)..." mv "$IMG_QCOW" "$IMG_QCOW.xz" - # 用 dd status=progress 来显示解压进度 - xz -dc "$IMG_QCOW.xz" | dd of="$IMG_QCOW" bs=4M status=progress + if command -v pv >/dev/null 2>&1; then + xz -dc "$IMG_QCOW.xz" | pv >"$IMG_QCOW" + else + xz -dc "$IMG_QCOW.xz" >"$IMG_QCOW" + fi fi -# Convert to raw using qemu-img (with progress) info "Converting qcow2 to raw with qemu-img (with progress)..." qemu-img convert -p -O raw "$IMG_QCOW" "$IMG_RAW" -# Final confirmation echo echo "WARNING: dd will be run on $DISK. ALL DATA ON THIS DISK WILL BE LOST!" -read -r -p "Type 'yes' or 'y' to continue: " ans -case "$ans" in - yes|y|Y) ;; - *) error "Operation cancelled by user." ;; -esac - -# RHEL: unregister subscription BEFORE dd(非必须,ISO 里一般不会触发) -if [ "$OS" = "Linux" ] && [ -f /etc/redhat-release ] && command -v subscription-manager >/dev/null 2>&1; then - info "RHEL detected, trying to unregister existing subscription before overwriting disk..." - if ! subscription-manager unregister; then - warn "subscription-manager unregister failed, continuing anyway." - fi +read -r -p "Type 'yes' to continue: " ans +if [ "$ans" != "yes" ]; then + error "Operation cancelled by user." fi -# Try to inject NoCloud into image EFI (best effort) -inject_nocloud_into_image() { - if [ "$OS" != "Linux" ]; then - return 0 - fi - if ! command -v losetup >/dev/null 2>&1; then - warn "losetup not found, skipping NoCloud injection into image." - return 0 - fi +# 注意:subscription-manager unregister 已经从这里移除, +# 请在 RHEL 阶段的脚本里(还在 RHEL 根系统时)单独调用。 - info "Mounting raw image and injecting NoCloud seed into its EFI (before dd)..." - local loopdev= - loopdev=$(losetup --find --show "$IMG_RAW") || { - warn "Failed to create loop device for image, skipping NoCloud injection." - return 0 - } - - # Try reread partition - partprobe "$loopdev" 2>/dev/null || true - - local efi_part="${loopdev}p1" - local mnt_img="$TMPDIR/img-efi" - mkdir -p "$mnt_img" - - if ! mount "$efi_part" "$mnt_img" 2>/dev/null; then - if ! mount -t vfat "$efi_part" "$mnt_img" 2>/dev/null && ! mount -t msdos "$efi_part" "$mnt_img" 2>/dev/null; then - warn "Failed to mount image EFI partition $efi_part; skipping NoCloud injection." - losetup -d "$loopdev" || true - return 0 - fi - fi - - local NOCLOUD_DIR_IMG="$mnt_img/nocloud" - mkdir -p "$NOCLOUD_DIR_IMG" - - # FRPC - if [ -n "$FRPC_TOML" ]; then - FRPC_PRESENT=1 - if [[ "$FRPC_TOML" =~ ^https?:// ]]; then - info "Downloading FRPC config for image: $FRPC_TOML" - if ! http_download "$FRPC_TOML" "$NOCLOUD_DIR_IMG/frpc.toml"; then - warn "Failed to download FRPC config for image, ignoring" - FRPC_PRESENT="" - fi - elif [ -f "$FRPC_TOML" ]; then - info "Copying FRPC config for image from: $FRPC_TOML" - cp "$FRPC_TOML" "$NOCLOUD_DIR_IMG/frpc.toml" - else - warn "Invalid FRPC config path: $FRPC_TOML, ignoring" - FRPC_PRESENT="" - fi - fi - - info "Writing NoCloud seed into image EFI:/nocloud/ ..." - write_nocloud_seed "$TARGET_OS" "$NOCLOUD_DIR_IMG/meta-data" "$NOCLOUD_DIR_IMG/user-data" - - sync || true - umount "$mnt_img" || true - losetup -d "$loopdev" || true -} - -inject_nocloud_into_image - -# dd to disk info "Writing image to disk with dd, this may take a while..." dd if="$IMG_RAW" of="$DISK" bs=4M conv=fsync status=progress -sync || true +sync info "dd finished." -# Try to refresh partition table (best-effort) if command -v partprobe >/dev/null 2>&1; then partprobe "$DISK" || true elif command -v blockdev >/dev/null 2>&1; then @@ -866,10 +744,8 @@ fi sleep 2 -# Find and mount EFI partition on target disk for NoCloud (best effort) -EFI_PART -= $(find_efi_partition "$DISK") -info "Trying EFI partition on target disk: $EFI_PART" +EFI_PART=$(find_efi_partition "$DISK") +info "Trying EFI partition: $EFI_PART" MNT_EFI="$TMPDIR/efi" mkdir -p "$MNT_EFI" @@ -881,7 +757,6 @@ if [[ "$OS" == "FreeBSD" ]]; then fi else if ! mount "$EFI_PART" "$MNT_EFI" 2>/dev/null; then - # Some systems require explicit vfat if ! mount -t vfat "$EFI_PART" "$MNT_EFI" 2>/dev/null && ! mount -t msdos "$EFI_PART" "$MNT_EFI" 2>/dev/null; then warn "Failed to mount EFI partition $EFI_PART, skipping cloud-init NoCloud injection." EFI_PART="" @@ -893,7 +768,6 @@ if [ -n "$EFI_PART" ]; then NOCLOUD_DIR="$MNT_EFI/nocloud" mkdir -p "$NOCLOUD_DIR" - # Handle FRPC config if present if [ -n "$FRPC_TOML" ]; then FRPC_PRESENT=1 if [[ "$FRPC_TOML" =~ ^https?:// ]]; then @@ -914,7 +788,7 @@ if [ -n "$EFI_PART" ]; then info "Writing NoCloud seed to EFI:/nocloud/ ..." write_nocloud_seed "$TARGET_OS" "$NOCLOUD_DIR/meta-data" "$NOCLOUD_DIR/user-data" - sync || true + sync umount "$MNT_EFI" || true else warn "EFI could not be mounted; target system can still boot, but cloud-init configuration may not be applied." @@ -922,10 +796,10 @@ fi info "Image write and cloud-init NoCloud injection completed." -# Show partition info +run_rhel_freebsd_hook + show_partition_info -# Final summary: username, password/key, SSH port FINAL_SSH_PORT="${SSH_PORT:-22}" echo @@ -962,25 +836,12 @@ if [ "$HOLD" = "2" ]; then exit 0 fi -# 注意:这里按你的要求,DD 完成后不再交互,直接尝试重启。 -info "Rebooting into new system..." - -# 尝试正常 reboot,如果已经被 dd 搞坏就用 sysrq 强制重启 -if command -v systemctl >/dev/null 2>&1; then - systemctl reboot || true +echo +echo "You can now reboot into the new system, for example:" +if [ "$OS" = "FreeBSD" ]; then + echo " shutdown -r now" +else + echo " reboot" fi -if command -v reboot >/dev/null 2>&1; then - reboot || true -fi - -# 最后兜底:sysrq 强制重启(内核级别,不依赖 systemd/用户空间) -if [ -w /proc/sysrq-trigger ]; then - echo 1 > /proc/sys/kernel/sysrq 2>/dev/null || true - echo b > /proc/sysrq-trigger -fi - -# 如果到这里还没重启,只能让用户手动在面板上重启 -warn "Automatic reboot failed. Please use your provider's control panel to power cycle the VM." - exit 0