個人用途で、ホストを極力汚さず、メンテナンス性と安全性を両立させるには、LXDを基盤としてVMやDocker(LXDコンテナ内)を運用する「ネスト構造」が便利です。そして、ホストOSの環境は「Timeshift」を導入しておけば、アップデートの失敗などによってOSが起動しなくなった時の復旧にも対応しやすくなります。
構成のイメージ
1. ホストOSの構成
- ファイルシステム: Btrfs
- 理由:
- Timeshift との相性が抜群で、ホストの設定(LXDのインストール状態など)をいつでもロールバックできる。
- ホスト側で透過圧縮(zstd)を効かせれば、LXDのイメージや仮想ディスクの容量を節約できる。
2. LXDのストレージプール構成
LXDのデフォルトプールには 「Btrfsバックエンド」 を推奨。
- バックエンド: Btrfs(ホストのBtrfs上にサブボリュームとして作成)
- 理由:
lxc copyやlxc snapshotが一瞬で終わる(CoWの恩恵)。- VM(仮想マシン) を作成した際、内部で自動的に
nocow設定が適用されるため、断片化のリスクが抑えられる。 - ZFSよりもメモリ消費が少なく、個人用PCの500GB SSDで運用するにはリソース管理が楽。
3. LXD内でのDocker(Nextcloud/Immich)運用
LXDコンテナの中でDockerを動かす場合、以下の設定で「ホストを汚さない&高速」を実現します。
- コンテナの種別: 特権なし(Unprivileged)コンテナ(セキュリティのため)
- Dockerストレージドライバ:
overlay2- LXDがBtrfsプールなら、コンテナ内のDockerはデフォルトで
vfs(激重)やbtrfsになろうとしますが、これをoverlay2に強制設定します。
- LXDがBtrfsプールなら、コンテナ内のDockerはデフォルトで
- データの逃がし先(NOCOWの適用):
- ホスト側に
sudo mkdir /var/lib/lxd-data && sudo chattr +C /var/lib/lxd-dataで NOCOW領域 を作成。 - NextcloudやImmichのDB用ディレクトリだけ、ホストのこの領域を
lxc config device addでコンテナにパススルー(ディスクマウント)する。 - 写真データは、NOCOWではない「通常のBtrfs領域」をマウントして、チェックサムによる保護を受ける。
- ホスト側に
最適な構成まとめ表
| 階層 | 種類 / 設定 | 役割 |
|---|---|---|
| ホストOS | Ubuntu (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の写真など)は無傷で残せます。
- サブボリュームの階層設計は、Btrfsのサブボリュームを「入れ子」にせず、並列(フラット)に配置するのが安全です。これにより、Timeshiftが @(ルート)を書き換えても他が巻き込まれません。
@ (ルート): / にマウント。Timeshiftの保護対象。
@home: /home にマウント。
@lxd_pool: /var/lib/lxd/storage-pools/default 等にマウント。(Timeshift対象外)
@lxd_data: /mnt/lxd-data 等にマウント。ホスト・LXD共通。(Timeshift対象外) - インストールと設定の手順
① 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"


