boot

0

grub

sudo rm isoboot-grub3.sh
sudo nano isoboot-grub3.sh
sudo bash isoboot-grub3.sh
#!/usr/bin/env bash
# ============================================================
#  isoboot-grub.sh  —  ISO ループブート GRUB エントリー管理
#
#  機能:
#    1. ISO ループブートエントリーの追加
#       (Ubuntu/casper系、antiX系、汎用live-boot系を自動判定)
#    2. 不要エントリーの削除 (40_custom 由来のみ)
#    3. 何もせず終了
#
#  対応環境:
#    - BIOS (legacy) / UEFI 両対応
#    - パーティションの FS は ext2/3/4, btrfs, xfs, vfat, ntfs,
#      exfat を自動判定して適切な insmod を選択
#
#  注意:
#    バックアップは /root/grub-backups/ に保存する。
#    /etc/grub.d/ 配下に古い .bak ファイルを置いたままにすると
#    update-grub がそれを拾ってエントリーが復活することがあるため
#    削除機能はそれも検出して片付ける。
# ============================================================

set -euo pipefail

CUSTOM_FILE="${CUSTOM_FILE:-/etc/grub.d/40_custom}"
BACKUP_DIR="/root/grub-backups"

RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
BOLD='\033[1m'
RESET='\033[0m'

info()    { echo -e "${CYAN}[INFO]${RESET}  $*"; }
success() { echo -e "${GREEN}[OK]${RESET}    $*"; }
warn()    { echo -e "${YELLOW}[WARN]${RESET}  $*"; }
error()   { echo -e "${RED}[ERROR]${RESET} $*" >&2; }
die()     { error "$*"; exit 1; }
hr()      { echo -e "${CYAN}$(printf '=%.0s' {1..64})${RESET}"; }
hr_thin() { echo -e "${CYAN}$(printf -- '-%.0s' {1..64})${RESET}"; }

confirm() {
    local ans
    while true; do
        echo -en "${YELLOW}[確認]${RESET} $1 [y/N]: "
        read -r ans
        case "$ans" in
            [yY]*) return 0 ;;
            [nN]*|"") return 1 ;;
            *) echo "  y または n を入力してください。" ;;
        esac
    done
}

check_root() {
    [[ $EUID -eq 0 ]] || die "sudo で実行してください。\n  例: sudo bash $0"
}

check_deps() {
    local missing=()
    for cmd in lsblk blkid mount umount find update-grub; do
        command -v "$cmd" &>/dev/null || missing+=("$cmd")
    done
    [[ ${#missing[@]} -eq 0 ]] || die "コマンドが見つかりません: ${missing[*]}"
}

backup_file() {
    local src="$1" base
    base=$(basename "$src")
    mkdir -p "$BACKUP_DIR"
    local dst="${BACKUP_DIR}/${base}.bak.$(date +%Y%m%d_%H%M%S)"
    cp "$src" "$dst"
    echo "$dst"
}

# ============================================================
#  ファームウェアモードを検出 (uefi / bios)
# ============================================================
detect_firmware() {
    if [[ -d /sys/firmware/efi ]]; then
        echo "uefi"
    else
        echo "bios"
    fi
}

# ============================================================
#  パーティションテーブル種別を検出 (gpt / msdos / unknown)
# ============================================================
detect_partition_table() {
    local dev="$1"
    local disk
    disk=$(lsblk -no PKNAME "$dev" 2>/dev/null | head -1)
    [[ -z "$disk" ]] && echo "unknown" && return
    local pttype
    pttype=$(blkid -p -o value -s PTTYPE "/dev/${disk}" 2>/dev/null || true)
    case "$pttype" in
        gpt)   echo "gpt" ;;
        dos)   echo "msdos" ;;
        *)     echo "unknown" ;;
    esac
}

# ============================================================
#  ファイルシステム種別から GRUB insmod 名を返す
#  戻り値: "insmod_fs:insmod_part" の形式
# ============================================================
fstype_to_grub_insmod() {
    local fstype="$1"
    local part_type="$2"   # gpt / msdos / unknown

    local ins_fs
    case "$fstype" in
        ext2|ext3|ext4)        ins_fs="ext2" ;;
        btrfs)                 ins_fs="btrfs" ;;
        xfs)                   ins_fs="xfs" ;;
        vfat|fat16|fat32|fat)  ins_fs="fat" ;;
        ntfs|ntfs-3g)          ins_fs="ntfs" ;;
        exfat)                 ins_fs="exfat" ;;
        *)                     ins_fs="ext2" ;;   # フォールバック
    esac

    local ins_part
    case "$part_type" in
        gpt)    ins_part="part_gpt" ;;
        msdos)  ins_part="part_msdos" ;;
        *)      ins_part="part_gpt part_msdos" ;;  # 判定不能なら両方
    esac

    echo "${ins_fs}:${ins_part}"
}

# ============================================================
#  grub.cfg のパスを返す (BIOS/UEFI 両対応で探索)
# ============================================================
find_grub_cfg() {
    local f
    for f in /boot/grub/grub.cfg /boot/grub2/grub.cfg \
             /boot/efi/EFI/ubuntu/grub.cfg /boot/efi/EFI/debian/grub.cfg \
             /boot/efi/EFI/*/grub.cfg; do
        [[ -f "$f" ]] && echo "$f" && return
    done
    echo ""
}

# ============================================================
#  menuentry/submenu の表示名を1行から抽出
# ============================================================
_SQ="'"
_DQ='"'
extract_entry_name() {
    local line="$1"
    local pat="(menuentry|submenu)[[:space:]]+[${_SQ}${_DQ}]([^${_SQ}${_DQ}]*)[${_SQ}${_DQ}]"
    if [[ "$line" =~ $pat ]]; then
        echo "${BASH_REMATCH[2]}"
    else
        echo ""
    fi
}

# ============================================================
#  grub.cfg をパースしてエントリー情報を配列に格納
# ============================================================
ENTRY_NAMES=()
ENTRY_CLASSES=()
ENTRY_GRUB_IDS=()
ENTRY_SOURCES=()

parse_grub_cfg() {
    local cfg_file="$1"

    declare -gA CUSTOM_ENTRY_MAP=()
    local in_cs=0
    while IFS= read -r line; do
        if [[ "$line" =~ ^###\ BEGIN\ (.+)\ ###$ ]]; then
            [[ "${BASH_REMATCH[1]}" == "$CUSTOM_FILE" ]] && in_cs=1 || in_cs=0
            continue
        fi
        [[ "$line" =~ ^###\ END\ (.+)\ ###$ ]] && { in_cs=0; continue; }
        if [[ $in_cs -eq 1 ]]; then
            local cn; cn=$(extract_entry_name "$line")
            [[ -n "$cn" ]] && CUSTOM_ENTRY_MAP["$cn"]=1
        fi
    done < "$cfg_file"

    ENTRY_NAMES=()
    ENTRY_CLASSES=()
    ENTRY_GRUB_IDS=()
    ENTRY_SOURCES=()

    local top_index=-1 in_submenu=0 sub_index=-1 sub_top_index=-1

    while IFS= read -r line; do

        if [[ $in_submenu -eq 0 ]] && [[ "$line" =~ ^submenu[[:space:]] ]]; then
            ((top_index++)) || true
            sub_top_index=$top_index; in_submenu=1; sub_index=-1
            local sname; sname=$(extract_entry_name "$line")
            local src="auto"; [[ -n "${CUSTOM_ENTRY_MAP[$sname]+_}" ]] && src="custom"
            ENTRY_NAMES+=("$sname"); ENTRY_CLASSES+=("submenu_header")
            ENTRY_GRUB_IDS+=("$top_index"); ENTRY_SOURCES+=("$src")
            continue
        fi

        if [[ $in_submenu -eq 1 ]] && [[ "$line" =~ ^'}'[[:space:]]*$ ]]; then
            in_submenu=0; sub_top_index=-1; continue
        fi

        if [[ $in_submenu -eq 1 ]] && [[ "$line" =~ ^[[:space:]]+menuentry[[:space:]] ]]; then
            ((sub_index++)) || true
            local ename; ename=$(extract_entry_name "$line")
            local src="auto"; [[ -n "${CUSTOM_ENTRY_MAP[$ename]+_}" ]] && src="custom"
            ENTRY_NAMES+=("$ename"); ENTRY_CLASSES+=("sub_entry")
            ENTRY_GRUB_IDS+=("${sub_top_index}>${sub_index}"); ENTRY_SOURCES+=("$src")
            continue
        fi

        if [[ $in_submenu -eq 0 ]] && [[ "$line" =~ ^menuentry[[:space:]] ]]; then
            ((top_index++)) || true
            local ename; ename=$(extract_entry_name "$line")
            local src="auto"; [[ -n "${CUSTOM_ENTRY_MAP[$ename]+_}" ]] && src="custom"
            ENTRY_NAMES+=("$ename"); ENTRY_CLASSES+=("toplevel")
            ENTRY_GRUB_IDS+=("$top_index"); ENTRY_SOURCES+=("$src")
            continue
        fi

    done < "$cfg_file"
}

# ============================================================
#  エントリー一覧を表示
# ============================================================
print_entries() {
    if [[ ${#ENTRY_NAMES[@]} -eq 0 ]]; then
        warn "menuentry が見つかりませんでした。"
        return
    fi

    printf "\n  ${BOLD}%-4s  %-14s  %-12s  %-8s  %s${RESET}\n" \
        "No." "GRUB ID" "種別" "出所" "エントリー名"
    hr_thin

    for i in "${!ENTRY_NAMES[@]}"; do
        local class="${ENTRY_CLASSES[$i]}" src="${ENTRY_SOURCES[$i]}"
        local grub_id="${ENTRY_GRUB_IDS[$i]}" name="${ENTRY_NAMES[$i]}"

        local type_label
        case "$class" in
            toplevel)       type_label="${GREEN}エントリー${RESET}" ;;
            submenu_header) type_label="${YELLOW}サブメニュー${RESET}" ;;
            sub_entry)      type_label="${CYAN}  └子エントリ${RESET}" ;;
        esac

        local src_label
        [[ "$src" == "custom" ]] \
            && src_label="${GREEN}custom${RESET}" \
            || src_label="${CYAN}auto${RESET}  "

        local display_name="$name"
        [[ "$class" == "sub_entry" ]] && display_name="    ${name}"

        printf "  ${BOLD}%3d${RESET}   %-14s  %-22b  %-16b  %s\n" \
            "$i" "$grub_id" "$type_label" "$src_label" "$display_name"
    done

    echo ""
    info "出所: ${GREEN}custom${RESET} = 40_custom(削除可能)  ${CYAN}auto${RESET} = 自動生成(削除不可)"
}

# ============================================================
#  現在の GRUB 設定を表示
# ============================================================
print_current_settings() {
    hr
    echo -e "${BOLD}■ 現在の GRUB 設定${RESET}"
    hr_thin

    local fw; fw=$(detect_firmware)
    if [[ "$fw" == "uefi" ]]; then
        echo -e "  ファームウェア: ${GREEN}${BOLD}UEFI${RESET}"
    else
        echo -e "  ファームウェア: ${YELLOW}${BOLD}BIOS (Legacy)${RESET}"
    fi

    local default_grub="/etc/default/grub"
    if [[ -f "$default_grub" ]]; then
        local dv tv
        dv=$(grep '^GRUB_DEFAULT=' "$default_grub" | cut -d= -f2 | tr -d '"' || echo "未設定")
        tv=$(grep '^GRUB_TIMEOUT='  "$default_grub" | cut -d= -f2 | tr -d '"' || echo "未設定")
        echo -e "  GRUB_DEFAULT  : ${BOLD}${dv}${RESET}"
        echo -e "  GRUB_TIMEOUT  : ${BOLD}${tv}${RESET} 秒"
    fi

    local env_file
    for env_file in /boot/grub/grubenv /boot/grub2/grubenv; do
        [[ -f "$env_file" ]] || continue
        local saved next rf
        saved=$(grub-editenv "$env_file" list 2>/dev/null | grep '^saved_entry=' | cut -d= -f2 || true)
        next=$(grub-editenv  "$env_file" list 2>/dev/null | grep '^next_entry='   | cut -d= -f2 || true)
        rf=$(grub-editenv    "$env_file" list 2>/dev/null | grep '^recordfail='   | cut -d= -f2 || true)
        echo -e "  saved_entry   : ${BOLD}${saved:-(なし)}${RESET}"
        echo -e "  next_entry    : ${BOLD}${next:-(なし)}${RESET}"
        if [[ "${rf}" == "1" ]]; then
            echo -e "  recordfail    : ${RED}${BOLD}1(残存)${RESET}"
            warn "recordfail=1 が残っています。次回起動時に影響する場合があります。"
        else
            echo -e "  recordfail    : ${GREEN}なし${RESET}"
        fi
        break
    done
    echo ""
}

# ============================================================
#  ISO をループマウントして起動方式・カーネル/initrdパスを検出
#
#  対応レイアウト:
#    casper  … Ubuntu / Ubuntu派生 (casper)
#    antix   … antiX (独自の live-init。casper/live-bootとは別物)
#    live    … 汎用 Debian live-boot (live-boot パッケージ)
#    custom  … 上記いずれにも一致しない場合のフォールバック(要手動入力)
#
#  加えて、ISO内に GRUB ループバック用の専用設定ファイル
#  (boot/grub/loopback.cfg など) が同梱されていないか調べる。
#  これが見つかった場合、そのファイルを configfile で読み込むことで
#  実機/実CDから起動したときと同じ起動メニュー(カーネル選択・
#  persistenceオプション等)をそのまま再現できる。
# ============================================================
_detect_boot_paths() {
    local iso_path="$1"
    local tmp="/mnt/_iso_inspect_$$"
    mkdir -p "$tmp"
    if ! mount -o loop,ro "$iso_path" "$tmp" 2>/dev/null; then
        rmdir "$tmp" 2>/dev/null || true
        echo "UNKNOWN:UNKNOWN:custom:NONE"; return
    fi

    local vmlinuz="" initrd="" boot_type=""

    if   [[ -f "$tmp/casper/vmlinuz" ]];     then vmlinuz="/casper/vmlinuz";     initrd="/casper/initrd";     boot_type="casper"
    elif [[ -f "$tmp/casper/vmlinuz.efi" ]]; then vmlinuz="/casper/vmlinuz.efi"; initrd="/casper/initrd.lz";  boot_type="casper"
    elif [[ -f "$tmp/antiX/vmlinuz1" ]];     then vmlinuz="/antiX/vmlinuz1";     initrd="/antiX/initrd.gz";   boot_type="antix"
    elif [[ -f "$tmp/antiX/vmlinuz" ]];      then vmlinuz="/antiX/vmlinuz";      initrd="/antiX/initrd.gz";   boot_type="antix"
    elif [[ -f "$tmp/live/vmlinuz" ]];       then vmlinuz="/live/vmlinuz";       initrd="/live/initrd.img";   boot_type="live"
    elif [[ -f "$tmp/live/vmlinuz.efi" ]];   then vmlinuz="/live/vmlinuz.efi";   initrd="/live/initrd.img";   boot_type="live"
    else
        local found; found=$(find "$tmp" -name "vmlinuz*" | head -1 || true)
        if [[ -n "$found" ]]; then
            vmlinuz="${found##"$tmp"}"
            local dir; dir=$(dirname "$found")
            local ird; ird=$(find "$dir" -name "initrd*" | head -1 || true)
            initrd="${ird##"$tmp"}"
            boot_type="custom"
        else
            vmlinuz="UNKNOWN"; initrd="UNKNOWN"; boot_type="custom"
        fi
    fi

    # ISO本来の起動メニューを再現するための loopback 用 GRUB 設定を探す。
    # antiX / MX Linux / Ubuntu 系の多くは、実機起動用の grub.cfg とは別に
    # loopback boot専用の設定ファイルを同梱している。
    local loopback_cfg="NONE"
    local cfg_candidates=(
        "/boot/grub/loopback.cfg"
        "/isolinux/loopback.cfg"
        "/EFI/BOOT/loopback.cfg"
        "/boot/grub/grub.cfg"
    )
    local c
    for c in "${cfg_candidates[@]}"; do
        if [[ -f "${tmp}${c}" ]]; then
            loopback_cfg="$c"
            break
        fi
    done

    umount "$tmp"; rmdir "$tmp"
    echo "${vmlinuz}:${initrd}:${boot_type}:${loopback_cfg}"
}

# ============================================================
#  1: ISO ループブートエントリーの追加
# ============================================================
action_add_iso() {
    hr
    echo -e "${BOLD}■ ISO ループブートエントリーの追加${RESET}"
    hr_thin
    echo ""

    info "パーティション一覧:"
    echo ""

    mapfile -t part_list < <(
        lsblk -lno NAME,SIZE,FSTYPE,LABEL,MOUNTPOINTS \
        | grep -v '^loop' \
        | awk 'NF && $1 ~ /[0-9]$/ { print $0 }'
    )
    [[ ${#part_list[@]} -eq 0 ]] && die "パーティションが見つかりませんでした。"

    printf "  ${BOLD}%-4s  %s${RESET}\n" "No." "NAME  SIZE  FSTYPE  LABEL  MOUNTPOINTS"
    hr_thin
    for i in "${!part_list[@]}"; do
        printf "  ${CYAN}[%2d]${RESET}  %s\n" "$((i+1))" "${part_list[$i]}"
    done
    echo ""

    local part_dev part_uuid part_fstype part_pttype
    while true; do
        echo -en "${BOLD}番号を入力してください${RESET} [1-${#part_list[@]}]: "
        read -r sel
        if ! [[ "$sel" =~ ^[0-9]+$ ]] || (( sel < 1 || sel > ${#part_list[@]} )); then
            error "1〜${#part_list[@]} の番号を入力してください。"; continue
        fi
        local pname; pname=$(echo "${part_list[$((sel-1))]}" | awk '{print $1}')
        part_dev="/dev/${pname}"
        [[ -b "$part_dev" ]] || { error "${part_dev} はブロックデバイスではありません。"; continue; }

        part_fstype=$(lsblk -no FSTYPE "$part_dev" 2>/dev/null | head -1 || true)
        part_pttype=$(detect_partition_table "$part_dev")

        case "$part_fstype" in
            ext2|ext3|ext4|btrfs|xfs|vfat|fat16|fat32|fat|ntfs|ntfs-3g|exfat) ;;
            *)
                warn "${part_dev} のファイルシステム(${part_fstype:-不明})は動作確認済みではありません。"
                confirm "このまま続けますか?" || continue
                ;;
        esac

        echo ""
        lsblk -o NAME,SIZE,FSTYPE,UUID,MOUNTPOINTS "$part_dev" 2>/dev/null || true
        echo -e "  パーティションテーブル: ${BOLD}${part_pttype}${RESET}"
        echo ""
        confirm "${part_dev} を使用しますか?" && break
    done

    part_uuid=$(blkid -s UUID -o value "$part_dev")
    [[ -n "$part_uuid" ]] || die "UUID を取得できませんでした: ${part_dev}"
    success "パーティション  : ${part_dev}"
    success "UUID           : ${part_uuid}"
    success "FS 種別        : ${part_fstype:-不明}"
    success "パーティション表: ${part_pttype}"
    echo ""

    local insmod_str; insmod_str=$(fstype_to_grub_insmod "$part_fstype" "$part_pttype")
    local ins_fs="${insmod_str%%:*}"
    local ins_part="${insmod_str#*:}"

    local mount_point="/mnt/_isoboot_tmp_$$"
    mkdir -p "$mount_point"
    mount "$part_dev" "$mount_point"
    info "${part_dev} を ${mount_point} にマウントしました"
    echo ""

    mapfile -t iso_candidates < <(find "$mount_point" -maxdepth 2 -name "*.iso" 2>/dev/null)
    if [[ ${#iso_candidates[@]} -eq 0 ]]; then
        umount "$mount_point"; rmdir "$mount_point"
        die "ISO ファイルが見つかりませんでした。"
    fi

    info "見つかった ISO ファイル:"
    for i in "${!iso_candidates[@]}"; do
        local sz; sz=$(du -sh "${iso_candidates[$i]}" 2>/dev/null | cut -f1)
        printf "  ${CYAN}[%d]${RESET}  %s  (%s)\n" "$((i+1))" "${iso_candidates[$i]##"$mount_point"}" "$sz"
    done
    printf "  ${CYAN}[%d]${RESET}  何もせず終了\n" "$((${#iso_candidates[@]}+1))"
    echo ""

    local iso_entries=()
    local sel_input sel_nums=()
    while true; do
        echo -en "${BOLD}追加する番号を入力${RESET}(スペース区切り複数可): "
        read -r sel_input
        local cancel_num=$(( ${#iso_candidates[@]} + 1 ))
        local do_cancel=0
        for n in $sel_input; do
            [[ "$n" == "$cancel_num" ]] && do_cancel=1 && break
        done
        if [[ $do_cancel -eq 1 ]] || [[ -z "$sel_input" ]]; then
            umount "$mount_point"; rmdir "$mount_point"
            info "キャンセルしました。"; return
        fi
        local valid=1
        sel_nums=()
        for n in $sel_input; do
            if ! [[ "$n" =~ ^[0-9]+$ ]] || (( n < 1 || n > ${#iso_candidates[@]} )); then
                error "1〜${#iso_candidates[@]} の番号を入力してください。"
                valid=0; break
            fi
            sel_nums+=("$n")
        done
        [[ $valid -eq 1 ]] && break
    done

    for n in "${sel_nums[@]}"; do
        local iso_full="${iso_candidates[$((n-1))]}"
        local iso_rel="${iso_full##"$mount_point"}"
        local iso_name; iso_name=$(basename "$iso_full")
        echo ""
        info "--- ${iso_name} を検査中 ---"
        local paths; paths=$(_detect_boot_paths "$iso_full")
        local vmlinuz initrd boot_type loopback_cfg
        IFS=':' read -r vmlinuz initrd boot_type loopback_cfg <<< "$paths"
        if [[ "$vmlinuz" == "UNKNOWN" ]]; then
            warn "カーネルパスを自動検出できませんでした。手動入力してください。"
            echo -en "  vmlinuz のパス (例: /casper/vmlinuz): "; read -r vmlinuz
            echo -en "  initrd  のパス (例: /casper/initrd): ";  read -r initrd
            boot_type="custom"
        else
            success "カーネル : ${vmlinuz}"
            success "initrd   : ${initrd}"
            success "起動方式 : ${boot_type}"
        fi

        local use_menu="no"
        if [[ "$loopback_cfg" != "NONE" ]]; then
            success "ISO本来の起動メニュー設定を検出: ${loopback_cfg}"
            if confirm "実CD起動時と同じ起動メニューを表示させますか?(推奨)"; then
                use_menu="yes"
            else
                loopback_cfg="NONE"
            fi
        else
            info "ISO本来の起動メニュー設定は見つかりませんでした(直接カーネル起動になります)。"
        fi

        iso_entries+=("${iso_rel}:${vmlinuz}:${initrd}:${boot_type}:${loopback_cfg}")
    done

    umount "$mount_point"; rmdir "$mount_point"
    success "アンマウント完了"

    [[ ${#iso_entries[@]} -eq 0 ]] && { warn "追加するエントリがありません。"; return; }

    echo ""
    local bak; bak=$(backup_file "$CUSTOM_FILE")
    success "バックアップ: ${bak}"
    echo ""

    for entry in "${iso_entries[@]}"; do
        local iso_rel vmlinuz initrd boot_type loopback_cfg
        IFS=':' read -r iso_rel vmlinuz initrd boot_type loopback_cfg <<< "$entry"
        local menu_label; menu_label=$(basename "$iso_rel" .iso)

        local insmod_lines=""
        for mod in $ins_part; do
            insmod_lines+="    insmod ${mod}"$'\n'
        done
        insmod_lines+="    insmod loopback"$'\n'
        insmod_lines+="    insmod iso9660"$'\n'
        insmod_lines+="    insmod ${ins_fs}"$'\n'
        insmod_lines+="    insmod regexp"

        local grub_entry
        if [[ "$loopback_cfg" != "NONE" ]]; then
            # ISO 内の専用 loopback 設定を configfile で読み込み、
            # 実CD/実機起動と同じメニュー(カーネル選択・言語・
            # Advanced Options 等)をそのまま表示する。
            grub_entry=$(cat <<EOF

menuentry "${menu_label} (ISO Loop Boot - 起動メニュー表示)" {
${insmod_lines}
    search --no-floppy --fs-uuid --set=isodev ${part_uuid}
    set isofile="${iso_rel}"
    loopback loop (\$isodev)\$isofile
    set root=(loop)
    configfile (loop)${loopback_cfg}
}
EOF
)
        else
            local params
            case "$boot_type" in
                casper)
                    # Ubuntu / casper 系
                    params="boot=casper iso-scan/filename=\$isofile quiet splash"
                    ;;
                live)
                    # 汎用 Debian live-boot (casper とは別物。findiso= を使う)
                    params="boot=live findiso=\$isofile quiet splash"
                    ;;
                antix)
                    # antiX は casper も live-boot も使わない独自の live-init。
                    # from=hd/all で「どの種類のデバイスを探すか」を絞ると、
                    # 一致するUUIDのデバイスがあってもブロックされてしまう
                    # (antiX側の既知の挙動)。buuid= で直接指定し、from= は
                    # 付けない。VMのvirtioディスクでもこれで検出できる。
                    params="buuid=${part_uuid} fromiso=\$isofile quiet nocheckfs persist"
                    ;;
                *)
                    # 未知のレイアウト。casper 相当のパラメータで試すが
                    # 起動しない場合は手動調整が必要。
                    params="iso-scan/filename=\$isofile quiet splash"
                    ;;
            esac

            grub_entry=$(cat <<EOF

menuentry "${menu_label} (ISO Loop Boot)" {
${insmod_lines}
    search --no-floppy --fs-uuid --set=isodev ${part_uuid}
    set isofile="${iso_rel}"
    loopback loop (\$isodev)\$isofile
    linux  (loop)${vmlinuz} ${params}
    initrd (loop)${initrd}
}
EOF
)
        fi

        echo -e "${CYAN}追加予定エントリ (検出レイアウト: ${boot_type}, メニュー使用: $([[ "$loopback_cfg" != "NONE" ]] && echo はい || echo いいえ)):${RESET}"
        echo "$grub_entry"
        echo ""
        if confirm "このエントリを追加しますか?"; then
            echo "$grub_entry" >> "$CUSTOM_FILE"
            success "追加しました: ${menu_label}"
        else
            warn "スキップ: ${menu_label}"
        fi
    done

    _run_update_grub
}

# ============================================================
#  2: 不要エントリーの削除
# ============================================================
action_delete() {
    hr
    echo -e "${BOLD}■ 不要エントリーの削除${RESET}"
    hr_thin
    echo ""

    local deletable_indices=()
    for i in "${!ENTRY_SOURCES[@]}"; do
        [[ "${ENTRY_SOURCES[$i]}" == "custom" ]] \
        && [[ "${ENTRY_CLASSES[$i]}" != "submenu_header" ]] \
        && deletable_indices+=("$i")
    done

    if [[ ${#deletable_indices[@]} -eq 0 ]]; then
        warn "削除可能なエントリー(40_custom 由来)が見つかりませんでした。"
        return
    fi

    echo -e "  ${BOLD}削除可能なエントリー一覧:${RESET}"
    echo ""
    printf "  ${BOLD}%-4s  %-14s  %s${RESET}\n" "No." "GRUB ID" "エントリー名"
    hr_thin
    for i in "${deletable_indices[@]}"; do
        printf "  ${BOLD}%3d${RESET}   %-14s  %s\n" \
            "$i" "${ENTRY_GRUB_IDS[$i]}" "${ENTRY_NAMES[$i]}"
    done
    echo ""

    local sel_input to_delete=() valid
    while true; do
        echo -en "${BOLD}削除する番号を入力${RESET}(スペース区切り複数可、Enter でキャンセル): "
        read -r sel_input
        [[ -z "$sel_input" ]] && { info "キャンセルしました。"; return; }

        valid=1; to_delete=()
        for num in $sel_input; do
            if ! [[ "$num" =~ ^[0-9]+$ ]]; then
                error "無効な入力: ${num}"; valid=0; break
            fi
            local found=0
            for di in "${deletable_indices[@]}"; do
                [[ "$di" -eq "$num" ]] && found=1 && break
            done
            if [[ $found -eq 0 ]]; then
                error "番号 ${num} は削除可能なエントリーではありません。"; valid=0; break
            fi
            to_delete+=("$num")
        done
        [[ $valid -eq 1 ]] && break
    done

    echo ""
    echo -e "  ${BOLD}${RED}以下のエントリーを削除します:${RESET}"
    for num in "${to_delete[@]}"; do
        echo -e "    ${RED}・${ENTRY_NAMES[$num]}${RESET}"
    done
    echo ""
    confirm "本当に削除しますか?" || { info "キャンセルしました。"; return; }

    local bak; bak=$(backup_file "$CUSTOM_FILE")
    success "バックアップ: ${bak}"

    local stale_baks=()
    while IFS= read -r -d '' f; do
        stale_baks+=("$f")
    done < <(find /etc/grub.d/ -maxdepth 1 -name '40_custom.bak.*' -print0 2>/dev/null)
    if [[ ${#stale_baks[@]} -gt 0 ]]; then
        warn "/etc/grub.d/ に古いバックアップが残っています(エントリー復活の原因):"
        for f in "${stale_baks[@]}"; do warn "  ${f}"; done
        echo ""
        if confirm "古いバックアップを /etc/grub.d/ から削除しますか?"; then
            for f in "${stale_baks[@]}"; do rm -f "$f" && success "削除: $f"; done
        fi
        echo ""
    fi

    declare -A name_delete_occurrences=()
    for num in "${to_delete[@]}"; do
        local tname="${ENTRY_NAMES[$num]}"
        local occ=0
        for i in "${!ENTRY_NAMES[@]}"; do
            [[ "${ENTRY_SOURCES[$i]}" != "custom" ]] && continue
            [[ "${ENTRY_CLASSES[$i]}" == "submenu_header" ]] && continue
            [[ "${ENTRY_NAMES[$i]}" == "$tname" ]] && ((occ++)) || true
            [[ "$i" -eq "$num" ]] && break
        done
        name_delete_occurrences["$tname"]+="${occ} "
    done

    local tmp; tmp=$(mktemp)
    cp "$CUSTOM_FILE" "$tmp"

    for tname in "${!name_delete_occurrences[@]}"; do
        local sorted_occs
        sorted_occs=$(echo "${name_delete_occurrences[$tname]}" | tr ' ' '\n' | grep -v '^$' | sort -rn)
        for occ in $sorted_occs; do
            awk -v name="$tname" -v target_occ="$occ" '
            BEGIN { skip=0; depth=0; match_count=0 }
            {
                if (!skip && /menuentry/ && index($0, name) > 0) {
                    match_count++
                    if (match_count == target_occ) { skip=1; depth=0 }
                }
                if (skip) {
                    n = split($0, chars, "")
                    for (i=1; i<=n; i++) {
                        if (chars[i] == "{") depth++
                        else if (chars[i] == "}") {
                            depth--
                            if (depth <= 0) { skip=0; break }
                        }
                    }
                    next
                }
                print
            }' "$tmp" > "${tmp}.new"
            mv "${tmp}.new" "$tmp"
            success "削除: ${tname}(${occ}番目)"
        done
    done

    cat "$tmp" > "$CUSTOM_FILE"
    rm -f "$tmp"

    _run_update_grub
}

# ============================================================
#  update-grub を実行してエントリー一覧を再表示
# ============================================================
_run_update_grub() {
    echo ""
    info "GRUB を更新中..."
    if update-grub 2>&1; then
        success "update-grub 完了"
        local grub_cfg; grub_cfg=$(find_grub_cfg)
        if [[ -n "$grub_cfg" ]]; then
            echo ""
            info "エントリー一覧を更新しました:"
            parse_grub_cfg "$grub_cfg"
            print_entries
        fi
    else
        warn "update-grub でエラーが発生しました。手動で確認してください: sudo update-grub"
    fi
}

# ============================================================
#  操作メニュー
# ============================================================
show_action_menu() {
    hr
    echo -e "${BOLD}■ 操作を選択してください${RESET}"
    hr_thin
    echo ""
    echo -e "  ${BOLD}[1]${RESET}  ISO ループブートエントリーを追加"
    echo -e "  ${BOLD}[2]${RESET}  不要エントリーを削除  ${CYAN}(40_custom 由来のみ)${RESET}"
    echo -e "  ${BOLD}[3]${RESET}  何もせず終了"
    echo ""

    local choice
    while true; do
        echo -en "${BOLD}選択${RESET} [1/2/3]: "
        read -r choice
        case "$choice" in
            1) action_add_iso; break ;;
            2) action_delete; break ;;
            3) echo ""; info "変更なしで終了します。"; exit 0 ;;
            *) error "1、2、または 3 を入力してください。" ;;
        esac
    done
}

# ============================================================
#  メイン
# ============================================================
main() {
    clear
    hr
    echo -e "${BOLD}  isoboot-grub.sh — ISO ループブート GRUB エントリー管理ツール${RESET}"
    hr
    echo -e "  実行日時: $(date '+%Y-%m-%d %H:%M:%S')"
    echo ""

    check_root
    check_deps

    local grub_cfg; grub_cfg=$(find_grub_cfg)
    [[ -n "$grub_cfg" ]] || die "grub.cfg が見つかりませんでした。"
    info "設定ファイル: ${BOLD}${grub_cfg}${RESET}"

    parse_grub_cfg "$grub_cfg"
    print_current_settings

    hr
    echo -e "${BOLD}■ GRUB エントリー一覧${RESET}"
    hr_thin
    print_entries

    if [[ $(detect_firmware) == "uefi" ]] && command -v efibootmgr &>/dev/null; then
        hr
        echo -e "${BOLD}■ EFI ブートエントリー(参考)${RESET}"
        hr_thin
        echo ""
        efibootmgr 2>/dev/null | while IFS= read -r line; do
            [[ "$line" =~ ^\* ]] \
                && echo -e "  ${GREEN}${line}${RESET}  ${CYAN}← アクティブ${RESET}" \
                || echo -e "  ${line}"
        done
        echo ""
    fi

    show_action_menu

    echo ""
    hr
    success "完了しました。"
    hr
    echo ""
}

main "$@"

パッチ

sudo rm antix-loopboot-patch.sh
sudo nano antix-loopboot-patch.sh
sudo bash antix-loopboot-patch.sh
#!/usr/bin/env bash
# ============================================================
#  antix-loopboot-patch.sh
#    — antiX ISO を GRUB からループブートした際に、実CD/実機起動と
#      同じメニュー(configfile方式)を使うと発生する
#        "Fatal Error: Could not find file antiX/linuxfs"
#      を解消するためのパッチスクリプト。
#
#  背景:
#    isoboot-grub.sh で「実CD起動と同じメニューを表示」を選ぶと、
#    ISO内の grub.cfg をそのまま configfile で読み込みます。
#    しかしこの grub.cfg は実メディア(CD/USB)起動専用に作られており、
#    各 menuentry の linux 行には fromiso=/buuid=/persist といった
#    ループブート用の起動コードが含まれていません。そのため antiX の
#    live-init が usb,cd デバイスしか探索せず、ハードディスク上の
#    ISOを見つけられずに Fatal Error になります。
#
#  このスクリプトの処理:
#    1. 対象のパーティション/ISOを選択
#    2. ISO内の grub.cfg (メニュー本体) を取り出し、
#       各カーネル起動行に fromiso=/buuid=/nocheckfs/persist を
#       追記した「パッチ済みコピー」を、ISOと同じパーティション上
#       (/isoboot-patched-cfg/) に保存
#    3. 40_custom 内に、この ISO を configfile 方式で読み込んでいる
#       既存エントリーがあれば、その configfile の参照先を
#       パッチ済みコピーに書き換える
#       (見つからない場合は、そのまま使えるエントリー例を表示)
#    4. update-grub を実行
#
#  既存の isoboot-grub.sh とは独立して動作します。
#  「実CD起動と同じメニュー」で登録した状態で今回のエラーが出た
#  場合にのみ、このスクリプトを実行してください。
# ============================================================

set -euo pipefail

CUSTOM_FILE="${CUSTOM_FILE:-/etc/grub.d/40_custom}"
BACKUP_DIR="/root/grub-backups"
PATCHED_CFG_DIR_NAME="isoboot-patched-cfg"

RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
BOLD='\033[1m'
RESET='\033[0m'

info()    { echo -e "${CYAN}[INFO]${RESET}  $*"; }
success() { echo -e "${GREEN}[OK]${RESET}    $*"; }
warn()    { echo -e "${YELLOW}[WARN]${RESET}  $*"; }
error()   { echo -e "${RED}[ERROR]${RESET} $*" >&2; }
die()     { error "$*"; exit 1; }
hr()      { echo -e "${CYAN}$(printf '=%.0s' {1..64})${RESET}"; }
hr_thin() { echo -e "${CYAN}$(printf -- '-%.0s' {1..64})${RESET}"; }

confirm() {
    local ans
    while true; do
        echo -en "${YELLOW}[確認]${RESET} $1 [y/N]: "
        read -r ans
        case "$ans" in
            [yY]*) return 0 ;;
            [nN]*|"") return 1 ;;
            *) echo "  y または n を入力してください。" ;;
        esac
    done
}

check_root() {
    [[ $EUID -eq 0 ]] || die "sudo で実行してください。\n  例: sudo bash $0"
}

check_deps() {
    local missing=()
    for cmd in lsblk blkid mount umount find sed update-grub; do
        command -v "$cmd" &>/dev/null || missing+=("$cmd")
    done
    [[ ${#missing[@]} -eq 0 ]] || die "コマンドが見つかりません: ${missing[*]}"
}

backup_file() {
    local src="$1" base
    base=$(basename "$src")
    mkdir -p "$BACKUP_DIR"
    local dst="${BACKUP_DIR}/${base}.bak.$(date +%Y%m%d_%H%M%S)"
    cp "$src" "$dst"
    echo "$dst"
}

detect_partition_table() {
    local dev="$1"
    local disk
    disk=$(lsblk -no PKNAME "$dev" 2>/dev/null | head -1)
    [[ -z "$disk" ]] && echo "unknown" && return
    local pttype
    pttype=$(blkid -p -o value -s PTTYPE "/dev/${disk}" 2>/dev/null || true)
    case "$pttype" in
        gpt)   echo "gpt" ;;
        dos)   echo "msdos" ;;
        *)     echo "unknown" ;;
    esac
}

fstype_to_grub_insmod() {
    local fstype="$1" part_type="$2" ins_fs ins_part
    case "$fstype" in
        ext2|ext3|ext4)        ins_fs="ext2" ;;
        btrfs)                 ins_fs="btrfs" ;;
        xfs)                   ins_fs="xfs" ;;
        vfat|fat16|fat32|fat)  ins_fs="fat" ;;
        ntfs|ntfs-3g)          ins_fs="ntfs" ;;
        exfat)                 ins_fs="exfat" ;;
        *)                     ins_fs="ext2" ;;
    esac
    case "$part_type" in
        gpt)    ins_part="part_gpt" ;;
        msdos)  ins_part="part_msdos" ;;
        *)      ins_part="part_gpt part_msdos" ;;
    esac
    echo "${ins_fs}:${ins_part}"
}

# ============================================================
#  対象パーティション / ISO を選択させる
# ============================================================
select_partition_and_iso() {
    info "パーティション一覧:"
    echo ""
    mapfile -t part_list < <(
        lsblk -lno NAME,SIZE,FSTYPE,LABEL,MOUNTPOINTS \
        | grep -v '^loop' \
        | awk 'NF && $1 ~ /[0-9]$/ { print $0 }'
    )
    [[ ${#part_list[@]} -eq 0 ]] && die "パーティションが見つかりませんでした。"

    printf "  ${BOLD}%-4s  %s${RESET}\n" "No." "NAME  SIZE  FSTYPE  LABEL  MOUNTPOINTS"
    hr_thin
    for i in "${!part_list[@]}"; do
        printf "  ${CYAN}[%2d]${RESET}  %s\n" "$((i+1))" "${part_list[$i]}"
    done
    echo ""

    local sel pname
    while true; do
        echo -en "${BOLD}番号を入力してください${RESET} [1-${#part_list[@]}]: "
        read -r sel
        if ! [[ "$sel" =~ ^[0-9]+$ ]] || (( sel < 1 || sel > ${#part_list[@]} )); then
            error "1〜${#part_list[@]} の番号を入力してください。"; continue
        fi
        pname=$(echo "${part_list[$((sel-1))]}" | awk '{print $1}')
        PART_DEV="/dev/${pname}"
        [[ -b "$PART_DEV" ]] || { error "${PART_DEV} はブロックデバイスではありません。"; continue; }
        break
    done

    PART_UUID=$(blkid -s UUID -o value "$PART_DEV")
    [[ -n "$PART_UUID" ]] || die "UUID を取得できませんでした: ${PART_DEV}"
    PART_FSTYPE=$(lsblk -no FSTYPE "$PART_DEV" 2>/dev/null | head -1 || true)
    PART_PTTYPE=$(detect_partition_table "$PART_DEV")

    success "パーティション: ${PART_DEV}  (UUID: ${PART_UUID})"

    # 既に別の場所へマウント済みならそれを再利用し、未マウントなら
    # 自前でマウントする(自前でマウントした場合のみ、後で自分で
    # アンマウントする=既存のマウントを勝手に外さない)。
    local existing_mp
    existing_mp=$(lsblk -no MOUNTPOINT "$PART_DEV" 2>/dev/null | grep -v '^$' | head -1 || true)

    if [[ -n "$existing_mp" ]]; then
        PART_MP="$existing_mp"
        PART_MP_OWNED=0
        info "既にマウント済みのため、そのまま使用します: ${PART_MP}"
    else
        PART_MP="/mnt/_antixpatch_part_$$"
        mkdir -p "$PART_MP"
        mount "$PART_DEV" "$PART_MP"
        PART_MP_OWNED=1
    fi

    mapfile -t iso_candidates < <(find "$PART_MP" -maxdepth 2 -name "*.iso" 2>/dev/null)
    if [[ ${#iso_candidates[@]} -eq 0 ]]; then
        die "ISO ファイルが見つかりませんでした。"
    fi

    echo ""
    info "見つかった ISO ファイル:"
    for i in "${!iso_candidates[@]}"; do
        printf "  ${CYAN}[%d]${RESET}  %s\n" "$((i+1))" "${iso_candidates[$i]##"$PART_MP"}"
    done
    echo ""

    local isel
    while true; do
        echo -en "${BOLD}パッチ対象の ISO 番号を入力${RESET} [1-${#iso_candidates[@]}]: "
        read -r isel
        if ! [[ "$isel" =~ ^[0-9]+$ ]] || (( isel < 1 || isel > ${#iso_candidates[@]} )); then
            error "1〜${#iso_candidates[@]} の番号を入力してください。"; continue
        fi
        break
    done

    ISO_FULL="${iso_candidates[$((isel-1))]}"
    ISO_REL="${ISO_FULL##"$PART_MP"}"
    ISO_NAME_NOEXT=$(basename "$ISO_REL" .iso)

    success "対象 ISO: ${ISO_REL}"
}

# ============================================================
#  ISO内の grub.cfg (メニュー本体) を探す
# ============================================================
find_iso_cfg_path() {
    local tmp="$1"
    local c
    for c in "/boot/grub/loopback.cfg" "/isolinux/loopback.cfg" \
             "/EFI/BOOT/loopback.cfg" "/boot/grub/grub.cfg"; do
        [[ -f "${tmp}${c}" ]] && { echo "$c"; return; }
    done
    echo ""
}

cleanup_on_exit() {
    if [[ "${PART_MP_OWNED:-0}" -eq 1 && -n "${PART_MP:-}" ]] && mountpoint -q "$PART_MP" 2>/dev/null; then
        umount "$PART_MP" 2>/dev/null || true
        rmdir "$PART_MP" 2>/dev/null || true
    fi
}

# ============================================================
#  メイン処理
# ============================================================
main() {
    trap cleanup_on_exit EXIT

    hr
    echo -e "${BOLD}  antix-loopboot-patch.sh — antiX ループブート起動コード修正パッチ${RESET}"
    hr
    echo ""
    warn "対象は antiX(および同系統の live-init を使うディストリ)です。"
    warn "「実CD起動と同じメニュー」方式(configfile)で登録し、"
    warn "  Fatal Error: Could not find file antiX/linuxfs が出た場合に使用してください。"
    echo ""

    check_root
    check_deps

    select_partition_and_iso

    local mp="/mnt/_antixpatch_iso_$$"
    mkdir -p "$mp"
    mount -o loop,ro "$ISO_FULL" "$mp"

    local cfg_rel; cfg_rel=$(find_iso_cfg_path "$mp")
    if [[ -z "$cfg_rel" ]]; then
        umount "$mp"; rmdir "$mp"
        die "ISO内にメニュー設定ファイル(grub.cfg/loopback.cfg)が見つかりませんでした。"
    fi
    success "メニュー設定ファイル: ${cfg_rel}"

    local src_cfg="${mp}${cfg_rel}"
    local work_cfg; work_cfg=$(mktemp)
    cp "$src_cfg" "$work_cfg"

    umount "$mp"; rmdir "$mp"

    # --------------------------------------------------------
    # linux 行にループブート用の起動コードを追記する。
    # linux / linux16 / linuxefi など表記ゆれに対応し、
    # vmlinuz を起動している行を広めに検出する。
    # すでに fromiso= や findiso= が含まれる行(=対応済み)には触れない。
    # --------------------------------------------------------
    local before_count after_count
    before_count=$(grep -Eic '^[[:space:]]*linux(16|efi)?[[:space:]]+.*vmlinuz' "$work_cfg" || true)

    if [[ "$before_count" -eq 0 ]]; then
        local debug_cfg="/root/isoboot-debug-loopback.cfg"
        cp "$work_cfg" "$debug_cfg"
        rm -f "$work_cfg"
        error "パッチ対象となる起動行 (linux/linux16/linuxefi ... vmlinuz) が見つかりませんでした。"
        echo ""
        info "検出したメニュー設定ファイルの内容をそのまま表示します(この内容を教えてください):"
        echo ""
        hr_thin
        cat -A "$debug_cfg" | sed -n '1,80p'
        hr_thin
        echo ""
        info "全文は ${debug_cfg} に保存しました。"
        die "起動行の自動検出に失敗しました。上記の内容を確認のうえ調整します。"
    fi

    local extra_codes="fromiso=${ISO_REL} buuid=${PART_UUID} nocheckfs persist"

    sed -i -E \
        "/^[[:space:]]*linux(16|efi)?[[:space:]]+.*vmlinuz/I { /fromiso=|findiso=/! s#\$# ${extra_codes}# }" \
        "$work_cfg"

    after_count=$(grep -Ec "fromiso=${ISO_REL//\//\\/}" "$work_cfg" || true)
    if [[ "$after_count" -eq 0 ]]; then
        rm -f "$work_cfg"
        die "起動コードの追記に失敗しました。手動確認が必要です。"
    fi
    success "起動行 ${before_count} 件に起動コードを追記しました:"
    echo -e "  ${CYAN}${extra_codes}${RESET}"

    # --------------------------------------------------------
    # パッチ済み cfg を ISO と同じパーティション上に保存する
    # (パーティションは select_partition_and_iso でマウント済み)
    # --------------------------------------------------------
    local dest_dir="${PART_MP}/${PATCHED_CFG_DIR_NAME}"
    mkdir -p "$dest_dir"
    local dest_cfg="${dest_dir}/${ISO_NAME_NOEXT}.cfg"
    cp "$work_cfg" "$dest_cfg"
    rm -f "$work_cfg"

    local dest_cfg_rel="/${PATCHED_CFG_DIR_NAME}/${ISO_NAME_NOEXT}.cfg"
    success "パッチ済みメニュー設定を保存しました: ${PART_DEV}${dest_cfg_rel}"

    # --------------------------------------------------------
    # 40_custom 内に、この ISO を configfile 方式で読み込んでいる
    # 既存エントリーがあれば書き換える
    # --------------------------------------------------------
    echo ""
    if [[ -f "$CUSTOM_FILE" ]] && grep -qF "set isofile=\"${ISO_REL}\"" "$CUSTOM_FILE" \
       && grep -qE "configfile[[:space:]]+\(loop\)" "$CUSTOM_FILE"; then

        info "既存の 40_custom エントリーを検出しました。configfile の参照先を書き換えます。"
        local bak; bak=$(backup_file "$CUSTOM_FILE")
        success "バックアップ: ${bak}"

        # isofile="<ISO_REL>" が設定されているブロック内の
        # configfile (loop)... 行だけを、パッチ済みcfgへの参照に置換する。
        awk -v isofile="${ISO_REL}" -v newref="(\$isodev)${dest_cfg_rel}" '
            BEGIN { in_block = 0 }
            {
                if ($0 ~ /^menuentry/) { in_block = 0; buf = $0"\n"; depth = 0; capturing = 1 }
                else if (capturing) { buf = buf $0 "\n" }

                if (capturing && index($0, "set isofile=\"" isofile "\"") > 0) { in_block = 1 }

                if (capturing && $0 ~ /^\}[[:space:]]*$/) {
                    if (in_block == 1) {
                        gsub(/configfile[[:space:]]+\(loop\)[^\n]*/, "configfile " newref, buf)
                    }
                    printf "%s", buf
                    capturing = 0; in_block = 0
                    next
                }
                if (!capturing) { print }
            }
            END { if (capturing) printf "%s", buf }
        ' "$CUSTOM_FILE" > "${CUSTOM_FILE}.tmp"

        if grep -qF "${dest_cfg_rel}" "${CUSTOM_FILE}.tmp"; then
            mv "${CUSTOM_FILE}.tmp" "$CUSTOM_FILE"
            success "40_custom を更新しました(パッチ済みメニューを参照するように変更)。"
            info "GRUB を更新中..."
            update-grub && success "update-grub 完了" || warn "update-grub でエラーが発生しました。手動確認してください。"
        else
            rm -f "${CUSTOM_FILE}.tmp"
            warn "自動書き換えに失敗しました。下記のエントリー例を手動で反映してください。"
            print_manual_entry_example "$dest_cfg_rel"
        fi
    else
        warn "この ISO を configfile 方式で登録している既存エントリーが見つかりませんでした。"
        info "以下のエントリー例を isoboot-grub.sh 側の該当エントリーと差し替えるか、"
        info "手動で ${CUSTOM_FILE} に追加してください。"
        print_manual_entry_example "$dest_cfg_rel"
    fi

    echo ""
    hr
    success "完了しました。"
    hr
}

print_manual_entry_example() {
    local dest_cfg_rel="$1"
    local insmod_str; insmod_str=$(fstype_to_grub_insmod "$PART_FSTYPE" "$PART_PTTYPE")
    local ins_fs="${insmod_str%%:*}" ins_part="${insmod_str#*:}"
    local insmod_lines=""
    local mod
    for mod in $ins_part; do insmod_lines+="    insmod ${mod}"$'\n'; done
    insmod_lines+="    insmod loopback"$'\n'
    insmod_lines+="    insmod iso9660"$'\n'
    insmod_lines+="    insmod ${ins_fs}"$'\n'
    insmod_lines+="    insmod regexp"

    echo ""
    echo -e "${CYAN}--------------------------------------------------------------${RESET}"
    cat <<EOF
menuentry "${ISO_NAME_NOEXT} (ISO Loop Boot - 起動メニュー表示 / パッチ済み)" {
${insmod_lines}
    search --no-floppy --fs-uuid --set=isodev ${PART_UUID}
    set isofile="${ISO_REL}"
    loopback loop (\$isodev)\$isofile
    set root=(loop)
    configfile (\$isodev)${dest_cfg_rel}
}
EOF
    echo -e "${CYAN}--------------------------------------------------------------${RESET}"
    echo ""
}

main "$@"
タイトルとURLをコピーしました