lp6m’s blog

いろいろかきます

WindowsとUbuntuのブートを選択できる物理スイッチを作った

小ネタです。

モチベーション

デスクトップPCには最初にWindowsをインストールし、その後別のSSDUbuntuをインストールしてデュアルブート環境を構築している。(実際には3つだが)
BIOSでのブートディスクの優先順位をUbuntu側のSSDにしておくと、PC起動時にGRUBメニューが表示され、どのOSを起動するか選択できる。

こんな感じのやつ。

これはこれで便利なのだが、一つ問題があった。
自分はKVM機能が付いているDellのモニタ (U3821DW) を使っている。キーボード他の周辺機器をモニターに接続しておけば、映像入力に合わせて周辺機器の接続先がPC間で切り替わるので便利だ。

しかし、「PCの電源を入れる → モニタの映像入力をデスクトップPCに切り替える → キーボードがデスクトップPCに認識される」というプロセスには数秒かかる。(しかも一瞬出力がオフになるせいで、信号なしと判断されて別の映像入力に強制的に切り替わったり・・・)その間にGRUBの選択タイムアウトを過ぎてしまい、起動したいOSを選べないことが多々あった。

「PCの電源を入れる前に、物理的なスイッチで起動OSを切り替えられたら最高に便利なのにな…」とは昔から思っていた。

Geminiに相談したらGRUBスクリプトをカスタマイズすることで、簡単にできるらしいのでやってみた。

これならいける。あとは、物理スイッチの状態に応じてUSBの中身(特定のファイルの有無)を切り替えられるデバイスを作ればいいだけだ。というわけでやってみる。

GRUBスクリプトで起動OSを動的に切り替える

まずはPC側の設定から。GRUBは起動時に/etc/grub.d/ディレクトリにあるスクリプトを読み込んで、設定ファイル(/boot/grub/grub.cfg)を生成する。なので、ここに自作のスクリプトを追加する。

1. カスタムスクリプトの作成

/etc/grub.d/09_custom_boot_selectという名前で以下のスクリプトを作成する。(先頭の数字が小さいほど先に実行される)

#!/bin/sh
# GRUBが起動時に実行するコマンドを grub.cfg に書き出すためのスクリプト

# 以下の cat << EOF から EOF までの内容が、そのまま grub.cfg に出力される
cat << EOF
# --- Start of Custom USB Boot Logic ---

echo "Searching for boot key on USB devices..."

# USBデバイスをUUIDで検索する (GRUBのsearchコマンド)
# ★あとでRP2040を接続して調べたUUIDに書き換える
search --no-floppy --fs-uuid --set=usbkey A1B2-C3D4

# もしUSBが見つかり、かつ 'windows.key' ファイルが存在すれば
if [ -n "\$usbkey" ] && [ -f (\$usbkey)/windows.key ]; then
  # デフォルトの起動OSをWindowsに設定する (GRUBのset defaultコマンド)
  echo "USB key found. Booting Windows."
  # ★自分の環境のWindowsエントリー名に書き換える
  set default="Windows Boot Manager (on /dev/sda1)"
else
  # 見つからなければ何もしない(通常のデフォルトOSで起動する)
  echo "USB key not found. Proceeding with normal boot."
fi

# --- End of Custom USB Boot Logic ---
EOF
2. 必要な情報を調べてスクリプトを編集

スクリプト内の2箇所(の部分)を自分の環境に合わせて書き換える必要がある。

  • USBのUUIDを調べる

自分は最初は適当なUSBメモリを使って手動で実験してみた。後の作業でRP2040側にCircuitPythonをインストールした後で、もう一度UUIDを調べて書き換える必要がある。

lsblk -f
  • Windowsのメニューエントリー名の調べ方

以下のコマンドで、自分のPCの正確なエントリー名を調べる。`'`で囲まれた部分全体をコピーする。

grep 'menuentry ' /boot/grub/grub.cfg
3. スクリプトの適用

最後に、作成したスクリプトに実行権限を与えて、GRUBの設定を更新すれば完了。

sudo chmod +x /etc/grub.d/09_custom_boot_select
sudo update-grub

これでPC側の準備はOK。

物理スイッチでUSBの内容を書き換える

次に、物理スイッチとなるUSBデバイスを作る。
マイコンがPCに対して「自分はUSBメモリです」と振る舞う必要があるが、Raspberry Pi Pico (RP2040)はネイティブUSBに対応しているので、容易に実装できる。

今回は手元に転がっていた&小さかった**RP2040-zero**というボードを使ったが、Pico系なら何でもOK。
1000円弱で買えるのでかなり安い。

www.waveshare.com

CircuitPythonというものが使えるらしいので使ってみた。環境構築は非常に簡単で、BOOTボタンを押しながらuf2ファイルを書き込むだけ。参考:
Installing CircuitPython | Welcome to CircuitPython! | Adafruit Learning System


配線は、トグルスイッチの片方をGP15に、もう片方を3V3(OUT)に接続するだけ。
`code.py`に以下のコードを書き込む。

import board
import digitalio
import storage
import os

# --- 設定 ---
# スイッチを接続するGPIOピン
SWITCH_PIN = board.GP15 

# --- 初期設定 ---
# スイッチのピンを入力モードに設定し、内部プルダウン抵抗を有効にする
switch = digitalio.DigitalInOut(SWITCH_PIN)
switch.direction = digitalio.Direction.INPUT
switch.pull = digitalio.Pull.DOWN

# PCからの書き込みに備え、一度書き込み可能にリマウント
storage.remount("/", readonly=False)

# 念のため、既にあるキーファイルを削除しておく
try:
    os.remove("/windows.key")
except OSError:
    # ファイルがなくてもエラーにしない
    pass 

# スイッチの状態を読み取る (ONならTrue, OFFならFalse)
if switch.value:
    print("Switch is ON. Creating windows.key")
    # スイッチがONなら、windows.keyファイルを作成する
    with open("/windows.key", "w") as f:
        f.write("This is a boot flag file.\n")
else:
    print("Switch is OFF. Not creating any file.")

# 安全のため、ファイルシステムを読み取り専用に戻す
storage.remount("/", readonly=True)

print("Setup complete. The device is now a USB drive.")

これで、Picoは起動時にスイッチがONになっていればwindows.keyというファイルを持つUSBメモリとして、OFFならwindows.keyは削除される。

正しくGRUBのデフォルト選択項目が選ばれるには、該当するGRUBスクリプトが実行されるまでにRP2040上でのCircuitPythonの処理が完了している必要がある。今回は細かいことを考えずに作っているが、実際に使ってみたところ100%成功したので、まあRP2040での処理完了のほうが速いからOKということにしておこう。

なおスクリプトとコードは全部Geminiに書かせた。Gemini特有のコメント文がいっぱい刺さってるね。

3Dプリンタでケースを作る。

最後に、RP2040-zeroとスライドスイッチを収めるケースを3Dプリンタで作成した。Fusion360力がいつまでたっても向上しないが。。
※なおよく見るとRESETボタンが破壊されているが、使わないのでまあヨシとする・・何度か取り外す時にペンチで掴んだせい。



まとめ

Searching for boot key on USB devices...の表示のあと、自動的にWindowsが選択されている様子。

今回の小ネタ、既にやってる人が多そうだなと思ったけど意外と似た記事は無かった気がする。
これで、PCの電源を入れる前に物理スイッチを切り替えておくだけで、快適にOSを選択できるようになった。やったね!
(もっと楽な方法あるのかな?)