Debos 使った Debian ベース Linux OS イメージの構築

# はじめに

Deabin 向けメージ作成ツールには [debootstrap](https://tracker.debian.org/pkg/debootstrap) を始め、[mmdebstrap](https://tracker.debian.org/pkg/mmdebstrap)、[vmdb2](https://tracker.debian.org/pkg/vmdb2)、[ISAR](https://github.com/ilbers/isar)など、複数存在します。
このページでは [collabora](https://www.collabora.com/) で開発されている [apertis](https://apertis.org/) などで利用されている [debos](https://github.com/go-debos/debos) を使った Linux OS イメージ構築方法を紹介します。

# Debos とは
Debos は Debian をベースとする Linux OS イメージ構築ツールです。

特徴としては
 - Goで記述されている
 - YAMLで記述する
 - rootfs だけではなく ブートローダやカーネルを含めた Linux OSイメージを構築できる
 - [OSTree](https://ostreedev.github.io/ostree/) をサポート
 - YAML 内で変数とデフォルト値を定義でき、実行時に変数を変更し、目的に合わせたイメージを構築できる

があります。

OSイメージ手順が書かれたYAMLをパースするエンジンを提供するだけではなく、パッケージのインストール、パ−ティショニングなどを行う `action` が提供されており、イメージ構築手順に合わせてYAMLを記述することで容易に Debian ベースのLinux OS イメージを構築できるようになっています。
```mermaid
flowchart LR
    PR@{ shape: cyl, label: "Debianパッケージ<br>リポジトリ"} -->|パッケージダウンロード| DEBOS
    P@{ shape: stadium, label: "Debianパッケージ" } -->|インストール方法<br>を記述| Y
    S@{ shape: doc, label: "設定ファイル" } -->|インストール方法<br>を記述| Y
    L@{ shape: notch-rect, label: "バイナリファイル"} -->|インストール方法<br>を記述| Y
    Y@{ label: "YAML<br>(イメージ構築方法を記載)"}

    S-->|インストール| DEBOS
    L -->|インストール| DEBOS    
    Y --> DEBOS[Debos]
    DEBOS[Debos] -->|イメージ構築| I@{shape: rounded, label: "OS Image"}

    I -->|ブート| QEMU
    I -->|書き込み| SD@{label: "SDカード"}
    I -->|書き込み| SSD@{label: "SSD"}
    I -->|書き込み| HDD@{label: "HDD"}
```

# 環境構築
Debos は Debian から[パッケージ](https://tracker.debian.org/pkg/debos)として提供されており、APT を用いてインストールできます。

```
$ sudo apt install debos
```

また、Docker コンテナも用意されているので、docker を使ってイメージを構築できます。

```
$ docker pull godebos/debos
```

# イメージ向け `action` と YAML

上記で説明したように、DebosにはOSイメージ構築を容易にするための `action` が提供されています。以下ではよく利用する [`action`](https://pkg.go.dev/github.com/go-debos/debos/actions) の内容と記述例を紹介します。

## debootstrap action

`debootstrap` action は OS のベースイメージ構築するための 必須の action です。[debootstrap](https://tracker.debian.org/pkg/debootstrap) と使い方は同じで、rootfs に利用する suite (Debian コードネーム)、compoonents、mirror (ミラーサーバー)、variant (イメージ構成の種類)などを指定します。

以下の例では、

  - suite に $suite 変数で指定されたもの (bookworm)
  - components に main
  - mirrorに $mirror 変数で指定されたもの (http://deb.debian.org/debian)
  - variant に minibase

を指定する内容となっています。
また、`architecture` は 必須の変数で、作成するDebian アーキテクチャーを指定します。

```yaml:debootstrap action の例
{{- $mirror := or .mirror "http://deb.debian.org/debian" -}}
{{- $suite := or .suite "bookworm" -}}

architecture: amd64

actions:
  - action: debootstrap
    suite: {{ $suite }}
    components:
      - main
    mirror: {{ $mirror }}
    variant: minbase
```

## apt action

`apt` action は `debootstrap` action で指定したパッケージリポジトリから、パッケージをダウンロードし、インストールします。`packages` にインストールしたいパッケージをリストで指定します。
また、`recommends` に `false` を指定することで、インストールするパッケージの `Recommends` に指定されているパッケージのインストールを抑制できます。`update` に `false` を指定することでインストール時に `apt update` を実行しません。

以下の例では

  - [i2c-tools](https://tracker.debian.org/pkg/i2c-tools) パッケージをインストール
  - [i2c-tools](https://tracker.debian.org/pkg/i2c-tools) パッケージの [Recommends に指定されているパッケージ](https://packages.debian.org/bookworm/i2c-tools) (read-edid) をインストールしない
  - パッケージインストール時に `apt update` を実行しない

という内容の `apt` action となっています。

```yaml:apt action の例
- action: apt
  recommends: false
  update: false
  packages:
    - i2c-tools 
```

## run action

`run` action は rootfs 内外でコマンドを実行する action です。`chroot` で rootfs(chroot)内外を指定できます。`command`　で指定されるコマンドやファイルは 実行される rootfs 内外に存在する必要があります。
ディレクトリやファイルの作成、rootfs 外で提供されるツールを参照したコマンド実行などを行う際に利用されます。

以下の例では 

  - chroot 外 (実行環境) で${ROOTDIR}/boot/dtbs ディレクトリを作成

という内容の `run` action となっています。`${ROOTDIR}` は roofs のパスが格納されています。

```yaml:run action の例
- action: run
  chroot: false
  command: mkdir -p ${ROOTDIR}/boot/dtbs
```

## overlay action

`overlay` action は指定したディレクトリまたはファイルを rootfs に再帰的にコピーする action です。
設定ファイルやプログラムなどを rootfs にコピーする際に利用します。

以下の例では
 - overlay/bootloader/ ディレクトリの内容を rootfs 内にコピーする

という内容の `overlay` action となっています。overlay/bootloader/opt が rootfs の /opt にコピーされます。


```shell:overlay/bootloader の内容
$ tree overlay/bootloader
overlay/bootloader/
└── opt
    ├── fip.bin
    └── tf-a-stm32mp157a-dk1.stm32
```

```yaml:overlay action の例
- action: overlay
  source: overlay/bootloader/
```

## image-partition action

`image-partition` action はパーティション構成を作成する action です。USBメモリ、SSD、SDカードなどに書き込むイメージのパーティション構成を作成します。パーティション構成を作成する action になるので、実際に構成した内容をイメージとして反映させるには後述する `filesystem-deploy` action を記述する必要がある点に注意が必要です。

以下の例では
 - 作成するイメージファイル名は `$imagename` で指定（`target-debian-bookworm.img`）
 - 4GB のイメージを作成
 - パーティションタイプは msdos
 - 第1パーティションのサイズは 32MBとし、fat32 フォーマット。名前は firmware
 - 第2パーティションのサイズは 最大サイズ (約 4GB - 32MB )とし、ext4 フォーマット。名前は root。ブートフラッグあり
 - 第1パーティションは /boot/firmware にマウント
 - 第2パーティションは / にマウント

という内容の `image-partition` action となっています。

```yaml:image-partition action の例
{{- $board := or .board "target" -}}
{{- $suite := or .suite "bookworm" -}}
{{- $imagename := or .image (printf "%s-debian-%s.img" $board $suite) -}}

 - action: image-partition
    imagename: {{ $imagename }}
    imagesize: 4GB
    partitiontype: msdos
    mountpoints:
      - mountpoint: /
        partition: root
      - mountpoint: /boot/firmware
        partition: firmware
        options: [ x-systemd.automount ]
    partitions:
      - name: firmware
        fs: fat32
        start: 0%
        end: 32MB
      - name: root
        fs: ext4
        start: 32MB
        end: 100%
        flags: [ boot ]
```

## filesystem-deploy action

`filesystem-deploy` action は `image-partition` action で指定したパーティション構成をファイルシステムイメージにデプロイする action です。`image-partition` action でも説明したように、利用には`image-partition` action が必要となるので、セットで記述するのがよいでしょう。

以下の例では
  - デプロイ時に設定されたパーティション構成を元に `/etc/fstab` を作成する

という内容の `filesystem-deploy` action となっています。

```yaml:filesystem-deploy action の例
  - action: filesystem-deploy
    setup-fstab: true
```

## pack action と unpack action

`pack` action ある時点のイメージを指定された圧縮フォーマットとファイル名で圧縮、`unpack` action は
指定されたファイルを rootfs イメージに展開する action です。`image-partition` action は使用せず、TAR イメージを作る場合やイメージのキャッシュを作成または利用する場合に使用します。

以下の例では
  - `cache_mode` に `unpack` が指定されている場合には `cache_filename` に指定されているファイルをrootfs イメージに展開
  - `cache_mode` に `pack` が指定されている場合には `image-debootstrap` action を使用して rootfs イメージを構築した後、 `cache_filename` に指定されているファイル名として保存

という内容となっています。

```yaml:pack action と unpack action
{{- $mirror := or .mirror "http://deb.debian.org/debian" -}}
{{- $suite := or .suite "bookworm" -}}
{{- $cache_mode := or .cache_mode "unpack" -}}
{{- $cache_filename := or .image (printf "cache-debian-%s.tar.gz" $suite) -}}

architecture: amd64

actions:
{{- if eq $cache_mode "unpack" }}
  - action: unpack
    file: {{ $cache_filename }}
    compression: gz
{{- else }}
  - action: debootstrap
    suite: {{ $suite }}
    components:
      - main
    mirror: {{ $mirror }}
    variant: minbase
{{- if eq $cache_mode "pack" }}
  - action: pack
    file: {{ $cache_filename }}
    compression: gz
{{- end }} #cache_mode: pack  
{{- end }} #cache_mode: unpack
```
## その他の action

上記ではよく利用される action の内容と記述例を紹介しました。これらの他い OSTree 向けの action、 Arch Linux のコマンドである pacmanコマンド向けの action、自身で作成した YAML を実行するための `recipe` アクションなどがあります。興味に有る方は [Debos action](https://pkg.go.dev/github.com/go-debos/debos/actions) を参照ください。

# QEMU / amd64 向け YAMLとイメージ構築方法

上記で紹介した action 用いて作成した QEMU / amd64 向け YAMLが下記にある `qemu-amd64.yaml` となります。`qemu-amd64.yaml` 内では grub 用の設定ファイル `overlays/grub/etc/default/grub` を overlay action から使用しているので、実行する場合このファイルも必要となります。

```yaml:qemu-amd64.yaml
{{- $architecture := or .architecture "amd64" -}}
{{- $suite := or .suite "bookworm" -}}
{{- $imagename := or .imagename (printf "debian-qemu-uefi-%s-%s" $suite $architecture) -}}
{{- $hostname := or .hostname "debian" -}}

{{- $cache_mode := or .cache_mode "pack" -}}
{{- $cache_filename := or .image (printf "cache-debian-%s.tar.gz" $suite) -}}

architecture: {{ $architecture }}

actions:
{{- if eq $cache_mode "unpack" }}
  - action: unpack
    description: Unpack {{ $cache_filename }}
    file: {{ $cache_filename }}
    compression: gz
{{- else }}
  - action: debootstrap
    suite: {{ $suite }}
    components:
      - main
    mirror: https://deb.debian.org/debian
    variant: minbase
{{- if eq $cache_mode "pack" }}
  - action: pack
    description: Pack {{ $cache_filename }}
    file: {{ $cache_filename }}
    compression: gz
{{- end }}
{{- end }}

  - action: apt
    description: Install base packages
    recommends: false
    packages:
      - grub-efi
      - isc-dhcp-client
      - linux-image-amd64
      - systemd-sysv

  - action: run
    description: Set root account
    chroot: true
    command: echo "root:root" | chpasswd

  - action: run
    description: Set host name
    chroot: true
    command: echo {{ $hostname }} > /etc/hostname

  - action: image-partition
    description: Set image partition
    imagename: {{ $imagename }}.img
    imagesize: 4GB
    partitiontype: gpt
    mountpoints:
      - mountpoint: /
        partition: root
      - mountpoint: /boot/efi
        partition: efi
        flags: [ boot, esp ]
    partitions:
      - name: efi
        fs: vfat
        start: 0%
        end: 256MB
        options: [ x-systemd.automount ]
      - name: root
        fs: ext4
        start: 256MB
        end: 100%

  - action: filesystem-deploy
    description: Deploy filesystem

  - action: overlay
    description: Overlay /etc/default/grub
    source: overlays/grub

  - action: run
    description: Install grub
    chroot: true
    command: grub-install --target=x86_64-efi --no-nvram

  - action: run
    description: Update grub
    chroot: true
    command: update-grub

  - action: run
    description: Install grubx64.efi
    chroot: true
    command: mkdir -p /boot/efi/EFI/BOOT && cp /boot/efi/EFI/debian/grubx64.efi /boot/efi/EFI/BOOT/BOOTX64.EFI
```

```text:overlays/grub/etc/default/grub
GRUB_DEFAULT=saved
GRUB_TIMEOUT=5
GRUB_DISTRIBUTOR=`lsb_release -i -s 2> /dev/null || echo Debian`
GRUB_CMDLINE_LINUX_DEFAULT="root=/dev/sda2 rw console=tty0 console=ttyS0,115200n8"
GRUB_CMDLINE_LINUX=""
GRUB_DISABLE_LINUX_UUID=true
```
## debos を使用したイメージの作成

`qemu-amd64.yaml` と `overlays/grub/etc/default/grub` を保存し、debos に `qemu-amd64.yaml`を指定して実行すると、OS イメージとして `debian-qemu-uefi-bookworm-amd64.img`、キャッシュファイルとして `cache-debian-bookworm.tar.gz` が作成されます。

```shell:debos実行
$ debos qemu-amd64.yaml
[...]
$ ls -1
cache-debian-bookworm.tar.gz
debian-qemu-uefi-bookworm-amd64.img
overlays
qemu-amd64.yaml
```

作成された `cache-debian-bookworm.tar.gz` を用いて再度 OS イメージを構築するには debos の `-t`オプションを用いて、`cache_mode` に `unpack` を指定し実行します。この際、`debootstrap` action が処理されないため、構築時間を短縮できます。

```shell:-t cache_mode:unpack を指定して debos 実行
$ debos -t cache_mode:unpack qemu-amd64.yaml
```

```shell:cache_mode に pack を指定している場合
real    1m8.102s
user    1m8.090s                                
sys     0m27.100s 
```

```shell:cache_mode に pack を指定している場合
real    0m45.384s
user    0m42.217s
sys     0m15.769s
```

# QEMU を使った動作確認

作成されたイメージ `debian-qemu-uefi-bookworm-amd64.img` を、QEMUを用いて動作確認してみましょう。以下のように `debian-qemu-uefi-bookworm-amd64.img` を指定してQEMUを実行すると、動作を確認できます。

```shell:QEMU を使った動作確認
$ sudo apt install qemu-system-x86 ovmf
$ qemu-system-x86_64 \
  --bios /usr/share/qemu/OVMF.fd \
  -m 1G \
  -device virtio-scsi-pci \
  -device scsi-hd,drive=hd0 \
  -blockdev driver=file,node-name=hd0,filename=debian-qemu-uefi-bookworm-amd64.img \
  -nographic -serial mon:stdio
```

# まとめ

[debos](https://github.com/go-debos/debos) を使ったLinux OS イメージ構築方法を紹介しました。YAMLで構築方法を管理でき、変数を用いて条件に合わせたイメージを構築、また足りない機能は `recipes` action を用いて拡張できるなど、複数のイメージを構築・管理している人には使いやすい Debian ベースの OS イメージ構築ツールです。 Debian や Ubuntuなど、Debian ベースのイメージを普段使っている方やちょっとDebianを使ってみようかなと思っている方、ぜひ使ってみてください。

# 商標について

- Linuxは、Linus Torvalds 氏の米国およびその他の国における登録商標です
- Debianは、Software in the Public Interest, Inc.が所有する登録商標です
- QEMUは、Fabrice Bellard　の商標です
- Ubuntuは、Canonical Ltd.の登録商標です