👊

【KVM】実機Windows 11を仮想マシンとして起動し、WinAppsでLinuxに統合する

に公開

実機でもVM(QEMU/KVM)でもWindowsを起動したい。
そのうえでWinAppsを使いたい。

前提

  • パーティションが死ぬ可能性があります。バックアップしてからやるか、消えてもいいものでやること。
  • Windowsのインストールiso(例: Win11_25H2_Japanese_x64.iso)を用意する
  • 作業ディレクトリはどこでもよいが ~/.config/winapps/images とかがいいかも

手順

1. 実機でWindowsをセットアップする

普通にセットアップすればいいです。
WindowsHelloはつけないほうがいいかも。
つけてもいいけど、MicrosoftのパスワードでもログオンできるようにしないとVMでログオンできません。

2. EFIイメージを作成

# 100MB(1MB x 100)のイメージを ./efi1 に作成
dd if=/dev/zero of=efi1 bs=1M count=100
# 1MB(1MB x 1)のイメージを ./efi2 に作成
dd if=/dev/zero of=efi2 bs=1M count=1

3. パーティションを特定する

lsblk -o NAME,SIZE,TYPE,PARTUUID

Windowsが入っているパーティションのPARTUUIDをメモする(EFIや回復パーティションではない)

4. 仮想ストレージデバイスを作成するスクリプトを作成

start_md0.sh (1箇所PARTUUIDを貼り付けするところがある)

#!/usr/bin/env bash

# 管理者権限で実行
if [ "$(id -u)" -ne 0 ]; then
    echo "Requesting root privileges..."
    exec sudo "$0" "$@"
fi

# Windows 11 Partition
WIN=/dev/disk/by-partuuid/{ここにWindowsパーティションのPARTUUID}

# このスクリプトがあるパス
EFIDIR=$(cd $(dirname "${BASH_SOURCE[0]}") && pwd)

# エラーがあればそこで終了
set -e

# すでに仮想デバイスが作成されていれば終了
if [[ -e /dev/md0 ]]; then
  echo "/dev/md0 already exists"
  exit 1
fi

# Windowsのパーティションがマウントされていたらアンマウント
if mountpoint -q -- "${WIN}"; then
  echo "Unmounting ${WIN}..."
  umount ${WIN}
fi

# 必要なモジュールをロード
modprobe loop
modprobe linear

# ループデバイスの作成
LOOP1=$(losetup -f --show "${EFIDIR}/efi1")
echo "Created loop device: $LOOP1"

LOOP2=$(losetup -f --show "${EFIDIR}/efi2")
echo "Created loop device: $LOOP2"

# RAID array (md0) の作成
echo "Building /dev/md0 ..."
mdadm --build --verbose /dev/md0 --chunk=512 --level=linear --raid-devices=3 ${LOOP1} ${WIN} ${LOOP2}

REAL_USER=${SUDO_USER:-$USER}

echo "Changing ownership to user: $REAL_USER"
chown $REAL_USER:disk /dev/md0

echo "$LOOP1 $LOOP2" > /root/.win11-loop-devices

stop_md0.sh

#!/usr/bin/env bash

if [ "$(id -u)" -ne 0 ]; then
    echo "Requesting root privileges..."
    exec sudo "$0" "$@"
fi

mdadm --stop /dev/md0
xargs losetup -d < /root/.win11-loop-devices
# 作成したスクリプトファイルに実行権限を与える
sudo chmod +X start_md0.sh stop_md0.sh

# 実際に /dev/md0 を作成できるか試す
./start_md0.sh

# /dev/md0 と出てデバイスが存在すればOK
ls /dev/md0

# /dev/md0 を削除
./stop_md0.sh

4. 仮想ストレージデバイスを構成

./start_md0.sh

# /dev/md0 のパーティションを構成
sudo parted /dev/md0

# 単位をセクタに指定
(parted) unit s

# パーティションテーブルをGPTで作成
(parted) mktable gpt

# 2048セクタ から 204799セクタ を FAT32 に指定 (efi1 用)
(parted) mkpart primary fat32 2048 204799

# 204800セクタ から 末尾2049セクタ を NTFS に指定 (Windows 11パーティション用)
(parted) mkpart primary ntfs 204800 -2049

# 1つ目のパーティション(efi1)をEFIとして指定
(parted) set 1 boot on
(parted) set 1 esp on
(parted) name 1 EFI

# 2つ目のパーティション(Windows 11)をWindowsとして指定
(parted) set 2 msftdata on
(parted) name 2 Windows

# parted を抜ける
(parted) quit

./stop_md0.sh

5. WindowsのブートエントリをEFIに書き込む

./start_md0.sh

virt-managerで作業を行う。

QEMU/KVM があることを確認する。
左上のアイコンの「新しい仮想マシンの作成」をクリック。
「接続」をQEMU/KVMにする。 (1つしかないなら選択できない場合もある)
「既存のディスクイメージをインポート」を選択。

「既存のストレージのパス」として/dev/md0を入力。
「インストールするオペレーティングシステム」としてMicrosoft Windows 11を入力(選択)。

メモリとCPUの設定をそのまま通過して、

「インストールの前に設定をカスタマイズする」にチェックを入れて「完了」

「ハードディスクを追加」>「ストレージ」で
「カスタムストレージ」をWindowsインストーラーのiso
「デバイスの種類」を「CD-ROMデバイス」
「バスの種類」を「SATA」
に指定して「完了」

ブートオプションを
「SATA CD-ROM」にチェックを入れ、一番上に指定する。
「適用」をクリック

左上の「インストールの開始」をクリック。

セットアップウィザードが起動したら、言語とキーボードを日本語に設定し、Shift + F10を押してコマンドプロンプトを起動する。

diskpartで仮想パーティションの構成

diskpart

REM ボリュームの一覧を表示
DISKPART> list volume

REM EFIパーティションを選択 (Size に 99MB と書いてあるもの。ここでは Volume 2
DISKPART> select volume 2

REM fat32でフォーマット
DISKPART> format quick fs=fat32 label="EFI"

REM ドライブレターをSとして登録
DISKPART> assign letter=S

REM Windowsパーティションを選択 (Fs に NTFS と書いてあるもの。ここでは Volume 1)
DISKPART> select volume 1

REM ドライブレターをCとして登録
DISKPART> assign letter=C

DISKPART> exit

Windows Boot Managerを書き込み

bcdboot C:\Windows /s S: /f UEFI

ブート ファイルは正常に作成されました。と出れば完了。

「コマンドプロンプト」と「Windows 11 セットアップ」をXボタンをクリックして終了させる。
「本当に終了しますか?」が出たら「はい」をクリック。

待っていたら「デバイスを準備しています」が終わって、勝手に再起動します。

これでWindows 11が起動するはずです。

6. ネットワークの設定

Windowsが起動するのを待っている間にLinux側のファイアウォールの設定をします。
おそらくデフォルトのままではNATや仮想ブリッジデバイスを通してVM側からネットワークにアクセスできません。

/etc/default/ufwのファイルを編集します。

DEFAULT_FORWARD_POLICY="DROP"

# の DROP を ACCEPT に書き換えます。

DEFAULT_FORWARD_POLICY="ACCEPT"

仮想ブリッジデバイス(virbr0)を信頼させます。
デバイス名が違うかもしれませんがip aとかで確認してください。

sudo ufw allow in on virbr0
sudo ufw allow out on virbr0

Windows 11 が起動したらログインしてネットワーク接続を確認します。

REM Windows 11 内
ping <自分のIP>
ping <enp1s0 とか wlan0 のIP>
ping <virbr0 のIP>
ping 1.1.1.1
curl ipinfo.io
# Linux 内
ping <virbr0 のIP>
curl ipinfo.io

7. virtio-guest-toolsを入れる。

virtio-win-0.1.285.isoとかをダウンロードして
cd-romでマウントするなりVM内でisoをダウンロードするなりして、
インストールしてください。
めんどいので詳しくは説明しません。

ついでにWindows 11のCD-ROMデバイスも削除しておいてください。

ドライバーとゲストツールをインストールしてください。どうせWinAppsあるしいらないかもしれないけど

8. パフォーマンス最適化

  • 「NIC」の「デバイスのモデル」をvirtioに変更
  • 「ビデオ」の「モデル」をVirtioに変更
  • 「ハードウェアの追加」から「ストレージ」で新しくVirtIOの「ディスクデバイス」を追加してください。私は1GBで試しましたが、100MBとかでも問題ないと思います。読めればいいので。

WindowsのVMを起動してログオンします。
デバイスマネージャー(devmgmt.msc)を開いて

  • 「ディスクドライブ」にRed Hat VirtIO SCSI Disk Device
  • 「ディスプレイアダプター」にRed Hat VirtIO GPU DOD controller
  • 「ネットワークアダプター」にRed Hat VirtIO Ethernet Adapter

があり、ドライバが当たっていることを確認します。

追加した「VirtIOディスク」を削除し、/dev/md0のディスクを「SATAディスク」から「VirtIOディスク」に置き換えます。
接続方法が選択できるわけではないので、削除して追加します。

WindowsのVMが起動して描画されることを確認します。

WinAppsをいれる

設定 > システム > リモートデスクトップ
で「リモートデスクトップ」を有効化します。

VMの名前をRDPWindowsに変更する。
チャンネルのorg.qemu.guest_agent.0 Unixを追加する。
メモリの現在割当を1024MBにする

<clock>を以下に書き換える

<clock offset='localtime'>
  <timer name='rtc' present='no' tickpolicy='catchup'/>
  <timer name='pit' present='no' tickpolicy='delay'/>
  <timer name='hpet' present='no'/>
  <timer name='kvmclock' present='no'/>
  <timer name='hypervclock' present='yes'/>
</clock>

<hyperv>をこれに書き換える

    <hyperv mode="custom">
      <relaxed state="on"/>
      <vapic state="on"/>
      <spinlocks state="on" retries="8191"/>
      <vpindex state="on"/>
      <runtime state="on"/>
      <synic state="on"/>
      <stimer state='on'>
        <direct state='on'/>
      </stimer>
      <reset state='on'/>
      <frequencies state="on"/>
      <reenlightenment state='on'/>
      <tlbflush state="on"/>
      <ipi state="on"/>
      <evmcs state="on"/>
      <avic state="on"/>
    </hyperv>
echo 'LIBVIRT_DEFAULT_URI="qemu:///system"' | sudo tee -a /etc/environment
# または
echo 'export LIBVIRT_DEFAULT_URI="qemu:///system"' >> ~/.zshrc
# .zshrc は各自のファイルに書き換えてください

再起動するか、
export LIBVIRT_DEFAULT_URI="qemu:///system"
をやって
virsh qemu-agent-command RDPWindows '{"execute":"guest-get-osinfo"}' --pretty
を実行して

{
  "return": {
    "name": "Microsoft Windows",
    "kernel-release": "26200",
    "version": "Microsoft Windows 11",
    "variant": "client",
    "pretty-name": "Windows 10 Pro",
    "version-id": "11",
    "variant-id": "client",
    "kernel-version": "10.0",
    "machine": "x86_64",
    "id": "mswindows"
  }
}

が出たら成功。

RDPApps.reg
install.bat
NetProfileCleanup
https://qiita.com/Targityen/items/3d2e0b5b0b7b04963750

をWindows VMで実行します。

VMを再起動して、
絶対にVMでログオンしないこと

Windowsにログオンしたいときは通常通りWindowsを起動してください。
あと、Windowsのパーティションを絶対にマウントしないでください。
NTFSが死んでWindowsが起動できなくなります。

https://github.com/winapps-org/winapps?tab=readme-ov-file#step-3-create-a-winapps-configuration-file
以下の項目を変更する
RDP_USER = "<ユーザー名>"
RDP_PASS = "<ユーザーパスワード>"
RDP_IP = ""
VM_NAME = RDPWindows (RDPWindows以外にしたならそれを)
WAFLAVOR = libvirt

このコマンドを実行して表示される手順に従えばインストールされます。

bash <(curl https://raw.githubusercontent.com/winapps-org/winapps/main/setup.sh)

日本語設定だと動かないっぽいです。
https://github.com/winapps-org/winapps/issues/457

が、

設定 > 時刻と言語 > 言語と地域 > システムロケールの変更 > ベータ:ワールドワイド言語サポートでUnicode UTF-8を使用
にチェックを入れて再起動すると機能します。

TODO: 続きを書く

参考

Gemini
https://note.com/polymorph_design/n/na9f4689b37c3
https://simgunz.org/posts/2021-12-12-boot-windows-partition-from-linux-kvm/
https://www.reddit.com/r/linuxquestions/comments/18viohx
https://slacknotebook.com/how-to-install-windows-11-on-virt-manager/
https://github.com/winapps-org/winapps/blob/main/docs/libvirt.md

Discussion