From e44f6acfcaf978f6e689eae3fbd1078a768e0ead Mon Sep 17 00:00:00 2001 From: JieXu Date: Sun, 16 Nov 2025 01:03:39 +0800 Subject: [PATCH] Update reinstall-fbl.sh --- reinstall-fbl.sh | 399 ++++++++++++++++++++++++++++------------------- 1 file changed, 237 insertions(+), 162 deletions(-) diff --git a/reinstall-fbl.sh b/reinstall-fbl.sh index 76831e7..32af04a 100644 --- a/reinstall-fbl.sh +++ b/reinstall-fbl.sh @@ -92,7 +92,7 @@ Password / SSH key behaviour: - If you specify neither password nor ssh-key: * The script will prompt for a root password. * If you leave it empty, a random 20-character password (A–Z, a–z, 0–9) will be generated. - * The generated password will be printed in the final summary. + * The generated password will be printed before reboot. - Username is always: root Examples: @@ -153,6 +153,7 @@ detect_os_arch() { } ensure_dependencies_linux_rhel_like() { + # Only called on Linux with /etc/redhat-release present. if [ "$(id -u)" -ne 0 ]; then warn "Running as non-root; cannot auto-install dependencies. Please run as root if you want auto-install." return @@ -189,6 +190,7 @@ ensure_dependencies_linux_rhel_like() { } 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 @@ -208,6 +210,8 @@ 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 @@ -240,11 +244,13 @@ ensure_dependencies() { } 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 @@ -262,6 +268,7 @@ 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) @@ -278,6 +285,7 @@ auto_detect_disk() { fi } +# Show disk partition info at the end (best-effort). show_partition_info() { echo echo "---------------- Disk partition layout ----------------" @@ -290,6 +298,7 @@ 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." @@ -300,6 +309,21 @@ show_partition_info() { echo "-------------------------------------------------------" } +# RHEL-specific hook: kept for compatibility; you可以按需删除 +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" local val_lower key_url tmpfile ssh_key @@ -344,9 +368,11 @@ 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 @@ -362,6 +388,7 @@ 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" @@ -399,7 +426,7 @@ get_default_image_url() { esac ;; *) - error "Unsupported Rocky version: $ver" + error "Unsupported Rocky version: $ver (future: add rocky 9, etc.)" ;; esac ;; @@ -444,6 +471,7 @@ get_default_image_url() { esac ;; redhat) + # Red Hat image must be supplied by user via --img echo "" ;; *) @@ -452,16 +480,37 @@ get_default_image_url() { esac } +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 + part="${disk}1" + fi + ;; + esac + echo "$part" +} + write_nocloud_seed() { local os="$1" meta_path="$2" user_path="$3" mkdir -p "$(dirname "$meta_path")" + # meta-data cat >"$meta_path" <"$user_path" } -inject_nocloud_into_image() { - info "Mounting raw image and injecting NoCloud seed into its EFI (before dd)..." - - if [ ! -f "$IMG_RAW" ]; then - warn "RAW image $IMG_RAW not found; cannot inject NoCloud into image." - return - fi - - if [ "$OS" = "Linux" ]; then - if ! command -v losetup >/dev/null 2>&1; then - warn "losetup not found; skipping NoCloud injection into image." - return - fi - - local loopdev efi_part mnt nocloud_dir - loopdev=$(losetup --find --show "$IMG_RAW" 2>/dev/null || true) - if [ -z "$loopdev" ]; then - warn "Failed to create loop device for $IMG_RAW; skipping NoCloud injection into image." - return - fi - - if command -v partprobe >/dev/null 2>&1; then - partprobe "$loopdev" || true - fi - - # 简单假设 EFI 是 p1;如果以后碰到不是 p1 的,再补充 parted/fdisk 检测逻辑 - efi_part="${loopdev}p1" - mnt="$TMPDIR/efi-image" - mkdir -p "$mnt" - - if ! mount "$efi_part" "$mnt" 2>/dev/null; then - if ! mount -t vfat "$efi_part" "$mnt" 2>/dev/null && ! mount -t msdos "$efi_part" "$mnt" 2>/dev/null; then - warn "Failed to mount image EFI partition $efi_part; skipping NoCloud injection." - umount "$mnt" 2>/dev/null || true - losetup -d "$loopdev" || true - return - fi - fi - - nocloud_dir="$mnt/nocloud" - mkdir -p "$nocloud_dir" - - if [ -n "$FRPC_TOML" ]; then - FRPC_PRESENT=1 - if [[ "$FRPC_TOML" =~ ^https?:// ]]; then - info "Downloading FRPC config: $FRPC_TOML" - if ! http_download "$FRPC_TOML" "$nocloud_dir/frpc.toml"; then - warn "Failed to download FRPC config, ignoring" - FRPC_PRESENT="" - fi - elif [ -f "$FRPC_TOML" ]; then - info "Copying FRPC config from: $FRPC_TOML" - cp "$FRPC_TOML" "$nocloud_dir/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/meta-data" "$nocloud_dir/user-data" - - sync || true - umount "$mnt" || true - losetup -d "$loopdev" || true - info "NoCloud seed injection into image completed." - - else - if ! command -v mdconfig >/dev/null 2>&1; then - warn "mdconfig not found; skipping NoCloud injection into image." - return - fi - - local mddev efi_part mnt nocloud_dir - mddev=$(mdconfig -a -t vnode -f "$IMG_RAW" 2>/dev/null || true) - if [ -z "$mddev" ]; then - warn "Failed to create md device for $IMG_RAW; skipping NoCloud injection into image." - return - fi - - efi_part="/dev/${mddev}p1" - mnt="$TMPDIR/efi-image" - mkdir -p "$mnt" - - if ! mount -t msdosfs "$efi_part" "$mnt" 2>/dev/null; then - warn "Failed to mount image EFI partition $efi_part; skipping NoCloud injection." - mdconfig -d -u "$mddev" || true - return - fi - - nocloud_dir="$mnt/nocloud" - mkdir -p "$nocloud_dir" - - if [ -n "$FRPC_TOML" ]; then - FRPC_PRESENT=1 - if [[ "$FRPC_TOML" =~ ^https?:// ]]; then - info "Downloading FRPC config: $FRPC_TOML" - if ! http_download "$FRPC_TOML" "$nocloud_dir/frpc.toml"; then - warn "Failed to download FRPC config, ignoring" - FRPC_PRESENT="" - fi - elif [ -f "$FRPC_TOML" ]; then - info "Copying FRPC config from: $FRPC_TOML" - cp "$FRPC_TOML" "$nocloud_dir/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/meta-data" "$nocloud_dir/user-data" - - sync || true - umount "$mnt" || true - mdconfig -d -u "$mddev" || true - info "NoCloud seed injection into image completed." - fi -} - # ----------------- main ----------------- [ $# -lt 1 ] && usage @@ -668,6 +599,7 @@ FRPC_PRESENT="" HOLD="0" AUTO_PASSWORD=0 +# If the next positional arg is numeric, treat it as version for specific OSes. if [ $# -gt 0 ] && [[ "$1" =~ ^[0-9]+$ ]]; then case "$TARGET_OS" in freebsd|rocky|almalinux|fedora) @@ -748,8 +680,9 @@ while [ $# -gt 0 ]; do done detect_os_arch -ensure_dependencies +ensure_dependencies # auto install / check qemu-img, xz, curl/wget/fetch +# Disk handling: auto-detect if not provided if [ -n "$DISK" ]; then if [[ "$DISK" != /dev/* ]]; then DISK="/dev/$DISK" @@ -762,14 +695,17 @@ if [ ! -b "$DISK" ] && [ ! -c "$DISK" ]; then error "Target disk $DISK does not exist or is not a block/char device" fi +# Password / SSH key behaviour: +# If neither password nor ssh-key specified, ask for password; if still empty, generate random. if [ -z "$PASSWORD" ] && [ -z "$SSH_KEYS_ALL" ]; then echo "No --password or --ssh-key specified." echo "You can set a root password now, or leave empty to auto-generate a random 20-character password." while :; do - read -r -p "Enter root password (leave empty to auto-generate): " pw1 + read -r -s -p "Enter root password (leave empty to auto-generate): " pw1 echo + # Empty: auto-generate random password, no need to confirm if [ -z "$pw1" ]; then if command -v tr >/dev/null 2>&1; then PASSWORD=$(LC_ALL=C tr -dc 'A-Za-z0-9' "$IMG_QCOW" + # 用 dd status=progress 来显示解压进度 + xz -dc "$IMG_QCOW.xz" | dd of="$IMG_QCOW" bs=4M status=progress 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 -ans_lower=$(printf '%s' "$ans" | to_lower) -if [ "$ans_lower" != "yes" ] && [ "$ans_lower" != "y" ]; then - error "Operation cancelled by user." -fi +case "$ans" in + yes|y|Y) ;; + *) error "Operation cancelled by user." ;; +esac +# RHEL: unregister subscription BEFORE dd 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 @@ -856,15 +801,146 @@ if [ "$OS" = "Linux" ] && [ -f /etc/redhat-release ] && command -v subscription- fi 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 + + 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 + umount "$mnt_img" || true + losetup -d "$loopdev" || true +} + inject_nocloud_into_image -# dd 之后不再让 EXIT trap 去跑 rm(rm 可能变成 FreeBSD 的 ELF,Linux 起不来) -trap - EXIT - +# 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 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 + blockdev --rereadpt "$DISK" || true +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" + +MNT_EFI="$TMPDIR/efi" +mkdir -p "$MNT_EFI" + +if [[ "$OS" == "FreeBSD" ]]; then + if ! mount -t msdosfs "$EFI_PART" "$MNT_EFI" 2>/dev/null; then + warn "Failed to mount EFI partition $EFI_PART, skipping cloud-init NoCloud injection." + EFI_PART="" + 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="" + fi + fi +fi + +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 + info "Downloading FRPC config: $FRPC_TOML" + if ! http_download "$FRPC_TOML" "$NOCLOUD_DIR/frpc.toml"; then + warn "Failed to download FRPC config, ignoring" + FRPC_PRESENT="" + fi + elif [ -f "$FRPC_TOML" ]; then + info "Copying FRPC config from: $FRPC_TOML" + cp "$FRPC_TOML" "$NOCLOUD_DIR/frpc.toml" + else + warn "Invalid FRPC config path: $FRPC_TOML, ignoring" + FRPC_PRESENT="" + fi + fi + + info "Writing NoCloud seed to EFI:/nocloud/ ..." + write_nocloud_seed "$TARGET_OS" "$NOCLOUD_DIR/meta-data" "$NOCLOUD_DIR/user-data" + + 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." +fi + +info "Image write and cloud-init NoCloud injection completed." + +# RHEL hook: run reinstall-fbll.sh freebsd 14 if present +run_rhel_freebsd_hook + +# Show partition info +show_partition_info + +# Final summary: username, password/key, SSH port FINAL_SSH_PORT="${SSH_PORT:-22}" echo @@ -903,36 +979,35 @@ fi echo echo "Reboot into new system now? [Y/n] (auto reboot in 10 seconds if empty)..." +printf "> " +read -r -t 10 REBOOT_ANS || REBOOT_ANS="" -reboot_ans="" -if ! read -t 10 -r -p "> " reboot_ans; then - reboot_ans="y" - echo -fi - -case "$reboot_ans" in - ""|y|Y|yes|YES|Yes) - echo "Rebooting..." - if [ "$OS" = "Linux" ]; then - # 尽量不依赖新 rootfs 里的 reboot/systemctl,优先用 Magic SysRq - if [ -w /proc/sysrq-trigger ]; then - echo 1 > /proc/sys/kernel/sysrq 2>/dev/null || true - echo b > /proc/sysrq-trigger - # 如果这里都没重启,才尝试外部命令(可能 Exec format error) - fi - if command -v systemctl >/dev/null 2>&1; then - systemctl reboot || reboot || echo "Please reboot manually from your provider panel." - else - reboot || echo "Please reboot manually from your provider panel." - fi - else - # FreeBSD 一般是同 OS 同架构,直接 shutdown -r - shutdown -r now || /sbin/shutdown -r now || echo "Please reboot manually from console or provider panel." - fi +case "$REBOOT_ANS" in + n|N|no) + info "User chose not to reboot automatically. Please reboot manually when ready." + exit 0 ;; *) - echo "Not rebooting automatically. You can reboot manually later." + info "Rebooting into new system..." ;; esac +# 尝试正常 reboot,如果已经被 dd 搞坏就用 sysrq 强制重启 +if command -v systemctl >/dev/null 2>&1; then + systemctl reboot || true +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