GRUBメニューに項目を追加してUbuntuのISOからブート

いざという時のために、ドライブにUbuntuのISOイメージを保存しておき、そのISOイメージから起動出来るようにしてみました。

後半パーティションにISOを保存

ディスク後半にext4パーティションを用意して、そこにUbuntuのISOイメージを保存します。

スクリプトでGRUBメニューに項目を追加

ISOイメージの準備ができたら、下記スクリプトを実行します。

nano isoboot-grub.sh
sudo bash isoboot-grub.sh
#!/usr/bin/env bash
# =============================================================================
# isoboot-grub.sh
# ISOブート用パーティション確認 → GRUB エントリ追加スクリプト
# 前提:ISOファイル入りのext4パーティションが既に存在すること
# =============================================================================

set -euo pipefail

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..70})${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 file grub-mkconfig update-grub; do
        command -v "$cmd" &>/dev/null || missing+=("$cmd")
    done
    [[ ${#missing[@]} -eq 0 ]] || die "コマンドが見つかりません: ${missing[*]}"
}

# -----------------------------------------------------------------------------
# ステップ1: ISOが入ったパーティションを選択
# -----------------------------------------------------------------------------
select_partition() {
    hr
    echo -e "${BOLD}【ステップ 1/3】ISO パーティションの選択${RESET}"
    hr
    echo ""
    info "現在のパーティション一覧:"
    echo ""
    lsblk -o NAME,SIZE,FSTYPE,LABEL,MOUNTPOINTS | grep -v loop
    echo ""

    while true; do
        echo -en "${BOLD}ISOが入ったパーティションを入力${RESET} (例: vda3, sda4, nvme0n1p3): /dev/"
        read -r part_name
        PART_DEV="/dev/${part_name}"

        if [[ ! -b "$PART_DEV" ]]; then
            error "${PART_DEV} はブロックデバイスではありません。再入力してください。"
            continue
        fi

        local fstype
        fstype=$(lsblk -no FSTYPE "$PART_DEV" 2>/dev/null || true)
        if [[ "$fstype" != "ext4" ]]; then
            warn "${PART_DEV} のファイルシステムは ${fstype:-不明} です(ext4 を想定)。"
            confirm "このまま続けますか?" || continue
        fi

        echo ""
        info "${PART_DEV} の情報:"
        lsblk -o NAME,SIZE,FSTYPE,LABEL,UUID,MOUNTPOINTS "$PART_DEV" 2>/dev/null || true
        echo ""
        confirm "${PART_DEV} を使用しますか?" && break
    done

    success "対象パーティション: ${PART_DEV}"

    # UUID取得(デバイス名非依存・VM/実機共通)
    PART_UUID=$(blkid -s UUID -o value "$PART_DEV")
    if [[ -z "$PART_UUID" ]]; then
        die "UUIDを取得できませんでした: ${PART_DEV}"
    fi
    success "UUID: ${PART_UUID}"
}

# -----------------------------------------------------------------------------
# ステップ2: ISOファイルの確認と起動パス検出
# -----------------------------------------------------------------------------
inspect_isos() {
    hr
    echo -e "${BOLD}【ステップ 2/3】ISO ファイルの確認${RESET}"
    hr
    echo ""

    MOUNT_POINT="/mnt/isoboot_grub_$$"
    mkdir -p "$MOUNT_POINT"
    mount "$PART_DEV" "$MOUNT_POINT"
    info "${PART_DEV} を ${MOUNT_POINT} にマウントしました"
    echo ""

    # ISOファイルを列挙
    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ファイルが見つかりませんでした。パーティションにISOを配置してから再実行してください。"
    fi

    info "見つかったISOファイル:"
    for i in "${!iso_candidates[@]}"; do
        local size
        size=$(du -sh "${iso_candidates[$i]}" 2>/dev/null | cut -f1)
        echo "  [$((i+1))] ${iso_candidates[$i]##"$MOUNT_POINT"}  (${size})"
    done
    echo ""

    ISO_ENTRIES=()  # "iso_path:vmlinuz:initrd:boot_type" の配列

    for iso_full in "${iso_candidates[@]}"; do
        local iso_rel="${iso_full##"$MOUNT_POINT"}"
        local iso_name
        iso_name=$(basename "$iso_full")
        echo ""
        info "--- ${iso_name} を検査中 ---"

        if confirm "${iso_name} をGRUBメニューに追加しますか?"; then
            local paths
            paths=$(detect_boot_paths "$iso_full")
            local vmlinuz="${paths%%:*}"
            local rest="${paths#*:}"
            local initrd="${rest%%:*}"
            local boot_type="${rest##*:}"

            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

            ISO_ENTRIES+=("${iso_rel}:${vmlinuz}:${initrd}:${boot_type}")
        else
            info "スキップ: ${iso_name}"
        fi
    done

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

    if [[ ${#ISO_ENTRIES[@]} -eq 0 ]]; then
        die "追加するエントリがありません。終了します。"
    fi
}

# ISOをループマウントして起動パスを自動検出
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
        echo "UNKNOWN:UNKNOWN:custom"
        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/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
        # フォールバック:vmlinuz を再帰検索
        local found
        found=$(find "$tmp" -name "vmlinuz*" | head -1 || true)
        if [[ -n "$found" ]]; then
            vmlinuz="${found##"$tmp"}"
            local dir
            dir=$(dirname "$found")
            initrd=$(find "$dir" -name "initrd*" | head -1 || true)
            initrd="${initrd##"$tmp"}"
            boot_type="custom"
        else
            vmlinuz="UNKNOWN"; initrd="UNKNOWN"; boot_type="custom"
        fi
    fi

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

# -----------------------------------------------------------------------------
# ステップ3: GRUBエントリ追加
# -----------------------------------------------------------------------------
add_grub_entries() {
    hr
    echo -e "${BOLD}【ステップ 3/3】GRUB エントリの追加${RESET}"
    hr
    echo ""

    local custom_file="/etc/grub.d/40_custom"
    cp "$custom_file" "${custom_file}.bak.$(date +%Y%m%d_%H%M%S)"
    info "40_custom をバックアップしました"
    echo ""

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

        local params=""
        case "$boot_type" in
            casper) params="boot=casper iso-scan/filename=\$isofile quiet splash ---" ;;
            live)   params="boot=live iso-scan/filename=\$isofile quiet splash" ;;
            *)      params="iso-scan/filename=\$isofile quiet splash" ;;
        esac

        local grub_entry
        grub_entry=$(cat <<EOF

menuentry "${menu_label} (ISO Loop Boot)" {
    insmod part_gpt
    insmod ext2
    insmod loopback
    insmod iso9660
    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
)
        echo -e "${CYAN}追加予定エントリ:${RESET}"
        echo "$grub_entry"
        echo ""

        if confirm "このエントリを追加しますか?"; then
            echo "$grub_entry" >> "$custom_file"
            success "追加しました: ${menu_label}"
        else
            warn "スキップ: ${menu_label}"
        fi
    done

    echo ""
    info "GRUB を更新中..."
    if update-grub 2>&1; then
        success "update-grub 完了"
    else
        warn "update-grub でエラーが発生しました。手動で確認してください:"
        warn "  sudo update-grub"
    fi
}

# -----------------------------------------------------------------------------
# 完了サマリー
# -----------------------------------------------------------------------------
print_summary() {
    hr
    echo -e "${BOLD}${GREEN}【完了】セットアップサマリー${RESET}"
    hr
    echo ""
    echo -e "  対象パーティション : ${BOLD}${PART_DEV}${RESET}"
    echo -e "  パーティションUUID : ${BOLD}${PART_UUID}${RESET}"
    echo -e "  追加エントリ数     : ${BOLD}${#ISO_ENTRIES[@]}${RESET}"
    echo ""
    echo -e "${YELLOW}【次のステップ】${RESET}"
    echo "  1. 再起動して GRUB メニューに新しいエントリが表示されることを確認"
    echo "  2. 起動に失敗した場合は /etc/grub.d/40_custom を確認し"
    echo "     カーネルパスを修正後 sudo update-grub を再実行"
    echo ""
    warn "バックアップ: /etc/grub.d/40_custom.bak.* に保存済み"
    hr
}

# -----------------------------------------------------------------------------
main() {
    clear
    hr
    echo -e "${BOLD}  isoboot-grub.sh — ISO ブート GRUB エントリ 追加スクリプト${RESET}"
    echo -e "  前提: ISOファイル入りのext4パーティションが既に存在すること"
    hr
    echo ""
    check_root
    check_deps
    select_partition
    inspect_isos
    add_grub_entries
    print_summary
}

main "$@"

GRUBメニューでISOイメージを選択して起動

再起動してGRUBメニューを表示します。UEFI環境ならEscキー、BIOSならShiftキーをタイミングよく押します。

ちなみに、生成されたGRUBエントリは次のようになっています。

menuentry "Ubuntu 26.04 Live ISO (ISO Loop Boot)" {
insmod part_gpt
insmod ext2
insmod loopback
insmod iso9660
search --no-floppy --fs-uuid --set=isodev b8be07b9-xxxx-xxxx-xxxx-xxxxxxxxxxxx
set isofile="/ubuntu-26.04-desktop-amd64.iso"
loopback loop ($isodev)$isofile
linux (loop)/casper/vmlinuz boot=casper iso-scan/filename=$isofile quiet splash ---
initrd (loop)/casper/initrd
}

gpartedでパーティション編集

パーティション編集用ににgpartedをインストールするなら下記。

sudo apt update
sudo apt install -y gparted

GRUBエントリの場所

GRUBエントリは複数のファイルに関係しています。

編集用(ここを変更する)

/etc/grub.d/40_custom

今回追記したカスタムエントリの保存場所。update-grub を実行するたびにここが読み込まれます。

sudo nano /etc/grub.d/40_custom

実際に起動時に使われる(自動生成・直接編集不可)

/boot/grub/grub.cfg

update-grub/etc/grub.d/ 以下の全ファイルを合成して自動生成するファイル。直接編集しても次の update-grub で上書きされます。

確認するなら

grep -A 12 "ISO Loop Boot" /boot/grub/grub.cfg

関係するファイルの全体像

/etc/default/grub        ← タイムアウトやデフォルト項目などの設定
/etc/grub.d/
  ├── 00_header          ← GRUB基本設定(自動)
  ├── 10_linux           ← インストール済みLinuxの検出(自動)
  ├── 40_custom          ← カスタムエントリ ← ここを編集
  └── ...
        ↓ update-grub
/boot/grub/grub.cfg      ← 起動時に実際に読まれる(自動生成)

つまり 編集は 40_custom、反映は update-grub が基本の流れです。

タイトルとURLをコピーしました