今回はブートローダーについてです。
最近、USBメモリにLinuxを入れて遊んでいるのですが、デスクトップPCから起動する際に、USBが刺さっているときはUSBから、そうでないときは内蔵SSDから起動するように自動で選んでくれないだろうか?と思いました。
単にUSBの起動順を上にしておけばUSBが使えない場合は自動的にそれ以降のオプションを試してくれる、という場合もあるのですが、件のデスクトップPCはUSBが刺さっていない状態で一度起動するとUSB関連のオプションの優先度を勝手に下げてしまうようでした。これだと次回はUSBを刺した状態でもSSDから起動してしまいます。
rEFIndを使ってみる
検索するとGetting UEFI BIOS boot order consistency when attaching/removing USB? - Super Userで同じ状況の人がいました。解決策としては、UEFI用のブートローダであるrEFIndを使って、USB側の優先度を上げてdefault_selectionを複数指定することでUSBがないときはrEFIndが内蔵ディスク側にフォールバックしてくれるというものでした。
ただしこの例だとdefault_selection "vmlinuz,Microsoft"のようにOSで指定しているので、USBにも内蔵ディスクにもLinuxが入っている今回のケースだとそのままでは使えません。
rEFIndは高機能なブートローダーなのでパーティションのUUID(あるいはラベル)とパス名でefiバイナリを直接指定することも可能です。しかしこれだと、指定が直接的だからか、見つからなかった場合は次のものにフォールバックすることなくrEFIndのメイン画面に戻されてしまいます。
一応、動かなかったrefind.confの概観を載せておきます。
scanfor manual,internal,external,optical
menuentry "elecom" {
volume XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
loader /EFI/ubuntu/shimx64.efi
}
menuentry "grub" {
volume XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
loader /EFI/MYGRUB/grubx64.efi
}
default_selection "elecom,grub"
elecomと書いてある方がUSBで、grubのほうが内蔵HDDです。volumeのところに書いているUUIDはPARTUUID(GPTにおいて各パーティションに付与される)です。
GRUBを使う
rEFIndはダメそうなのでGRUBでできないか考えてみることにします。
するとGRUBにはfallbackというそのものズバリな名前の変数があることがわかりました(GNU GRUB Manual 2.12: fallback)。あまり使われていないようでよくわかりませんが、/etc/default/grubにfallback="0 1"あるいは(GRUB_DEFAULTなどと似た感じで)GRUB_FALLBACK="0 1"のように書いておくとデフォルトのものが起動失敗した際にそっちを見に行ってくれるようです。
しかし試してみてもうまく動きませんでした。"Failed to boot both default and fallback entries."というエラーが出てしまいます(もちろんfallbackに指定したエントリを普通に選ぶとちゃんと起動します)。
それとUSBからの起動に失敗した後、fallbackに以降する前のタイミングで「press any key to continue...」と5秒くらい表示されているのもちょっと微妙な感じです(仮にfallbackでうまくいったとしても無駄に5秒待たされそう)。
もうちょっとお行儀よく、文字通りUSBの有無を判定して切り替えるようなやり方にできないもんかなあと思ってGRUB - ArchWikiを読んでいると、どうやらGRUBのメニューエントリではシェルスクリプトのような(「のような」という言い方が正しいかわかりませんが)if文が使えることがわかりました。というか普通の(update-grubで自動生成されるような)grub.cfgも読んでみるとif文が入っています。UUID(やラベル)でファイルシステムを指定できるsearchコマンドもあるので、ifと組み合わせれば、ファイルシステムが見つかったときにそっちから起動(今回でいえばUSBメモリ)、なければ内蔵ディスクから起動、というふうにできます。
GRUBを別でインストール
さらに今回は、内蔵ディスクの方にはLinuxとWindowsが両方入っていて既にGRUBで選択できるようになっていたので、内蔵ディスクから起動する場合はそっちのGRUBに移行するようにできるとベストです。
環境変数などをうまく使えばこの既存のGRUBインストールを使いつつUSBの有無の判定をさせることも不可能ではないかもしれませんが、ちょっと見通しが悪くなりそうです。そこで、既存のGRUBと同じEFIパーティションにもう一つ別にGRUBをインストールし、そっちにUSBメモリの有無の判定をやらせて、USBがなかった場合には既存のGRUBをチェーンロードするという作戦でいくことにしました。
通常、GRUBでは設定ファイルを/boot以下(つまりEFIパーティションではなくLinux本体側のパーティション)に保存します。ここを変えずに新しいGRUBをインストールすると既存のGRUBと干渉してしまいます。grub-installのときに--boot-directoryで直接ディスクを指定するとこれを変えることができます(参考:GRUB/ヒントとテクニック - ArchWiki)。
具体的には、(内蔵ディスクのEFIシステムパーティションが/boot/efiにマウントされている状態で)以下のようなコマンドを実行します。
grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=mygrubid --boot-directory=/boot/efi/mygrub
これで/boot/efi/EFI/mygrubidにgrubx64.efiが生成され、あわせて/boot/efi/mygrub以下にgrubフォルダができて設定ファイル一式が格納されます。しかしこの状態だとエントリの設定は皆無です。/boot/efi/mygrub/grub/grub.cfgが生成されるかなと予測していたのですが何もありません。そこで勝手にこのファイルを作成し、以下の内容を書き込みます。使える構文はこのへんに書いてあるようです。
- ちなみに/boot/efi/EFI/mygrubidのほうには既にgrub.cfg(/boot/efi/mygrub/grub/grub.cfgのほうを呼び出す設定だけが3行ほど書いてある)がありましたが、こっちを直接編集するのはなんとなくベストプラクティスじゃない雰囲気がしたのでやめました。
set timeout=0
menuentry "detect usb" {
search --no-floppy --fs-uuid --set=root_hdd XXXX-XXXX
search --no-floppy --fs-uuid --set=root_usb YYYY-YYYY
if [ -n "$root_usb" ]; then
root=$root_usb
chainloader /EFI/ubuntu/shimx64.efi
boot
else
root=$root_hdd
chainloader /EFI/MYGRUB/grubx64.efi
boot
fi
}
タイムアウトは0秒に設定しているのでGRUB画面は表示されず一瞬でデフォルト(というか1つしかない)の"detect usb"が実行されます。
この中で、内蔵ディスクとUSBメモリのそれぞれのEFIパーティションのUUID(これはPARTUUIDではなく、ファイルシステム自身が持っているもので、EFIシステムパーティション(FAT32)の場合は8桁)(blkidコマンドやGPartedなどで確認できる)を指定してsearchをします。usbのほうでヒットした場合にはそちらをroot変数に指定した上でUSBのGRUBを起動し、そうでなければhddのほうから既存のGRUBを呼び出します。
これで、USBが刺さっていない場合は既存のGRUB、刺さっている場合はUSBのGRUBが(待ち時間なく一瞬で)呼び出されるようになります。
ブートエントリの確認
grub-installがうまくいっていれば、UEFIブートエントリに/EFI/mygrubid/shimx64.efi(あるいはセキュアブートがないならgrubx64.efi)が登録されていると思います。あまり詳しくわかっていないのですがgrubx64.efiからだとshimx64.efiをチェインロードできないような気がします。
うまく動作しない場合は上記のgrub.cfgに加えてefibootmgrコマンドを使ってブートエントリ側の設定も確認しましょう。
ちなみに、aptなどでカーネルアップデートが入るとそのたびにupdate-grubが走ることになりますが、少なくとも普通にupdate-grubする分には今回の設定に影響することはなさそうです。
まとめ
GRUBを二つインストールするのもgrub.cfgを手書きするのも明らかに公式が推奨するやり方ではないと思うのですが、その気になればかなり柔軟な設定ができそうで面白いですね。