UbuntuをBtrfsパーティションにインストール

個人用途で、ホストを極力汚さず、メンテナンス性と安全性を両立させるには、LXDを基盤としてVMやDocker(LXDコンテナ内)を運用する「ネスト構造」が便利です。そして、ホストOSの環境は「Timeshift」を導入しておけば、アップデートの失敗などによってOSが起動しなくなった時の復旧にも対応しやすくなります。

構成のイメージ

1. ホストOSの構成

  • ファイルシステム: Btrfs
  • 理由:
    • Timeshift との相性が抜群で、ホストの設定(LXDのインストール状態など)をいつでもロールバックできる。
    • ホスト側で透過圧縮(zstd)を効かせれば、LXDのイメージや仮想ディスクの容量を節約できる。

2. LXDのストレージプール構成

LXDのデフォルトプールには 「Btrfsバックエンド」 を推奨。

  • バックエンド: Btrfs(ホストのBtrfs上にサブボリュームとして作成)
  • 理由:
    • lxc copylxc snapshot が一瞬で終わる(CoWの恩恵)。
    • VM(仮想マシン) を作成した際、内部で自動的に nocow 設定が適用されるため、断片化のリスクが抑えられる。
    • ZFSよりもメモリ消費が少なく、個人用PCの500GB SSDで運用するにはリソース管理が楽。

3. LXD内でのDocker(Nextcloud/Immich)運用

LXDコンテナの中でDockerを動かす場合、以下の設定で「ホストを汚さない&高速」を実現します。

  • コンテナの種別: 特権なし(Unprivileged)コンテナ(セキュリティのため)
  • Dockerストレージドライバ: overlay2
    • LXDがBtrfsプールなら、コンテナ内のDockerはデフォルトで vfs(激重)や btrfs になろうとしますが、これを overlay2 に強制設定します。
  • データの逃がし先(NOCOWの適用):
    1. ホスト側に sudo mkdir /var/lib/lxd-data && sudo chattr +C /var/lib/lxd-dataNOCOW領域 を作成。
    2. NextcloudやImmichのDB用ディレクトリだけ、ホストのこの領域を lxc config device add でコンテナにパススルー(ディスクマウント)する。
    3. 写真データは、NOCOWではない「通常のBtrfs領域」をマウントして、チェックサムによる保護を受ける。

最適な構成まとめ表

階層種類 / 設定役割
ホストOSUbuntu (Btrfs)Timeshiftで土台を保護
LXDプールBtrfs (loopバックではない実体)高速なクローンとスナップショット
LXDコンテナUbuntuコンテナDockerを動かす「器」
Dockerドライバoverlay2コンテナ内の動作を標準化・高速化
DBデータHostのNOCOW領域をマウントPostgreSQL等の断片化防止
写真データHostの通常Btrfs領域をマウントデータの腐敗防止(チェックサム)

各ボリュームの構成

Ubuntuをインストールする際、パーティション設定で Btrfsを選び、インストール直後にTimeshift を設定するという流れになります。ホストのUbuntuをBtrfsでインストールしTimeshiftで保護、サブボリュームとしてLXDプールを作成、そのサブボリュームとしてホストとLXD内の共通してアクセス出来るlxd-dataボリュームを作成。Ubuntu(ホスト)を壊しても、LXD内のコンテナや大事なデータ(Nextcloudの写真など)は無傷で残せます。

  1. サブボリュームの階層設計は、Btrfsのサブボリュームを「入れ子」にせず、並列(フラット)に配置するのが安全です。これにより、Timeshiftが @(ルート)を書き換えても他が巻き込まれません。
    @ (ルート): / にマウント。Timeshiftの保護対象。
    @home: /home にマウント。
    @lxd_pool: /var/lib/lxd/storage-pools/default 等にマウント。(Timeshift対象外)
    @lxd_data: /mnt/lxd-data 等にマウント。ホスト・LXD共通。(Timeshift対象外)
  2. インストールと設定の手順
    ① Ubuntuのインストール インストーラーで「Btrfs」を選択してインストール。
    ② 追加サブボリュームの作成(インストール後) LXDを入れる前に、手動で独立したサブボリュームを作成します。

Ubuntuセットアップ

セットアップ時に手動パーティションにして、Btrfsを選択。マウントポイントは「/」にします。EFIパーティションなどは自動で作成されますsが、それだと順序が逆になるので、先に1GB程度のEFIパーティションを作成し、そのあとBtrfsパーティションを作成します。

なお、KubuntuやFedora、openSUSEなどが採用しているインストーラー(Calamaresなど)は、Btrfsの高度な機能を活用する設計になっており、インストール時にBtrfsを指定すれば自動で@や@homeが作成されますが、Ubuntuの現在のバージョンのインストーラでは@や@homeが自動作成されないので、セットアップ後に修正が必要になります。

Ubuntu起動後

Ubuntuが起動したら、ひとまずマウント状況を確認します。

mount | grep btrfs

Ubuntu24.04以降では自動で@、@homeを作成しないため、下記のように、/や/homeのままとなっているはずです。

/dev/vda2 on / type btrfs (rw,relatime,discard=async,space_cache=v2,subvolid=5,subvol=/)  # subvol=/@のようになっていない。

ホームフォルダ内に下記スクリプトを保存しておきます。

nano btrfs_migrate.sh
#!/bin/bash
# ============================================================
#  Btrfs サブボリューム移行スクリプト(環境自動取得版)
#  実行環境: ライブUSB環境のターミナル
#  構成想定: 1ドライブ / ブートパーティション + Btrfsパーティション の2構成
#           EFI(UEFI)/ BIOS(Legacy)両対応
#  使い方  : sudo bash btrfs_migrate.sh
# ============================================================

set -euo pipefail

RED='\033[0;31m'; YELLOW='\033[1;33m'; GREEN='\033[0;32m'; CYAN='\033[0;36m'; NC='\033[0m'
info()  { echo -e "${GREEN}[INFO]${NC}  $*"; }
warn()  { echo -e "${YELLOW}[WARN]${NC}  $*"; }
error() { echo -e "${RED}[ERROR]${NC} $*"; exit 1; }
step()  { echo -e "\n${CYAN}=== $* ===${NC}"; }

# root確認
[[ $EUID -ne 0 ]] && error "sudo で実行してください"

# ============================================================
# 起動モード判定(EFI / BIOS)
# ============================================================
step "環境を自動検出"

if [[ -d /sys/firmware/efi ]]; then
    BOOT_MODE="EFI"
else
    BOOT_MODE="BIOS"
fi
info "起動モード: $BOOT_MODE"

# --- Btrfsパーティション検出 ---
BTRFS_PART=$(lsblk -lnpo NAME,FSTYPE | awk '$2=="btrfs"{print $1}' | head -1)
[[ -n "$BTRFS_PART" ]] || error "Btrfsパーティションが見つかりませんでした"

# --- ディスク本体(パーティション番号を除いたもの)---
DISK=$(lsblk -lnpo PKNAME "$BTRFS_PART" | head -1)
[[ -n "$DISK" ]] || DISK=$(echo "$BTRFS_PART" | sed 's/p\?[0-9]*$//')
DISK="/dev/${DISK##*/}"

# --- EFI / BIOS それぞれのブートパーティション検出 ---
EFI_PART=""
BIOS_BOOT_PART=""

if [[ "$BOOT_MODE" == "EFI" ]]; then
    # EFI System Partition: PARTTYPE UUID で検出、なければ vfat で代替
    EFI_PART=$(lsblk -lnpo NAME,PARTTYPE | \
        awk 'tolower($2) == "c12a7328-f81f-11d2-ba4b-00a0c93ec93b" {print $1}' | head -1)
    if [[ -z "$EFI_PART" ]]; then
        EFI_PART=$(lsblk -lnpo NAME,FSTYPE | awk '$2=="vfat"{print $1}' | head -1)
    fi
    [[ -n "$EFI_PART" ]] || error "EFIパーティションが見つかりませんでした(vfat / EFI System)"
else
    # BIOS Boot Partition: PARTTYPE UUID 21686148... で検出
    BIOS_BOOT_PART=$(lsblk -lnpo NAME,PARTTYPE | \
        awk 'tolower($2) == "21686148-6449-6e6f-744e-656564454649" {print $1}' | head -1)
    # 見つからなければ警告のみ(MBRディスクでは不要な場合もある)
    if [[ -z "$BIOS_BOOT_PART" ]]; then
        warn "BIOS Bootパーティション(21686148...)が見つかりません。MBRディスクの場合は問題ありません。"
    else
        info "BIOS Bootパーティション: $BIOS_BOOT_PART"
    fi
fi

# --- 結果表示 ---
echo ""
echo "  検出結果:"
echo "  ┌──────────────────────────────────────────┐"
printf "  │  起動モード          : %-21s│\n" "$BOOT_MODE"
printf "  │  Btrfsパーティション : %-21s│\n" "$BTRFS_PART"
if [[ "$BOOT_MODE" == "EFI" ]]; then
    printf "  │  EFIパーティション   : %-21s│\n" "$EFI_PART"
else
    printf "  │  BIOS Bootパーティション: %-18s│\n" "${BIOS_BOOT_PART:-(なし / MBR)}"
fi
printf "  │  ディスク            : %-21s│\n" "$DISK"
echo "  └──────────────────────────────────────────┘"
echo ""

info "ディスク構成(参考):"
lsblk -o NAME,SIZE,FSTYPE,PARTTYPE,MOUNTPOINT "$DISK"
echo ""

warn "上記の検出結果で続行しますか? [yes/N]"
read -r answer
[[ "$answer" == "yes" ]] || { info "中止しました。"; exit 0; }

# ============================================================
# ステップ1: マウント
# ============================================================
step "Step 1: Btrfsパーティションをマウント"
# 実機ではライブUSB起動時に対象パーティションが自動マウントされている場合がある
# /mnt と /boot/efi 以外のマウントポイントは除外し、安全なものだけ解除
info "対象パーティションの既存マウントを解除"
for mp in $(findmnt -rno TARGET --source "$BTRFS_PART" 2>/dev/null | sort -r); do
    # / は絶対に触らない
    [[ "$mp" == "/" ]] && continue
    info "  既存マウント解除: $mp"
    umount "$mp" 2>/dev/null || umount -l "$mp" 2>/dev/null || true
done
if [[ -n "${EFI_PART:-}" ]]; then
    for mp in $(findmnt -rno TARGET --source "$EFI_PART" 2>/dev/null | sort -r); do
        [[ "$mp" == "/" ]] && continue
        info "  既存マウント解除 (EFI): $mp"
        umount "$mp" 2>/dev/null || umount -l "$mp" 2>/dev/null || true
    done
fi
mkdir -p /mnt
mount "$BTRFS_PART" /mnt

# サブボリューム二重実行チェック
if btrfs subvolume list /mnt 2>/dev/null | grep -qE '\s@$|\s@home$'; then
    error "サブボリューム @ または @home がすでに存在します。移行済みの可能性があります。"
fi

# ============================================================
# ステップ2: サブボリューム作成・データ移動
# ============================================================
step "Step 2: サブボリューム作成・データ移動"
cd /mnt

info "スナップショット @ を作成"
btrfs subvolume snapshot . @

info "サブボリューム @home を作成"
btrfs subvolume create @home

if [[ -d /mnt/home ]] && compgen -G "/mnt/home/*" > /dev/null 2>&1; then
    info "home/* を @home/ へコピー"
    cp -a /mnt/home/. /mnt/@home/
else
    warn "home/ が空またはありません。スキップします。"
fi

info "フラットなルートデータを削除(@ へコピー済み)"
# 実機でマウント済みのパーティション(/boot/efi など)を先にアンマウント
for entry in /mnt/*; do
    name=$(basename "$entry")
    [[ "$name" == "@" || "$name" == "@home" ]] && continue
    # マウント中のディレクトリは先にアンマウント(ビジーエラー対策)
    if mountpoint -q "$entry" 2>/dev/null; then
        info "  アンマウント中: $entry"
        umount -R "$entry" 2>/dev/null || umount -l "$entry" 2>/dev/null || true
    fi
    rm -rf "$entry"
    info "  削除: $entry"
done

# ============================================================
# ステップ3: fstab 書き換え
# ============================================================
step "Step 3: fstab を更新"
FSTAB="/mnt/@/etc/fstab"
[[ -f "$FSTAB" ]] || error "fstab が見つかりません: $FSTAB"

UUID=$(blkid -s UUID -o value "$BTRFS_PART")
[[ -n "$UUID" ]] || error "UUIDの取得に失敗しました"
info "UUID: $UUID"

cp "$FSTAB" "${FSTAB}.bak"
info "バックアップ: ${FSTAB}.bak"

python3 - "$FSTAB" "$UUID" << 'PYEOF'
import sys, re

fstab_path = sys.argv[1]
uuid        = sys.argv[2]

with open(fstab_path) as f:
    lines = f.readlines()

new_lines = []
has_home  = False

for line in lines:
    stripped = line.strip()
    if stripped.startswith("#") or stripped == "":
        new_lines.append(line)
        continue
    cols = stripped.split()
    if len(cols) < 4:
        new_lines.append(line)
        continue

    mountpoint = cols[1]
    fstype     = cols[2]

    if fstype == "btrfs" and mountpoint == "/":
        opts = re.sub(r'subvol=[^,\s]+,?', '', cols[3]).strip(',')
        opts = (opts + ',subvol=@') if opts else 'defaults,subvol=@'
        # スペース区切りで1行に収める(タブによる折り返し防止)
        new_lines.append(f"UUID={uuid} / btrfs {opts} 0 0\n")
    elif fstype == "btrfs" and mountpoint == "/home":
        opts = re.sub(r'subvol=[^,\s]+,?', '', cols[3]).strip(',')
        opts = (opts + ',subvol=@home') if opts else 'defaults,subvol=@home'
        new_lines.append(f"UUID={uuid} /home btrfs {opts} 0 0\n")
        has_home = True
    else:
        new_lines.append(line)

if not has_home:
    new_lines.append(f"UUID={uuid} /home btrfs defaults,subvol=@home 0 0\n")

with open(fstab_path, 'w') as f:
    f.writelines(new_lines)
PYEOF

info "更新後の fstab:"
cat "$FSTAB"

# ============================================================
# ステップ4: chroot して GRUB 更新
# ============================================================
step "Step 4: chroot して GRUB を更新"

# --- Step 4a: subvol=@ を明示した専用マウントポイントを用意 ---
# /mnt はフラットマウントのまま残し、別に /mnt2 へ subvol=@ で再マウント
# → /proc/mounts に「デバイス → / (subvol=@)」が正しく登録される
mkdir -p /mnt2
mount -t btrfs -o subvol=@ "$BTRFS_PART" /mnt2
info "subvol=@ で /mnt2 に再マウント完了"

# /boot/grub ディレクトリが存在することを確認・作成
mkdir -p /mnt2/boot/grub

# --- Step 4b: 仮想FSを rbind+rslave でバインド ---
mount --rbind /dev  /mnt2/dev  && mount --make-rslave /mnt2/dev
mount --rbind /proc /mnt2/proc && mount --make-rslave /mnt2/proc
mount --rbind /sys  /mnt2/sys  && mount --make-rslave /mnt2/sys
mount --rbind /run  /mnt2/run  && mount --make-rslave /mnt2/run

if [[ "$BOOT_MODE" == "EFI" ]]; then
    mount --bind /sys/firmware/efi/efivars /mnt2/sys/firmware/efi/efivars 2>/dev/null || \
        warn "efivars のバインドに失敗しました"
    mkdir -p /mnt2/boot/efi
    mount "$EFI_PART" /mnt2/boot/efi
    chroot /mnt2 /bin/bash -e << CHROOT
echo '[chroot] grub-install (EFI) ...'
grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=ubuntu --recheck
echo '[chroot] grub-mkconfig ...'
grub-mkconfig -o /boot/grub/grub.cfg
echo '[chroot] 完了'
CHROOT
else
    chroot /mnt2 /bin/bash -e << CHROOT
echo '[chroot] grub-install (BIOS) ...'
grub-install --target=i386-pc --recheck $DISK
echo '[chroot] grub-mkconfig ...'
grub-mkconfig -o /boot/grub/grub.cfg
echo '[chroot] 完了'
CHROOT
fi

# ============================================================
# ステップ5: アンマウント
# ============================================================
step "Step 5: アンマウント"
umount -R /mnt2 2>/dev/null || true
umount -R /mnt  2>/dev/null || true

echo ""
echo -e "${GREEN}======================================================"
echo "  移行が完了しました!"
echo "  再起動後に以下で確認してください:"
echo "    sudo btrfs subvolume list /"
echo -e "======================================================${NC}"
echo ""
warn "再起動しますか? [yes/N]"
read -r reboot_answer
[[ "$reboot_answer" == "yes" ]] && reboot || info "手動で再起動してください: sudo reboot"

ライブUSBで起動するために再起動します。

sudo reboot

ライブUSBで起動してスクリプトを実行

次に、インストール時に使用したUbuntuのライブUSBで起動します。今度は「Ubuntuを試す」を選択し、マウントしたドライブでhomeフォルダまで辿り、ターミナルを開き、保存しておいたスクリプトを実行します。
失敗すれば起動しなくなります。それでも構わないような、インストール直後に実施を。あくまでも自己責任で!

sudo bash btrfs_migrate.sh

無事終了したら指示に従い再起動します。

変換後に再度状況を確認

再起動で無事起動したら、先ほどのコマンドを再度実行します。

mount | grep btrfs

下記のような内容なら成功です。

/dev/vda1 on / type btrfs (rw,relatime,discard=async,space_cache=v2,subvolid=256,subvol=/@)
/dev/vda1 on /home type btrfs (rw,relatime,discard=async,space_cache=v2,subvolid=257,subvol=/@home)

管理者権限でBtrfsのサブボリュームの一覧を表示すれば確実です。

sudo btrfs subvolume list /

成功していればこのような表示になります。

ID 256 gen 123 top level 5 path @
ID 257 gen 111 top level 5 path @home

最初から

下記はテスト中。いまいち上手くいかないので中止予定。

nano ubuntu-limine-btrfs-install.sh
sudo bash ubuntu-limine-btrfs-install.sh
#!/usr/bin/env bash
# Ubuntu + Limine + Btrfs 対話型インストーラー
# 使い方: sudo bash ubuntu-limine-btrfs-install.sh

set -euo pipefail

# ─── 色定義 ───────────────────────────────────────────────
RED='\033[0;31m'; YELLOW='\033[1;33m'; GREEN='\033[0;32m'
CYAN='\033[0;36m'; BOLD='\033[1m'; RESET='\033[0m'

info()  { echo -e "${CYAN}[INFO]${RESET}  $*"; }
ok()    { echo -e "${GREEN}[OK]${RESET}    $*"; }
warn()  { echo -e "${YELLOW}[WARN]${RESET}  $*"; }
error() { echo -e "${RED}[ERROR]${RESET} $*"; exit 1; }
hdr()   { echo -e "\n${BOLD}━━━ $* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"; }
ask()   { echo -e "${YELLOW}[?]${RESET}    $*"; }

# ─── root確認 ─────────────────────────────────────────────
[[ $EUID -ne 0 ]] && error "このスクリプトは sudo で実行してください。"

# ─── UEFI確認 ─────────────────────────────────────────────
hdr "環境確認"
if [[ -d /sys/firmware/efi/efivars ]]; then
    ok "UEFI環境を確認しました"
else
    error "UEFIが検出できません。EFIモードで起動しているか確認してください。"
fi

# ─── ディスク選択 ──────────────────────────────────────────
hdr "① インストール先ディスクの選択"
echo ""

mapfile -t DISKS < <(lsblk -dn -o NAME,SIZE,TYPE,MODEL 2>/dev/null \
    | awk '$3=="disk" {print $0}' \
    | grep -E '^(sd|nvme|vd)')

if [[ ${#DISKS[@]} -eq 0 ]]; then
    error "ディスクが見つかりません。lsblk で確認してください。"
fi

echo -e "  ${BOLD}No.  デバイス        サイズ  モデル${RESET}"
echo    "  ─────────────────────────────────────────────"
for i in "${!DISKS[@]}"; do
    NAME=$(echo "${DISKS[$i]}"  | awk '{print $1}')
    SIZE=$(echo "${DISKS[$i]}"  | awk '{print $2}')
    MODEL=$(echo "${DISKS[$i]}" | awk '{$1=$2=$3=""; print $0}' | sed 's/^ *//')
    printf "  %2d.  /dev/%-10s  %6s  %s\n" "$((i+1))" "$NAME" "$SIZE" "$MODEL"
done
echo ""

while true; do
    ask "番号を入力してください (1-${#DISKS[@]}):"
    read -r SEL
    if [[ "$SEL" =~ ^[0-9]+$ ]] && (( SEL >= 1 && SEL <= ${#DISKS[@]} )); then
        DISK_NAME=$(echo "${DISKS[$((SEL-1))]}" | awk '{print $1}')
        DISK="/dev/${DISK_NAME}"
        break
    fi
    warn "無効な番号です。再入力してください。"
done

ok "選択されたディスク: ${BOLD}${DISK}${RESET}"

# nvme/mmcblk はパーティション名が p1, p2
if [[ "$DISK" =~ nvme|mmcblk ]]; then
    ESP="${DISK}p1"
    ROOT="${DISK}p2"
else
    ESP="${DISK}1"
    ROOT="${DISK}2"
fi

echo ""
echo -e "  作成されるパーティション構成:"
echo -e "  ${GREEN}${ESP}${RESET}  →  512MB  FAT32  (EFI System Partition)"
echo -e "  ${GREEN}${ROOT}${RESET}  →  残り全域  Btrfs  (ルート)"
echo ""

warn "⚠ ${DISK} の全データが消去されます!"
ask "本当に続行しますか?ディスク名を入力して確認してください (${DISK_NAME} と入力):"
read -r CONFIRM
[[ "$CONFIRM" == "$DISK_NAME" ]] || error "確認入力が一致しません。中止します。"

# ─── Ubuntuバージョン選択 ──────────────────────────────────
hdr "② Ubuntu バージョン選択"
echo ""
echo "  1.  noble      — Ubuntu 24.04 LTS (Noble Numbat)      ← 推奨・安定"
echo "  2.  resolute   — Ubuntu 26.04 LTS (Resolute Raccoon)  ← 最新・debootstrap未対応時はnobleで代替"
echo "  3.  jammy      — Ubuntu 22.04 LTS (Jammy Jellyfish)"
echo "  4.  focal      — Ubuntu 20.04 LTS (Focal Fossa)"
echo ""
ask "番号を入力してください [1]:"
read -r VSEL
case "$VSEL" in
    2) DISTRO="resolute" ;;
    3) DISTRO="jammy" ;;
    4) DISTRO="focal" ;;
    *) DISTRO="noble" ;;
esac

# resolute (26.04) は debootstrap スクリプトが存在しない可能性があるため確認
if [[ "$DISTRO" == "resolute" ]]; then
    if [[ ! -f /usr/share/debootstrap/scripts/resolute ]]; then
        warn "debootstrap に resolute スクリプトが見つかりません。noble (24.04) にフォールバックします。"
        DISTRO="noble"
        ok "Ubuntu バージョンを noble (24.04) に変更しました"
    else
        ok "Ubuntu バージョン: ${BOLD}resolute (26.04)${RESET}"
    fi
else
    ok "Ubuntu バージョン: ${BOLD}${DISTRO}${RESET}"
fi

# ─── ホスト名・ユーザー名 ──────────────────────────────────
hdr "③ システム設定"
echo ""
ask "ホスト名を入力してください [ubuntu-limine]:"
read -r INPUT_HOST
HOSTNAME_="${INPUT_HOST:-ubuntu-limine}"
ok "ホスト名: ${HOSTNAME_}"

ask "ユーザー名を入力してください [ubuntu]:"
read -r INPUT_USER
USERNAME="${INPUT_USER:-ubuntu}"
ok "ユーザー名: ${USERNAME}"

# ─── 確認サマリー ──────────────────────────────────────────
hdr "④ 実行確認"
echo ""
echo -e "  ディスク    : ${BOLD}${DISK}${RESET}"
echo -e "  ESP         : ${ESP}  (512MB / FAT32)"
echo -e "  Root        : ${ROOT}  (残り全域 / Btrfs)"
echo -e "  Ubuntu      : ${DISTRO}"
echo -e "  ホスト名    : ${HOSTNAME_}"
echo -e "  ユーザー名  : ${USERNAME}"
echo ""
ask "上記の内容でインストールを開始しますか? [y/N]:"
read -r FINAL
[[ "$FINAL" =~ ^[yY]$ ]] || { info "中止しました。"; exit 0; }

# ════════════════════════════════════════════════════════════
# ─── ここから実際の処理 ─────────────────────────────────────
# ════════════════════════════════════════════════════════════

hdr "⑤ パーティション作成"
info "既存のパーティションテーブルを削除中..."
wipefs -a "$DISK"
sgdisk --zap-all "$DISK"

info "GPTパーティションテーブルを作成中..."
sgdisk -n 1:0:+512M -t 1:ef00 -c 1:"EFI System" \
       -n 2:0:0     -t 2:8300 -c 2:"Linux Btrfs" \
       "$DISK"

partprobe "$DISK"
sleep 2
ok "パーティション作成完了"
lsblk "$DISK"

# ─── フォーマット ──────────────────────────────────────────
hdr "⑥ フォーマット"
info "ESPをFAT32でフォーマット中..."
mkfs.vfat -F32 -n EFI "$ESP"
ok "ESP フォーマット完了: ${ESP}"

info "BtrfsでRootをフォーマット中..."
mkfs.btrfs -f -L ubuntu-root "$ROOT"
ok "Btrfs フォーマット完了: ${ROOT}"

# ─── Btrfsサブボリューム ───────────────────────────────────
hdr "⑦ Btrfsサブボリューム作成"
mount "$ROOT" /mnt
btrfs subvolume create /mnt/@
btrfs subvolume create /mnt/@home
btrfs subvolume create /mnt/@snapshots
umount /mnt
ok "サブボリューム @  @home  @snapshots 作成完了"

info "サブボリュームをマウント中..."
BTRFS_OPTS="defaults,noatime,compress=zstd"
mount -o "${BTRFS_OPTS},subvol=@"          "$ROOT" /mnt
mkdir -p /mnt/{home,boot/efi,.snapshots}
mount -o "${BTRFS_OPTS},subvol=@home"      "$ROOT" /mnt/home
mount -o "${BTRFS_OPTS},subvol=@snapshots" "$ROOT" /mnt/.snapshots
mount "$ESP" /mnt/boot/efi
ok "マウント完了"

# ─── debootstrap ──────────────────────────────────────────
hdr "⑧ debootstrap (Ubuntu ${DISTRO})"
if ! command -v debootstrap &>/dev/null; then
    info "debootstrap をインストール中..."
    apt-get install -y debootstrap
fi

info "Ubuntuベースシステムを展開中 (数分かかります)..."
debootstrap --arch=amd64 "$DISTRO" /mnt https://archive.ubuntu.com/ubuntu
ok "debootstrap 完了"

# ─── fstab生成 ────────────────────────────────────────────
hdr "⑨ fstab生成"
ROOT_UUID=$(blkid "$ROOT" -s UUID -o value)
ESP_UUID=$(blkid "$ESP"   -s UUID -o value)

{
    echo "# <file system>  <mount>  <type>  <options>  <dump>  <pass>"
    echo "UUID=${ROOT_UUID}  /            btrfs  ${BTRFS_OPTS},subvol=@           0  0"
    echo "UUID=${ROOT_UUID}  /home        btrfs  ${BTRFS_OPTS},subvol=@home       0  0"
    echo "UUID=${ROOT_UUID}  /.snapshots  btrfs  ${BTRFS_OPTS},subvol=@snapshots  0  0"
    echo "UUID=${ESP_UUID}   /boot/efi    vfat   defaults,umask=0077              0  1"
} > /mnt/etc/fstab
ok "fstab 生成完了"
cat /mnt/etc/fstab

# ─── chroot準備 ───────────────────────────────────────────
hdr "⑩ chroot環境準備"
for d in dev proc sys run; do
    mount --bind "/$d" "/mnt/$d"
done

# efivarfs を明示的にマウント(efibootmgr に必要)
mkdir -p /mnt/sys/firmware/efi/efivars
mount --bind /sys/firmware/efi/efivars /mnt/sys/firmware/efi/efivars
ok "efivarfs マウント完了"

info "DNS設定をchroot環境へ書き込み中..."
if [[ -f /run/systemd/resolve/resolv.conf ]]; then
    cp /run/systemd/resolve/resolv.conf /mnt/etc/resolv.conf
elif [[ -f /etc/resolv.conf ]]; then
    cat "$(readlink -f /etc/resolv.conf)" > /mnt/etc/resolv.conf
fi

if [[ ! -s /mnt/etc/resolv.conf ]]; then
    warn "resolv.conf が取得できませんでした。パブリックDNSを設定します。"
    printf 'nameserver 8.8.8.8\nnameserver 1.1.1.1\n' > /mnt/etc/resolv.conf
fi
ok "chroot準備完了"

# ─── chroot内処理 ─────────────────────────────────────────
hdr "⑪ chroot内: パッケージインストール & システム設定"
info "カーネル・systemd・必要パッケージをインストール中..."

chroot /mnt /bin/bash -c "
set -e
export DEBIAN_FRONTEND=noninteractive

cat > /etc/apt/sources.list << SOURCES
deb http://archive.ubuntu.com/ubuntu ${DISTRO} main restricted universe multiverse
deb http://archive.ubuntu.com/ubuntu ${DISTRO}-updates main restricted universe multiverse
deb http://security.ubuntu.com/ubuntu ${DISTRO}-security main restricted universe multiverse
SOURCES

apt-get update -q
apt-get install -y \
    linux-generic linux-headers-generic \
    systemd systemd-sysv \
    sudo locales tzdata \
    efibootmgr \
    btrfs-progs \
    bash-completion vim nano \
    network-manager wget xz-utils unzip

locale-gen ja_JP.UTF-8
update-locale LANG=ja_JP.UTF-8

echo '${HOSTNAME_}' > /etc/hostname
cat > /etc/hosts << HOSTS
127.0.0.1   localhost
127.0.1.1   ${HOSTNAME_}
HOSTS

useradd -m -s /bin/bash '${USERNAME}'
usermod -aG sudo '${USERNAME}'
"

ok "パッケージインストール完了"

# ─── パスワード設定 ────────────────────────────────────────
hdr "⑫ ユーザーパスワード設定"
ask "ユーザー「${USERNAME}」のパスワードを設定します:"
chroot /mnt passwd "$USERNAME"

ask "rootパスワードを設定します (Enterでスキップ):"
read -r -s SET_ROOT
if [[ -n "$SET_ROOT" ]]; then
    echo "root:${SET_ROOT}" | chroot /mnt chpasswd
    ok "rootパスワード設定完了"
else
    info "rootパスワードはスキップしました"
fi

# ─── Limine取得 ───────────────────────────────────────────
hdr "⑬ Limine ブートローダー設定"

if ! command -v wget &>/dev/null; then
    apt-get install -y wget
fi

info "Limine最新バージョンをGitHubから取得中..."

RELEASE_JSON=$(wget -qO- https://api.github.com/repos/limine-bootloader/limine/releases/latest)

LIMINE_VER=$(echo "$RELEASE_JSON" | grep '"tag_name"' | sed 's/.*"tag_name": *"v\([^"]*\)".*/\1/')
[[ -z "$LIMINE_VER" ]] && error "Limineのバージョン取得に失敗しました。ネットワーク接続を確認してください。"
info "Limine バージョン: ${LIMINE_VER}"

ASSETS=$(echo "$RELEASE_JSON" \
    | grep '"browser_download_url"' \
    | sed 's/.*"browser_download_url": *"\([^"]*\)".*/\1/')

mkdir -p /tmp/limine-bin /mnt/usr/share/limine /mnt/usr/local/bin

# limine-binary.tar.xz をダウンロードして展開
BINARY_TXZ_URL=$(echo "$ASSETS" | grep 'limine-binary\.tar\.xz$' | head -1)

if [[ -n "$BINARY_TXZ_URL" ]]; then
    info "limine-binary.tar.xz ダウンロード中..."
    wget -O /tmp/limine-bin/limine-binary.tar.xz "$BINARY_TXZ_URL" \
        || error "limine-binary.tar.xz のダウンロードに失敗しました。"
    info "アーカイブを展開中..."
    tar -xf /tmp/limine-bin/limine-binary.tar.xz -C /tmp/limine-bin/
    ok "展開完了"
else
    BINARY_TGZ_URL=$(echo "$ASSETS" | grep 'limine-binary\.tar\.gz$' | head -1)
    if [[ -n "$BINARY_TGZ_URL" ]]; then
        info "limine-binary.tar.gz ダウンロード中..."
        wget -O /tmp/limine-bin/limine-binary.tar.gz "$BINARY_TGZ_URL" \
            || error "limine-binary.tar.gz のダウンロードに失敗しました。"
        tar -xf /tmp/limine-bin/limine-binary.tar.gz -C /tmp/limine-bin/
        ok "展開完了"
    else
        error "limine-binary アーカイブが見つかりません。"
    fi
fi

info "展開されたファイル:"
find /tmp/limine-bin/ -type f | head -30

# BOOTX64.EFI をコピー
BOOTX64_PATH=$(find /tmp/limine-bin/ -iname 'BOOTX64.EFI' | head -1)
if [[ -z "$BOOTX64_PATH" ]]; then
    BOOTX64_PATH=$(find /tmp/limine-bin/ -iname '*.efi' \
        | grep -iv 'ia32\|aa64\|riscv\|loong' | head -1)
fi
[[ -z "$BOOTX64_PATH" ]] && error "BOOTX64.EFI がアーカイブ内に見つかりません。"
cp "$BOOTX64_PATH" /mnt/usr/share/limine/BOOTX64.EFI
ok "BOOTX64.EFI コピー完了"

# limine-bios.sys (存在する場合のみ)
BIOS_SYS_PATH=$(find /tmp/limine-bin/ -iname 'limine-bios.sys' | head -1)
if [[ -n "$BIOS_SYS_PATH" ]]; then
    cp "$BIOS_SYS_PATH" /mnt/usr/share/limine/
    ok "limine-bios.sys コピー完了"
fi

# limine CLI バイナリ (存在する場合のみ)
LIMINE_CLI_PATH=$(find /tmp/limine-bin/ -type f -name 'limine' | head -1)
if [[ -n "$LIMINE_CLI_PATH" ]]; then
    cp "$LIMINE_CLI_PATH" /mnt/usr/local/bin/limine
    chmod +x /mnt/usr/local/bin/limine
    ok "limine CLI コピー完了"
fi

# ─── ESPへLimineファイル配置 ──────────────────────────────
mkdir -p /mnt/boot/efi/EFI/limine /mnt/boot/efi/EFI/BOOT
cp /mnt/usr/share/limine/BOOTX64.EFI /mnt/boot/efi/EFI/limine/BOOTX64.EFI
cp /mnt/usr/share/limine/BOOTX64.EFI /mnt/boot/efi/EFI/BOOT/BOOTX64.EFI
ok "BOOTX64.EFI をESPに配置完了"

# ─── initramfs更新(カーネルファイル名確定のために先に実行)─
chroot /mnt /bin/bash -c "
set -e
update-initramfs -u -k all
"
ok "initramfs 更新完了"

# ─── limine.conf 生成(実ファイル名で指定・Limine 12.x対応)─
# 修正点:
#   1. シンボリックリンク (vmlinuz, initrd.img) ではなく実ファイル名を使う
#      → boot():/vmlinuz はシンボリックリンクを辿れないため "no valid entries" になる
#   2. パス指定は uuid(UUID):/ 形式にする
#      → boot():/ 形式は /boot が独立パーティションの場合向け
#   3. エントリ先頭は ":" のみ("/:XXX" はサブメニュー扱いで PANIC になる)
ROOT_UUID=$(blkid "$ROOT" -s UUID -o value)
KERNEL_FILE=$(ls /mnt/boot/vmlinuz-* 2>/dev/null | grep -v '\.old$' | sort -V | tail -1 | xargs basename)
INITRD_FILE=$(ls /mnt/boot/initrd.img-* 2>/dev/null | grep -v '\.old$' | sort -V | tail -1 | xargs basename)

[[ -z "$KERNEL_FILE" ]] && error "カーネルファイルが /mnt/boot に見つかりません。"
[[ -z "$INITRD_FILE" ]] && error "initrd ファイルが /mnt/boot に見つかりません。"

info "カーネル: ${KERNEL_FILE}"
info "initrd:   ${INITRD_FILE}"

cat > /mnt/boot/efi/EFI/limine/limine.conf << CONF
TIMEOUT=5
DEFAULT=1

:Ubuntu (Btrfs)
PROTOCOL=linux
KERNEL_PATH=uuid(${ROOT_UUID}):/boot/${KERNEL_FILE}
MODULE_PATH=uuid(${ROOT_UUID}):/boot/${INITRD_FILE}
CMDLINE=root=UUID=${ROOT_UUID} rw rootflags=subvol=@ quiet splash
CONF

# /boot にも参照用コピー
cp /mnt/boot/efi/EFI/limine/limine.conf /mnt/boot/limine.conf

ok "limine.conf 生成完了"
info "=== limine.conf 内容 ==="
cat /mnt/boot/efi/EFI/limine/limine.conf

# ─── UEFIブートエントリ登録 ───────────────────────────────
chroot /mnt /bin/bash -c "
efibootmgr -c -d ${DISK} -p 1 -L 'Limine' -l '\EFI\limine\BOOTX64.EFI' \
    && echo '[OK] UEFIブートエントリ登録完了' \
    || echo '[WARN] efibootmgr 失敗 (EFI/BOOT/BOOTX64.EFI にフォールバック配置済み)'
"
ok "Limine設定完了"

# ─── アンマウント & 完了 ───────────────────────────────────
hdr "⑭ アンマウント"
sync
umount /mnt/sys/firmware/efi/efivars
umount -R /mnt
ok "アンマウント完了"

echo ""
echo -e "${GREEN}${BOLD}╔══════════════════════════════════════════════╗${RESET}"
echo -e "${GREEN}${BOLD}║  インストール完了!                           ║${RESET}"
echo -e "${GREEN}${BOLD}║  USBを抜いて reboot してください              ║${RESET}"
echo -e "${GREEN}${BOLD}╚══════════════════════════════════════════════╝${RESET}"
echo ""
ask "今すぐ再起動しますか? [y/N]:"
read -r REBOOT_NOW
if [[ "$REBOOT_NOW" =~ ^[yY]$ ]]; then
    reboot
fi

EFI + ext4(/boot) + btrfsの3パーティション版

#!/bin/bash
# ============================================================
#  Btrfs サブボリューム移行スクリプト(/boot 独立パーティション対応版)
#  実行環境: ライブUSB環境のターミナル
#  構成想定: EFI + /boot(ext4) + btrfs の3パーティション構成、
#           および EFI + btrfs の2パーティション構成
#  使い方  : sudo bash btrfs_migrate.sh
# ============================================================

set -euo pipefail

RED='\033[0;31m'; YELLOW='\033[1;33m'; GREEN='\033[0;32m'; CYAN='\033[0;36m'; NC='\033[0m'
info()  { echo -e "${GREEN}[INFO]${NC}  $*"; }
warn()  { echo -e "${YELLOW}[WARN]${NC}  $*"; }
error() { echo -e "${RED}[ERROR]${NC} $*"; exit 1; }
step()  { echo -e "\n${CYAN}=== $* ===${NC}"; }

# root確認
[[ $EUID -ne 0 ]] && error "sudo で実行してください"

# ============================================================
# 起動モード判定(EFI / BIOS)
# ============================================================
step "環境を自動検出"

if [[ -d /sys/firmware/efi ]]; then
    BOOT_MODE="EFI"
else
    BOOT_MODE="BIOS"
fi
info "起動モード: $BOOT_MODE"

# --- Btrfsパーティション検出 ---
BTRFS_PART=$(lsblk -lnpo NAME,FSTYPE | awk '$2=="btrfs"{print $1}' | head -1)
[[ -n "$BTRFS_PART" ]] || error "Btrfsパーティションが見つかりませんでした"

# --- ディスク本体 ---
DISK=$(lsblk -lnpo PKNAME "$BTRFS_PART" | head -1)
[[ -n "$DISK" ]] || DISK=$(echo "$BTRFS_PART" | sed 's/p\?[0-9]*$//')
DISK="/dev/${DISK##*/}"

# --- EFI / BIOS それぞれのブートパーティション検出 ---
EFI_PART=""
BIOS_BOOT_PART=""

if [[ "$BOOT_MODE" == "EFI" ]]; then
    EFI_PART=$(lsblk -lnpo NAME,PARTTYPE | \
        awk 'tolower($2) == "c12a7328-f81f-11d2-ba4b-00a0c93ec93b" {print $1}' | head -1)
    if [[ -z "$EFI_PART" ]]; then
        EFI_PART=$(lsblk -lnpo NAME,FSTYPE | awk '$2=="vfat"{print $1}' | head -1)
    fi
    [[ -n "$EFI_PART" ]] || error "EFIパーティションが見つかりませんでした"
else
    BIOS_BOOT_PART=$(lsblk -lnpo NAME,PARTTYPE | \
        awk 'tolower($2) == "21686148-6449-6e6f-744e-656564454649" {print $1}' | head -1)
    if [[ -z "$BIOS_BOOT_PART" ]]; then
        warn "BIOS Bootパーティションが見つかりません(MBRディスクでは正常)"
    else
        info "BIOS Bootパーティション: $BIOS_BOOT_PART"
    fi
fi

# ============================================================
# /boot 独立パーティション検出(ext4 かつ EFI・Btrfs 以外)
# ★ここが今回の追加対応の核心
# ============================================================
BOOT_PART=""

# EFI・Btrfs以外の ext4/ext3/ext2 パーティションを /boot 候補として検出
for part in $(lsblk -lnpo NAME,FSTYPE "$DISK" | awk '$2 ~ /^ext[234]$/ {print $1}'); do
    [[ "$part" == "$EFI_PART" ]] && continue
    [[ "$part" == "$BTRFS_PART" ]] && continue
    # fstab や実際のマウント情報から /boot かどうか確認
    # ライブ環境でマウントされていない場合も想定し、パーティション番号の小ささで判断
    BOOT_PART="$part"
    break
done

if [[ -n "$BOOT_PART" ]]; then
    info "独立 /boot パーティションを検出: $BOOT_PART"
    BOOT_FS=$(lsblk -lnpo FSTYPE "$BOOT_PART" | head -1)
    info "/boot ファイルシステム: $BOOT_FS"
else
    info "/boot 独立パーティションなし(btrfs の @ サブボリューム内に /boot が存在する構成)"
fi

# --- 結果表示 ---
echo ""
echo "  検出結果:"
echo "  ┌──────────────────────────────────────────┐"
printf "  │  起動モード          : %-21s│\n" "$BOOT_MODE"
printf "  │  Btrfsパーティション : %-21s│\n" "$BTRFS_PART"
if [[ "$BOOT_MODE" == "EFI" ]]; then
    printf "  │  EFIパーティション   : %-21s│\n" "$EFI_PART"
else
    printf "  │  BIOS Boot           : %-21s│\n" "${BIOS_BOOT_PART:-(なし / MBR)}"
fi
printf "  │  /boot パーティション: %-21s│\n" "${BOOT_PART:-(なし / btrfs内)}"
printf "  │  ディスク            : %-21s│\n" "$DISK"
echo "  └──────────────────────────────────────────┘"
echo ""

info "ディスク構成(参考):"
lsblk -o NAME,SIZE,FSTYPE,PARTTYPE,MOUNTPOINT "$DISK"
echo ""

warn "上記の検出結果で続行しますか? [yes/N]"
read -r answer
[[ "$answer" == "yes" ]] || { info "中止しました。"; exit 0; }

# ============================================================
# ステップ1: マウント
# ============================================================
step "Step 1: Btrfsパーティションをマウント"

info "対象パーティションの既存マウントを解除"
for mp in $(findmnt -rno TARGET --source "$BTRFS_PART" 2>/dev/null | sort -r); do
    [[ "$mp" == "/" ]] && continue
    info "  既存マウント解除: $mp"
    umount "$mp" 2>/dev/null || umount -l "$mp" 2>/dev/null || true
done
if [[ -n "${EFI_PART:-}" ]]; then
    for mp in $(findmnt -rno TARGET --source "$EFI_PART" 2>/dev/null | sort -r); do
        [[ "$mp" == "/" ]] && continue
        info "  既存マウント解除 (EFI): $mp"
        umount "$mp" 2>/dev/null || umount -l "$mp" 2>/dev/null || true
    done
fi
if [[ -n "$BOOT_PART" ]]; then
    for mp in $(findmnt -rno TARGET --source "$BOOT_PART" 2>/dev/null | sort -r); do
        [[ "$mp" == "/" ]] && continue
        info "  既存マウント解除 (/boot): $mp"
        umount "$mp" 2>/dev/null || umount -l "$mp" 2>/dev/null || true
    done
fi

mkdir -p /mnt
mount "$BTRFS_PART" /mnt

# サブボリューム二重実行チェック
if btrfs subvolume list /mnt 2>/dev/null | grep -qE '\s@$|\s@home$'; then
    error "サブボリューム @ または @home がすでに存在します。移行済みの可能性があります。"
fi

# ============================================================
# ステップ2: サブボリューム作成・データ移動
# ============================================================
step "Step 2: サブボリューム作成・データ移動"
cd /mnt

info "スナップショット @ を作成"
btrfs subvolume snapshot . @

# /boot が独立パーティションの場合、@ 内の /boot は空ディレクトリにする
# (独立パーティションが ext4 の場合、btrfs 内に /boot の実データは不要)
# ただし /boot ディレクトリ自体はマウントポイントとして残す
if [[ -n "$BOOT_PART" ]]; then
    info "/boot 独立パーティション構成のため、@/boot/ を空にします(マウントポイントとして残す)"
    # @/boot の中身を確認(スナップショット直後なので実データが入っている)
    # btrfs スナップショット内なので操作は /mnt/@/boot 経由
    if [[ -d /mnt/@/boot ]]; then
        # /boot 内の全ファイルを削除し、空のマウントポイントだけ残す
        find /mnt/@/boot -mindepth 1 -delete
        info "  @/boot/ を空のマウントポイントとして準備完了"
    fi
fi

info "サブボリューム @home を作成"
btrfs subvolume create @home

if [[ -d /mnt/home ]] && compgen -G "/mnt/home/*" > /dev/null 2>&1; then
    info "home/* を @home/ へコピー"
    cp -a /mnt/home/. /mnt/@home/
else
    warn "home/ が空またはありません。スキップします。"
fi

info "フラットなルートデータを削除(@ へコピー済み)"
for entry in /mnt/*; do
    name=$(basename "$entry")
    [[ "$name" == "@" || "$name" == "@home" ]] && continue
    if mountpoint -q "$entry" 2>/dev/null; then
        info "  アンマウント中: $entry"
        umount -R "$entry" 2>/dev/null || umount -l "$entry" 2>/dev/null || true
    fi
    rm -rf "$entry"
    info "  削除: $entry"
done

# ============================================================
# ステップ3: fstab 書き換え
# ============================================================
step "Step 3: fstab を更新"
FSTAB="/mnt/@/etc/fstab"
[[ -f "$FSTAB" ]] || error "fstab が見つかりません: $FSTAB"

UUID=$(blkid -s UUID -o value "$BTRFS_PART")
[[ -n "$UUID" ]] || error "UUIDの取得に失敗しました"
info "Btrfs UUID: $UUID"

# /boot パーティションの UUID も取得
BOOT_UUID=""
if [[ -n "$BOOT_PART" ]]; then
    BOOT_UUID=$(blkid -s UUID -o value "$BOOT_PART")
    [[ -n "$BOOT_UUID" ]] || error "/boot パーティションの UUID 取得に失敗しました"
    info "/boot UUID: $BOOT_UUID"
fi

cp "$FSTAB" "${FSTAB}.bak"
info "バックアップ: ${FSTAB}.bak"

python3 - "$FSTAB" "$UUID" "$BOOT_UUID" "${BOOT_FS:-}" << 'PYEOF'
import sys, re

fstab_path = sys.argv[1]
uuid        = sys.argv[2]
boot_uuid   = sys.argv[3]   # 空文字なら /boot 独立なし
boot_fs     = sys.argv[4]   # ext4 など

with open(fstab_path) as f:
    lines = f.readlines()

new_lines = []
has_home  = False
has_boot  = False

for line in lines:
    stripped = line.strip()
    if stripped.startswith("#") or stripped == "":
        new_lines.append(line)
        continue
    cols = stripped.split()
    if len(cols) < 4:
        new_lines.append(line)
        continue

    mountpoint = cols[1]
    fstype     = cols[2]

    if fstype == "btrfs" and mountpoint == "/":
        opts = re.sub(r'subvol=[^,\s]+,?', '', cols[3]).strip(',')
        opts = (opts + ',subvol=@') if opts else 'defaults,subvol=@'
        new_lines.append(f"UUID={uuid} / btrfs {opts} 0 0\n")

    elif fstype == "btrfs" and mountpoint == "/home":
        opts = re.sub(r'subvol=[^,\s]+,?', '', cols[3]).strip(',')
        opts = (opts + ',subvol=@home') if opts else 'defaults,subvol=@home'
        new_lines.append(f"UUID={uuid} /home btrfs {opts} 0 0\n")
        has_home = True

    elif mountpoint == "/boot" and boot_uuid:
        # 既存の /boot エントリを正しい UUID で上書き
        new_lines.append(f"UUID={boot_uuid} /boot {boot_fs} defaults 0 2\n")
        has_boot = True

    else:
        new_lines.append(line)

# /home エントリがなければ追加
if not has_home:
    new_lines.append(f"UUID={uuid} /home btrfs defaults,subvol=@home 0 0\n")

# /boot 独立パーティションがあるのに fstab にエントリがなければ追加
if boot_uuid and not has_boot:
    new_lines.append(f"UUID={boot_uuid} /boot {boot_fs} defaults 0 2\n")

with open(fstab_path, 'w') as f:
    f.writelines(new_lines)
PYEOF

info "更新後の fstab:"
cat "$FSTAB"

# ============================================================
# ステップ4: chroot して GRUB 更新
# ============================================================
step "Step 4: chroot して GRUB を更新"

mkdir -p /mnt2
mount -t btrfs -o subvol=@ "$BTRFS_PART" /mnt2
info "subvol=@ で /mnt2 に再マウント完了"

mkdir -p /mnt2/boot/grub

# ★ /boot 独立パーティションがある場合は chroot 前にマウント
if [[ -n "$BOOT_PART" ]]; then
    info "/boot パーティション ($BOOT_PART) を /mnt2/boot にマウント"
    mkdir -p /mnt2/boot
    mount "$BOOT_PART" /mnt2/boot
    info "  /mnt2/boot の内容:"
    ls /mnt2/boot/ || true
fi

# 仮想FSバインド
mount --rbind /dev  /mnt2/dev  && mount --make-rslave /mnt2/dev
mount --rbind /proc /mnt2/proc && mount --make-rslave /mnt2/proc
mount --rbind /sys  /mnt2/sys  && mount --make-rslave /mnt2/sys
mount --rbind /run  /mnt2/run  && mount --make-rslave /mnt2/run

if [[ "$BOOT_MODE" == "EFI" ]]; then
    mount --bind /sys/firmware/efi/efivars /mnt2/sys/firmware/efi/efivars 2>/dev/null || \
        warn "efivars のバインドに失敗しました"
    mkdir -p /mnt2/boot/efi
    mount "$EFI_PART" /mnt2/boot/efi

    chroot /mnt2 /bin/bash -e << CHROOT
echo '[chroot] /boot の内容確認:'
ls /boot/ || true
echo '[chroot] grub-install (EFI) ...'
grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=ubuntu --recheck
echo '[chroot] grub-mkconfig ...'
grub-mkconfig -o /boot/grub/grub.cfg
echo '[chroot] grub.cfg 内の subvol 指定確認:'
grep -i subvol /boot/grub/grub.cfg || echo '(subvol 指定なし)'
echo '[chroot] 完了'
CHROOT

else
    chroot /mnt2 /bin/bash -e << CHROOT
echo '[chroot] /boot の内容確認:'
ls /boot/ || true
echo '[chroot] grub-install (BIOS) ...'
grub-install --target=i386-pc --recheck $DISK
echo '[chroot] grub-mkconfig ...'
grub-mkconfig -o /boot/grub/grub.cfg
echo '[chroot] grub.cfg 内の subvol 指定確認:'
grep -i subvol /boot/grub/grub.cfg || echo '(subvol 指定なし)'
echo '[chroot] 完了'
CHROOT
fi

# ============================================================
# ステップ5: アンマウント
# ============================================================
step "Step 5: アンマウント"
umount -R /mnt2 2>/dev/null || true
umount -R /mnt  2>/dev/null || true

echo ""
echo -e "${GREEN}======================================================"
echo "  移行が完了しました!"
echo "  再起動後に以下で確認してください:"
echo "    sudo btrfs subvolume list /"
echo "    findmnt -T /boot"
echo -e "======================================================${NC}"
echo ""
warn "再起動しますか? [yes/N]"
read -r reboot_answer
[[ "$reboot_answer" == "yes" ]] && reboot || info "手動で再起動してください: sudo reboot"
タイトルとURLをコピーしました