antiXが起動しなくなった時にインストールメディアを用意しなくても済むように、ドライブの後半にiso用のパーティションを用意し、そこから起動出来るようにします。

ホストにインストール
末尾にisoパーティションを作ったら、そこにantiXのISOイメージを保存し、下記スクリプトも保存して実行します。なお、スクリプト実行中の上記画面の場面で、起動メニューを表示させる場合には別途パッチを適用する必要があります。表示させないなら、このスクリプトだけで起動出来ますば、antiXの場合は起動メニューがあったほうが便利でしょう。
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 "$@"

起動メニューを表示させるためのパッチ
先述したように、下記のような起動メニューを表示させるにはパッチを当てる必要があります。

ISOイメージのあるパーティションで下記を実行します。
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 / loopback.cfg) をそのまま
# configfile で読み込みます。この設定ファイルがループブートに
# 対応していない場合、antiX の live-init が usb,cd デバイスしか
# 探索せず、ハードディスク上のISOを見つけられずに Fatal Error に
# なります。
#
# 対応方式(自動判定、2段階):
#
# 方式A: iso_path 変数方式
# antiX/MX Linux系の多くは、以下のような専用テンプレートを
# 同梱しています:
# export iso_path
# prefix=($root)/boot/grub
# source $prefix/grub.cfg
# これは「呼び出し元が iso_path 変数をセットしてから
# configfile すること」を前提にした設計で、本体の grub.cfg が
# $iso_path の有無を見てループブート用の起動コードを自動的に
# 付与してくれます。この場合、cfgファイル自体の書き換えは
# 一切不要で、40_custom 側の configfile 呼び出し直前に
# set iso_path="<ISOの相対パス>"
# export iso_path
# を追加するだけで解決します。
#
# 方式B: テキストパッチ方式(フォールバック)
# iso_path 変数に対応していないメニュー設定の場合、
# linux/linux16/linuxefi 行に直接 fromiso=/buuid=/persist と
# いった起動コードを追記したコピーを作成し、そちらを
# configfile で読み込むように変更します。
#
# このスクリプトの処理:
# 1. 対象のパーティション/ISOを選択
# 2. ISO内のメニュー設定ファイルを検査し、方式A/Bを自動判定
# 3. 40_custom 内に、この ISO を 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"
DEBUG_CFG_PATH="/root/isoboot-debug-loopback.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 mountpoint find sed awk 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}"
}
insmod_block() {
local insmod_str; insmod_str=$(fstype_to_grub_insmod "$PART_FSTYPE" "$PART_PTTYPE")
local ins_fs="${insmod_str%%:*}" ins_part="${insmod_str#*:}"
local lines="" mod
for mod in $ins_part; do lines+=" insmod ${mod}"$'\n'; done
lines+=" insmod loopback"$'\n'
lines+=" insmod iso9660"$'\n'
lines+=" insmod ${ins_fs}"$'\n'
lines+=" insmod regexp"
echo "$lines"
}
# ============================================================
# ISO内のメニュー設定ファイルのパスを探す
# ============================================================
find_iso_cfg_path() {
local tmp="$1" 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
}
# ============================================================
# 対象パーティション / 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)
[[ ${#iso_candidates[@]} -eq 0 ]] && die "ISO ファイルが見つかりませんでした。"
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}"
}
# ============================================================
# 40_custom 内で、この ISO を configfile 方式(実CD同等メニュー)で
# 登録しているエントリーを探す
# ============================================================
has_matching_custom_entry() {
[[ -f "$CUSTOM_FILE" ]] \
&& grep -qF "set isofile=\"${ISO_REL}\"" "$CUSTOM_FILE" \
&& grep -qE "configfile[[:space:]]+\(loop\)" "$CUSTOM_FILE"
}
# ============================================================
# 方式A: iso_path 変数方式
# configfile 呼び出し行の直前に
# set iso_path="<ISO_REL>"
# export iso_path
# を挿入する。cfg 自体は書き換えない。
# ============================================================
patch_via_iso_path() {
local cfg_rel="$1"
success "iso_path 変数方式のループブート対応 cfg を検出しました。"
info "cfg 自体の書き換えは不要です。configfile 呼び出し前に iso_path をセットします。"
echo ""
if has_matching_custom_entry; then
info "既存の 40_custom エントリーを検出しました。iso_path の設定を追加します。"
local bak; bak=$(backup_file "$CUSTOM_FILE")
success "バックアップ: ${bak}"
awk -v isofile="${ISO_REL}" -v isopath="${ISO_REL}" '
BEGIN { in_block = 0; already = 0 }
{
if ($0 ~ /^menuentry/) { in_block = 0; already = 0; buf = $0"\n"; capturing = 1 }
else if (capturing) { buf = buf $0 "\n" }
if (capturing && index($0, "set isofile=\"" isofile "\"") > 0) { in_block = 1 }
if (capturing && index($0, "set iso_path=") > 0) { already = 1 }
if (capturing && $0 ~ /^\}[[:space:]]*$/) {
if (in_block == 1 && already == 0) {
ins = " set iso_path=\"" isopath "\"\n export iso_path\n "
gsub(/configfile[[:space:]]+\(loop\)[^\n]*/, ins "&", buf)
}
printf "%s", buf
capturing = 0; in_block = 0; already = 0
next
}
if (!capturing) { print }
}
END { if (capturing) printf "%s", buf }
' "$CUSTOM_FILE" > "${CUSTOM_FILE}.tmp"
if grep -qF 'set iso_path=' "${CUSTOM_FILE}.tmp"; then
chmod --reference="$CUSTOM_FILE" "${CUSTOM_FILE}.tmp" 2>/dev/null || chmod 755 "${CUSTOM_FILE}.tmp"
mv "${CUSTOM_FILE}.tmp" "$CUSTOM_FILE"
success "40_custom を更新しました(iso_path を追加)。"
run_update_grub
else
rm -f "${CUSTOM_FILE}.tmp"
warn "自動書き換えに失敗しました。下記のエントリー例を手動で反映してください。"
print_entry_example_isopath "$cfg_rel"
fi
else
warn "この ISO を configfile 方式で登録している既存エントリーが見つかりませんでした。"
info "以下のエントリー例を isoboot-grub.sh 側の該当エントリーと差し替えるか、"
info "手動で ${CUSTOM_FILE} に追加してください。"
print_entry_example_isopath "$cfg_rel"
fi
}
# ============================================================
# 方式B: テキストパッチ方式(フォールバック)
# linux/linux16/linuxefi 行に fromiso=/buuid=/persist を追記した
# コピーを ISO と同じパーティション上に保存し、configfile の
# 参照先をそちらに書き換える。
# ============================================================
patch_via_text_patch() {
local cfg_rel="$1" work_cfg="$2"
local before_count after_count
before_count=$(grep -Eic '^[[:space:]]*linux(16|efi)?[[:space:]]+.*vmlinuz' "$work_cfg" || true)
if [[ "$before_count" -eq 0 ]]; then
cp "$work_cfg" "$DEBUG_CFG_PATH"
rm -f "$work_cfg"
error "パッチ対象となる起動行 (linux/linux16/linuxefi ... vmlinuz) が見つかりませんでした。"
echo ""
info "検出したメニュー設定ファイルの内容をそのまま表示します(この内容を教えてください):"
echo ""
hr_thin
cat -A "$DEBUG_CFG_PATH" | sed -n '1,80p'
hr_thin
echo ""
info "全文は ${DEBUG_CFG_PATH} に保存しました。"
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 -Fc "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}"
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}"
echo ""
if has_matching_custom_entry; then
info "既存の 40_custom エントリーを検出しました。configfile の参照先を書き換えます。"
local bak; bak=$(backup_file "$CUSTOM_FILE")
success "バックアップ: ${bak}"
awk -v isofile="${ISO_REL}" -v newref="(\$isodev)${dest_cfg_rel}" '
BEGIN { in_block = 0 }
{
if ($0 ~ /^menuentry/) { in_block = 0; buf = $0"\n"; 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
chmod --reference="$CUSTOM_FILE" "${CUSTOM_FILE}.tmp" 2>/dev/null || chmod 755 "${CUSTOM_FILE}.tmp"
mv "${CUSTOM_FILE}.tmp" "$CUSTOM_FILE"
success "40_custom を更新しました(パッチ済みメニューを参照するように変更)。"
run_update_grub
else
rm -f "${CUSTOM_FILE}.tmp"
warn "自動書き換えに失敗しました。下記のエントリー例を手動で反映してください。"
print_entry_example_textpatch "$dest_cfg_rel"
fi
else
warn "この ISO を configfile 方式で登録している既存エントリーが見つかりませんでした。"
info "以下のエントリー例を isoboot-grub.sh 側の該当エントリーと差し替えるか、"
info "手動で ${CUSTOM_FILE} に追加してください。"
print_entry_example_textpatch "$dest_cfg_rel"
fi
}
run_update_grub() {
if [[ -f "$CUSTOM_FILE" && ! -x "$CUSTOM_FILE" ]]; then
warn "${CUSTOM_FILE} に実行権限が無かったため付与します(無いと update-grub に無視されます)。"
chmod 755 "$CUSTOM_FILE"
fi
info "GRUB を更新中..."
if update-grub; then
success "update-grub 完了"
else
warn "update-grub でエラーが発生しました。手動で確認してください: sudo update-grub"
fi
}
print_entry_example_isopath() {
local cfg_rel="$1"
echo ""
echo -e "${CYAN}--------------------------------------------------------------${RESET}"
cat <<EOF
menuentry "${ISO_NAME_NOEXT} (ISO Loop Boot - 起動メニュー表示)" {
$(insmod_block)
search --no-floppy --fs-uuid --set=isodev ${PART_UUID}
set isofile="${ISO_REL}"
loopback loop (\$isodev)\$isofile
set root=(loop)
set iso_path="${ISO_REL}"
export iso_path
configfile (loop)${cfg_rel}
}
EOF
echo -e "${CYAN}--------------------------------------------------------------${RESET}"
echo ""
}
print_entry_example_textpatch() {
local dest_cfg_rel="$1"
echo ""
echo -e "${CYAN}--------------------------------------------------------------${RESET}"
cat <<EOF
menuentry "${ISO_NAME_NOEXT} (ISO Loop Boot - 起動メニュー表示 / パッチ済み)" {
$(insmod_block)
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() {
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"
echo ""
if grep -qw 'iso_path' "$work_cfg"; then
rm -f "$work_cfg"
patch_via_iso_path "$cfg_rel"
else
warn "iso_path 変数方式ではないようです。従来のテキストパッチ方式を試みます。"
patch_via_text_patch "$cfg_rel" "$work_cfg"
fi
echo ""
hr
success "完了しました。"
hr
}
main "$@"
後からISOパーティションを作った場合
nano mount-setup.sh
sudo bash mount-setup.sh
#!/bin/bash
# ============================================================
# 対話式 fstab マウント登録スクリプト
# - ディスク一覧を表示して番号で選択
# - 永続マウント(fstab 登録)か一時マウントかを選択
# - /iso ディレクトリがなければ作成・権限設定
# - UUID を /etc/fstab に追記してマウント(永続時のみ)
# ============================================================
set -euo pipefail
MOUNT_POINT="/iso"
FSTAB="/etc/fstab"
info() { echo -e "\033[1;32m[INFO]\033[0m $*"; }
warn() { echo -e "\033[1;33m[WARN]\033[0m $*"; }
error() { echo -e "\033[1;31m[ERROR]\033[0m $*" >&2; exit 1; }
confirm() {
local msg="$1"
local ans
read -rp "$(echo -e "\033[1;33m[確認]\033[0m $msg [y/N]: ")" ans
[[ "${ans,,}" == "y" ]]
}
# root 確認
[[ "$EUID" -ne 0 ]] && error "このスクリプトは sudo で実行してください。"
# ============================================================
# 1. パーティション一覧を表示
# ============================================================
echo ""
echo "======================================================"
echo " 現在のディスク・パーティション一覧"
echo "======================================================"
echo ""
lsblk -o NAME,SIZE,FSTYPE,LABEL,MOUNTPOINT,UUID | grep -v "^loop"
echo ""
# パーティションのみ抽出(ディスク本体除く)
mapfile -t PARTS < <(lsblk -lnpo NAME,SIZE,FSTYPE,MOUNTPOINT \
| awk '$3!="" && $1!~/^(loop|sr)/' \
| grep -v "^$")
if [[ ${#PARTS[@]} -eq 0 ]]; then
error "マウント可能なパーティションが見つかりませんでした。"
fi
echo "------------------------------------------------------"
printf " %-3s %-15s %-8s %-10s %s\n" "No." "デバイス" "サイズ" "FS種別" "現在のマウント先"
echo "------------------------------------------------------"
for i in "${!PARTS[@]}"; do
read -r dev size fstype mount <<< "${PARTS[$i]}"
mount="${mount:-(未マウント)}"
printf " %-3s %-15s %-8s %-10s %s\n" "$((i+1))" "$dev" "$size" "$fstype" "$mount"
done
echo "------------------------------------------------------"
echo ""
# ============================================================
# 2. パーティション選択
# ============================================================
while true; do
read -rp "$(echo -e "\033[1;36m[入力]\033[0m マウントするパーティションの番号を入力してください: ")" SEL
if [[ "$SEL" =~ ^[0-9]+$ ]] && (( SEL >= 1 && SEL <= ${#PARTS[@]} )); then
break
fi
warn "1〜${#PARTS[@]} の番号を入力してください。"
done
read -r SELECTED_DEV _ SELECTED_FS CURRENT_MOUNT <<< "${PARTS[$((SEL-1))]}"
CURRENT_MOUNT="${CURRENT_MOUNT:-(未マウント)}"
echo ""
info "選択: $SELECTED_DEV FS: $SELECTED_FS 現在: $CURRENT_MOUNT"
# すでに /iso にマウント済みなら確認
if [[ "$CURRENT_MOUNT" == "$MOUNT_POINT" ]]; then
warn "$SELECTED_DEV はすでに $MOUNT_POINT にマウントされています。"
confirm "再登録を続けますか?" || exit 0
fi
# 別の場所にマウント済みなら警告
if [[ "$CURRENT_MOUNT" != "(未マウント)" && "$CURRENT_MOUNT" != "$MOUNT_POINT" ]]; then
warn "$SELECTED_DEV は現在 $CURRENT_MOUNT にマウントされています。"
confirm "続けますか?(現在のマウントは変更しません)" || exit 0
fi
# ============================================================
# 3. マウント方式の選択
# ============================================================
echo ""
echo "======================================================"
echo " マウント方式を選択してください"
echo "======================================================"
echo ""
echo " 1) 永続マウント - fstab に登録し、再起動後も自動マウント"
echo " 2) 一時マウント - 今回のみマウント(再起動で解除)"
echo ""
MOUNT_MODE=""
while true; do
read -rp "$(echo -e "\033[1;36m[入力]\033[0m 番号を入力してください [1/2]: ")" MODE_SEL
case "$MODE_SEL" in
1) MOUNT_MODE="persistent"; break ;;
2) MOUNT_MODE="temporary"; break ;;
*) warn "1 または 2 を入力してください。" ;;
esac
done
echo ""
if [[ "$MOUNT_MODE" == "persistent" ]]; then
info "モード: 永続マウント(fstab 登録あり)"
else
info "モード: 一時マウント(fstab 登録なし)"
fi
# ============================================================
# 4. UUID 取得
# ============================================================
UUID=$(blkid -s UUID -o value "$SELECTED_DEV" 2>/dev/null || true)
if [[ -z "$UUID" ]]; then
error "$SELECTED_DEV の UUID を取得できませんでした。FS が認識されているか確認してください。"
fi
info "UUID: $UUID"
# FS タイプの決定(空なら auto)
FS_TYPE="${SELECTED_FS:-auto}"
# ntfs の場合は ntfs-3g に補正
[[ "$FS_TYPE" == "ntfs" ]] && FS_TYPE="ntfs-3g"
# ============================================================
# 5. /iso ディレクトリの準備
# ============================================================
if [[ ! -d "$MOUNT_POINT" ]]; then
info "$MOUNT_POINT が存在しないため作成します..."
mkdir -p "$MOUNT_POINT"
# 所有者を実行前のユーザーに設定
REAL_USER="${SUDO_USER:-root}"
chown "${REAL_USER}:${REAL_USER}" "$MOUNT_POINT"
chmod 775 "$MOUNT_POINT"
info "作成完了: $MOUNT_POINT(所有者: ${REAL_USER}, パーミッション: 775)"
else
info "$MOUNT_POINT はすでに存在します。"
fi
# ============================================================
# 6. fstab の重複チェックと追記(永続マウント時のみ)
# ============================================================
if [[ "$MOUNT_MODE" == "persistent" ]]; then
# 同じ UUID がすでに fstab にないか確認
if grep -q "$UUID" "$FSTAB"; then
warn "UUID=$UUID はすでに $FSTAB に登録されています。"
grep "$UUID" "$FSTAB"
confirm "上書き(既存行をコメントアウトして追記)しますか?" || exit 0
# 既存行をコメントアウト
sed -i "s|.*${UUID}.*|# & # mount-setup.sh により無効化 $(date '+%Y-%m-%d')|" "$FSTAB"
fi
# fstab に /iso のマウント先がすでにあるか確認
if grep -qP "^\s*[^#].*\s${MOUNT_POINT}\s" "$FSTAB"; then
warn "$MOUNT_POINT のエントリがすでに $FSTAB にあります。"
grep -P "^\s*[^#].*\s${MOUNT_POINT}\s" "$FSTAB"
confirm "既存行をコメントアウトして上書きしますか?" || exit 0
sed -i "s|^\([^#].*\s${MOUNT_POINT}\s.*\)|# \1 # mount-setup.sh により無効化 $(date '+%Y-%m-%d')|" "$FSTAB"
fi
FSTAB_LINE="UUID=${UUID} ${MOUNT_POINT} ${FS_TYPE} defaults 0 2"
echo ""
info "以下の行を $FSTAB に追記します:"
echo ""
echo " $FSTAB_LINE"
echo ""
confirm "追記しますか?" || exit 0
# バックアップ
cp "$FSTAB" "${FSTAB}.bak.$(date '+%Y%m%d%H%M%S')"
info "バックアップ: ${FSTAB}.bak.* を作成しました。"
echo "" >> "$FSTAB"
echo "# /iso - mount-setup.sh により追加 $(date '+%Y-%m-%d %H:%M:%S')" >> "$FSTAB"
echo "$FSTAB_LINE" >> "$FSTAB"
info "fstab への追記が完了しました。"
fi
# ============================================================
# 7. マウント実行
# ============================================================
echo ""
if [[ "$MOUNT_MODE" == "persistent" ]]; then
info "mount -a でマウントを実行します..."
if mount -a 2>&1; then
info "マウント成功!"
else
warn "mount -a でエラーが発生しました。fstab の内容を確認してください:"
tail -5 "$FSTAB"
exit 1
fi
else
info "mount -t ${FS_TYPE} ${SELECTED_DEV} ${MOUNT_POINT} でマウントを実行します..."
if mount -t "${FS_TYPE}" "${SELECTED_DEV}" "${MOUNT_POINT}" 2>&1; then
info "マウント成功!"
else
warn "マウントに失敗しました。デバイスと FS タイプを確認してください。"
exit 1
fi
fi
# ============================================================
# 8. 確認
# ============================================================
echo ""
echo "======================================================"
if [[ "$MOUNT_MODE" == "persistent" ]]; then
echo " 完了!(永続マウント)マウント状態:"
else
echo " 完了!(一時マウント・再起動で解除)マウント状態:"
fi
echo "======================================================"
lsblk -o NAME,SIZE,FSTYPE,LABEL,MOUNTPOINT | grep -E "NAME|$(basename "$SELECTED_DEV")"
echo ""
df -h "$MOUNT_POINT"
echo "======================================================"

