virt-installがネットワークインストールを実現している仕組みとlibosinfoの話

## はじめに

`virt-install` は仮想マシンを作成して OS をインストールするためのコマンドです。インストールする OS が Linux の場合は ISO イメージをダウンロードしなくても `--location` に URL を指定することでネットワークから直接インストールすることができます。私が気になったのはこの URL の先のディレクトリ構造です。実行ログから特定のファイルをダウンロードしているのはわかるのですが、いったいどのファイルをダウンロードするのでしょうか？ またどうやってそのファイルを探しているのでしょうか？ ディレクトリ構造に何かしらの決まりがあるのならわかるのですが各ディストリビューションによってディレクトリ構造は異なっているように思えます。この仕組みが分からなかったので調べてみました。

## 仮想マシンの作成例

次のコマンドで仮想マシン（OS は Debian 12）を作成することができます。シリアルコンソールを使っているので、そのままターミナル上でインストール作業を行うことができ、インストール後に `virsh console` から接続することもできます。

```sh
virt-install --name deb12 --osinfo debian12 \
  --vcpus 1 --memory 2048 --disk size=10 --network bridge=br0 --graphics none \
  --extra-args 'console=ttyS0,115200n8 --- console=ttyS0,115200n8' \
  --location 'http://deb.debian.org/debian/dists/bookworm/main/installer-amd64'

# 補足 ローカルにダウンロードした ISO ファイルからインストールする場合
virt-install --name deb12 --osinfo debian12 \
  --vcpus 1 --memory 2048 --disk size=10 --network bridge=br0 --graphics none \
  --extra-args 'console=ttyS0,115200n8 --- console=ttyS0,115200n8' \
  --location '/var/isos/debian-12.0.0-amd64-DVD-1.iso'
```

**補足 インストールの注意点** Debian ではインストールの最後の方に grub のインストール先 (Device for boot loader installation) を聞かれるので、デフォルトの手動 (Enter device manually) ではなく `/dev/vda` を選択してください。

```sh
# 参考 上記で作成した仮想マシンをデータごと削除する場合
virsh destroy deb12; virsh undefine deb12 --remove-all-storage
```

以下は `virt-install` を実行した時の表示されるメッセージの冒頭部分です。`linux` と `initrd.gz` をネットワークからダウンロードしていることがわかります。

```txt
Starting install...
Retrieving 'linux'                                    | 7.6 MB  00:00:12 ...
Retrieving 'initrd.gz'                                |  38 MB  00:01:07 ...
Allocating 'virtinst-fqgrlje6-linux'                  |    0 B  00:00:00 ...
Transferring 'virtinst-fqgrlje6-linux'                |    0 B  00:00:00 ...
Allocating 'virtinst-2xg9sfwe-initrd.gz'              |    0 B  00:00:00 ...
Transferring 'virtinst-2xg9sfwe-initrd.gz'            |    0 B  00:00:00 ...
Allocating 'deb12.qcow2'                              |    0 B  00:00:00 ...
Creating domain...                                    |    0 B  00:00:00
Running text console command: virsh --connect qemu:///system console deb12
Connected to domain 'deb12'
Escape character is ^] (Ctrl + ])
（以下 Linux のブートログが出力される）
```

ロケーションで指定したのは http://deb.debian.org/debian/dists/bookworm/main/installer-amd64 です。URL 先のディレクトリ構造を見ればわかるのですが、結構複雑で最終的に以下のパスのファイルをダウンロードしているのですが、`virt-install` はどうやってこのパスを知ったのでしょうか？

- `current/images/netboot/debian-installer/amd64/linux`
- `current/images/netboot/debian-installer/amd64/initrd.gz`

この謎を解く鍵は `--osinfo debian12` です。`virt-install` は、仮想マシンを作成するための OS のデータベースを参照しており、その情報からファイルのパスを取得しています。

### おまけ `virt-install` の非推奨のオプション

`virt-install` は昔に比べて結構オプション名が変わっています。検索すると古いオプションを使っている例が結構見つかるのでここにメモしておきます。

| 現在のオプション | 古いオプション                       |
| -------------- | ----------------------------------- | 
| `--osinfo`     | `--os-variant`、`--os-type`         |
| `--memory`     | `-r`/`--ram`                       |
| `--metadata`   | `-u`/`--uuid`、`--description`     |
| `--cdrom <ISO> --install no_install=yes` | `--live` |
| `--disk` | `-f`/`--file`、`-s`/`--file-size`<br>`--nonsparse`、`--nodisks` |
| `--network` | `-m`/`--mac`、`-b`/`--bridge`、`--nonetworks` |
| `--graphics` | `--vnc`、`--vncport`、`--vnclisten`<br>`-k/--keymap`、`--sdl`、`--nographics` |
| `--virt-type` （デフォルトの動作） | `--accelerate` |
| `--sound` | `--soundhw` |

## libosinfo (osinfo) とは？

libosinfo とは、[公式サイト](https://libosinfo.org/)、[プロジェクトページ](https://gitlab.com/libosinfo/)より仮想マシンの作成を支援するための OS に関するデータベースと API を提供するプロジェクトとのことです。`--osinfo` を指定すると、指定した OS にとって適切なデフォルト設定が決定されるようです。以下は `virt-install --help` の出力の一部です。

```console
$ virt-install --help
　︙
OS options:
  --os-variant OS_VARIANT, --osinfo OS_VARIANT
                        The OS being installed in the guest.
                        This is used for deciding optimal defaults like VirtIO.
                        Example values: fedora29, rhel7.0, win10, ...
                        Use '--osinfo list' to see a full list.
　︙
```

補足ですが、`virt-install` の一部のオプションは `?` を指定すると、そのオプションが持っているサブオプションを出力します。

```console
$ virt-install --osinfo=? # --osinfo のパラメータ
--os-variant options:
  detect
  id
  name
  require
  short-id
```

対応している OS の種類は `virt-install --osinfo list` で出力することができますが、その出力の最後に追加の情報は `osinfo-query os` で調べることが出来ると書いてあります。

```console
$ virt-install --osinfo list # 対応している OS の種類の出力
almalinux9
almalinux8
alpinelinux3.18
alpinelinux3.17
　︙
winvista
winxp

You can see additional information with:

  osinfo-query os
```

## osinfo-query コマンドとは？

`osinfo-query` コマンドを使うと以下のような情報を得られます。

```console
$ osinfo-query os
 Short ID        | Name               | Version | ID
-----------------+--------------------+---------+-----------------------------------------
 almalinux8      | AlmaLinux 8        | 8       | http://almalinux.org/almalinux/8
 almalinux9      | AlmaLinux 9        | 9       | http://almalinux.org/almalinux/9
 alpinelinux3.10 | Alpine Linux 3.10  | 3.10    | http://alpinelinux.org/alpinelinux/3.10
 alpinelinux3.11 | Alpine Linux 3.11  | 3.11    | http://alpinelinux.org/alpinelinux/3.11
　︙
```

引数を指定するとリリース日やサポート終了日を調べることできるので便利ですが、なんとなく最新データをすぐに反映したりはしていなさそうな気がします。

```console
$ osinfo-query os vendor="Debian Project" -s release-date \
  -f short-id,vendor,release-date,eol-date

 Short ID             | Vendor                    | Release date | End of life
----------------------+---------------------------+--------------+-------------
 debian1.2            | Debian Project            | 1996-12-12   | 1998-06-05
 debian1.3            | Debian Project            | 1997-06-05   | 1999-03-09
 debian2.0            | Debian Project            | 1998-07-24   | 2000-03-09
 debian2.1            | Debian Project            | 1999-03-09   | 2000-10-30
 debian2.2            | Debian Project            | 2000-08-15   | 2003-06-30
 debian3              | Debian Project            | 2002-07-19   | 2006-06-30
 debian3.1            | Debian Project            | 2005-06-06   | 2008-03-31
 debian4              | Debian Project            | 2007-04-08   | 2010-02-15
 debian5              | Debian Project            | 2009-02-14   | 2012-02-06
 debian6              | Debian Project            | 2011-02-06   | 2016-02-29
 debian7              | Debian Project            | 2013-05-04   | 2018-05-31
 debian8              | Debian Project            | 2015-04-25   | 2020-06-30
 debian9              | Debian Project            | 2017-06-17   | 2022-06-30
 debian10             | Debian Project            | 2019-07-06   |
 debian11             | Debian Project            | 2021-08-14   |
 debian12             | Debian Project            |              |
 debiantesting        | Debian Project            |              |
 debian1.1            | Debian Project            | 1996-07-17   | 1997-06-05

（release-dateでソートしたはずなのになぜdebian1.1は最後にでてるの？）
```

`virt-install` は、このような libosinfo の情報を使っており、`virt-install` 自身で OS のデータベースを持っているわけでありません。とは言うものの現実はそんなに簡単な話ではないようで、詳しく読んではいませんが、[こんな感じ](https://github.com/virt-manager/virt-manager/blob/7cd6151a212c4c477a947fe5a7f2b3363dd0dcbd/virtinst/install/urldetect.py#L855)で基本は libosinfo の情報を取得するものの一部の OS では例外対応が行われているような気がします。

```python
def _build_distro_list(osobj):
    allstores = [
        # Libosinfo takes priority
        _LibosinfoDistro,
        _FedoraDistro,
        _RHELDistro,
        _CentOSDistro,
        _SLESDistro,
        _SLEDDistro,
        _OpensuseDistro,
        _DebianDistro,
        _UbuntuDistro,
        _MageiaDistro,
        # Always stick GenericDistro at the end, since it's a catchall
        _GenericTreeinfoDistro,
    ]
```
## osinfo-db の中身を見てみる

libosinfo が持っている OS の情報は Debian では `/usr/share/osinfo/os/` 以下にあります。

```console
$ ls -al /usr/share/osinfo/os/
total 216
drwxr-xr-x 54 root root 4096 Jun 24 12:29 .
drwxr-xr-x  8 root root 4096 Jun 24 12:29 ..
drwxr-xr-x  2 root root 4096 Jun 24 12:29 almalinux.org
drwxr-xr-x  2 root root 4096 Jun 24 12:29 alpinelinux.org
drwxr-xr-x  2 root root 4096 Jun 24 12:29 altlinux.org
　︙
```

この中には XML ファイルが配置されていますが、それと同じ情報を [osinfo-db](https://gitlab.com/libosinfo/osinfo-db/-/tree/main/data/os) から参照することができます。どうやら昔はこの情報は libosinfo に含まれていたようですがデータを独立したプロジェクトに分離したようです。試していませんがこのデータをローカルにダウンロードして、環境変数 `OSINFO_SYSTEM_DIR` （古い名前 `OSINFO_DATA_DIR`）を指定すれば最新のデータを使える気がします。古い環境で新しい OS をインストールしたいときに使えるかもしれません。詳細は [libosinfo: The Operating System information database](ttps://libosinfo.org/download.html) に書かれています。たぶん ` osinfo-db-import` コマンドを使います。

debian12 のデータである [debian-12.xml.in](https://gitlab.com/libosinfo/osinfo-db/-/blob/d7e68fca845f52628c2e107d1b94798a4b702ff2/data/os/debian.org/debian-12.xml.in#L161
) を見てみると、次のようにダウンロードするファイルのパスが書かれていることがわかります。どうやらここから kernel と initrd のパスを取得しているようです。

```xml
    <tree arch="x86_64">
      <url>http://deb.debian.org/debian/dists/bookworm/main/installer-amd64</url>
      <kernel>current/images/netboot/debian-installer/amd64/linux</kernel>
      <initrd>current/images/netboot/debian-installer/amd64/initrd.gz</initrd>
    </tree>
```

ここまでくれば後は XML ファイルを読むだけです。いろんな情報が書かれていることがわかります。この情報を見れば `--location` にどの URL を指定すれば良いかもわかります。所で上記の `<url>` のアドレスは（おそらく）`virt-install` では使われません。`--ososinfo` の値からこの `<url>` のアドレスをデフォルトで選んでくれても良い気もしますが、きっと負荷を減らすために同じ構造のミラーを使えということなのでしょう。

さて各 OS の XML からある事実がわかります。それは少なくとも現時点では `--location` を使ったインストールは Linux 以外では対応していないということです。その他の OS では `<kernel>` や `<initrd>` 相当のデータが含まれていません。Linux でも一部は対応していないようです。したがって他の OS ではおそらくシリアルコンソールを使ったインストールは難しそうです。何かしら方法はあるかもしれませんが Linux のように簡単にはいかないと思います。

## osinfo-detect コマンドについて

`osinfo-detect` コマンドは libosinfo に付属しているちょっと便利なツールです。指定した ISO ファイルが起動可能かそして（対応していれば）それがどの OS のインストーラーであるかを調べることができます。PATH または URI を指定すると、指定されたディレクトリ構造がどの OS のインストーラーであるかを調べることもできます。 PATH または URI を指定した場合、どうやら `.treeinfo`（隠しファイルで見えない場合がある） または `treeinfo` ファイルがあることを確認しているようです。

```console
$ osinfo-detect debian-12.0.0-amd64-DVD-1.iso
Media is bootable.
Media is an installer for OS 'Debian 12 (x86_64)'

$ osinfo-detect FreeBSD-12.4-RELEASE-amd64-disc1.iso
Media is bootable.
Media is an installer for OS 'FreeBSD 12.4 (x86_64)'
$ osinfo-detect FreeBSD-13.2-RELEASE-amd64-disc1.iso # 起動可能かしかわからない
Media is bootable.

$ osinfo-detect --type tree 'https://repo.almalinux.org/almalinux/9/BaseOS/x86_64/os/'
Tree is an installer for OS 'AlmaLinux 9 (x86_64)'

$ curl 'https://repo.almalinux.org/almalinux/9/BaseOS/x86_64/os/.treeinfo'
[checksums]
images/boot.iso = sha256:f501de55f92e59a3fcf4ad252fdfc4e02ee2ad013d2e1ec818bb38052bcb3c32
images/efiboot.img = sha256:7239e305f99a67fb41068b92f29d030f8b44d6775df00a3de509cb620cefd89d
images/install.img = sha256:5b8202efb6f679a273fdd9b52d7b1e092fcd36d056980939e29a1b6087415d1f
images/pxeboot/initrd.img = sha256:d134cc209cd17a6764ba7d17846742a320da27850c070dc6c2d4372fe273e82d
images/pxeboot/vmlinuz = sha256:c09cd123fd96d36e565a5983d22f448f03be675f84f2e6d20be6579087102338

[general]
arch = x86_64
family = AlmaLinux
name = AlmaLinux 9
packagedir = Packages
platforms = x86_64,xen
repository = .
timestamp = 1683740651
variant = BaseOS
variants = AppStream,BaseOS
version = 9

[header]
type = productmd.treeinfo
version = 1.2

[images-x86_64]
boot.iso = images/boot.iso
efiboot.img = images/efiboot.img
initrd = images/pxeboot/initrd.img
kernel = images/pxeboot/vmlinuz

[images-xen]
initrd = images/pxeboot/initrd.img
kernel = images/pxeboot/vmlinuz

[release]
name = AlmaLinux
short = AlmaLinux
version = 9

[stage2]
mainimage = images/install.img

[tree]
arch = x86_64
build_timestamp = 1683740651
platforms = x86_64,xen
variants = AppStream,BaseOS

[variant-AppStream]
id = AppStream
name = AppStream
packages = ../../../AppStream/x86_64/os/Packages
repository = ../../../AppStream/x86_64/os
type = variant
uid = AppStream

[variant-BaseOS]
id = BaseOS
name = BaseOS
packages = Packages
repository = .
type = variant
uid = BaseOS
```

## osinfo-install-script コマンドについて

`osinfo-install-script` コマンドはおそらく自動インストールファイル（kickstart script など）を生成するプログラムです。（まだ？）使っておらず、EXAMPLE USAGE を実行してもエラーが出たので、使い方はよくわかっていません。このようなものが有るとだけ紹介しておきます。まだ調べてないのでよくわかりませんが、デフォルトで JEOS スタイルのスクリプトを生成するといえば知っている人には理解できるのではないかと思います。どなたか使い方をまとめてください。

## さいごに

`virt-install` で URL を指定してインストールできることはわかるのですが、どのような URL を指定すればいいのか、どのようなディレクトリ構造なのか、その仕組みが分からずずっとモヤモヤしていましました。ちょうど仮想マシンを再構築する機会ができたのでまとめてみました。