Anaconda カスタマイズガイド
インストーラーのカスタマイズと機能強化
概要
1. Anaconda のカスタマイズについて
- ブートメニュー - 事前設定のオプション、カラースキームおよび背景
- グラフィカルインターフェースの外観 - ロゴ、背景、製品名
- インストーラーの機能 - アドオン。これは、グラフィカルおよびテキストのユーザーインターフェースに新たな Kickstart コマンドと画面を追加することでインストーラーの機能を強化します。
重要
genisoimage など) 使用するツールやアプリケーションが異なり、手順を調節する必要がある場合があります。
2. ISO イメージを使った作業
2.1. Red Hat Enterprise Linux ブートイメージの抽出
手順1 ISO イメージの抽出
- ダウンロードしたイメージをマウントします。
#mount -t iso9660 -o loop path/to/image.iso /mnt/isopath/to/image.iso をダウンロードした ISO へのパスで置き換えます。ターゲットとするディレクトリー (/mnt/iso) が存在し、そこに他のものがマウントされていないことを確認します。 - ISO イメージのコンテンツを配置する作業ディレクトリーを作成します。
$mkdir /tmp/ISO - マウントしたイメージの全コンテンツを作業ディレクトリーにコピーします。
-pオプションを使ってファイルおよびディレクトリーのパーミッションと所有権を保持するようにしてください。#cp -pRf /mnt/iso /tmp/ISO - イメージをアンマウントします。
#umount /mnt/iso
/tmp/ISO に抽出され、ここでコンテンツの修正ができます。「ブートメニューのカスタマイズ」 または 「インストーラーアドオンの開発」 に進みます。変更を加えたら、「カスタムブートイメージの作成」 の指示に従って新規の修正済み ISO イメージを作成します。
2.2. product.img ファイルの作成
product.img イメージファイルは、インストーラーのランタイムに既存ファイルを置き換える、または新たなファイルを追加する、ファイルを含むアーカイブです。起動中の Anaconda はブートメディアの images/ ディレクトリーからこのファイルを読み込みます。そしてこのファイル内にあるファイルを使用してインストーラーのファイルシステム内にある同一名のファイルを置換します。インストーラーのカスタマイズにはこれが必要になります (例えば、デフォルトのイメージをカスタムのもので置き換える場合)。product.img イメージに含まれるディレクトリーは、インストーラーと同じディレクトリー構造である必要があります。
表1 アドオンおよび Anaconda Visuals の場所
| カスタムコンテンツのタイプ | ファイルシステムの場所 |
|---|---|
| Pixmaps (ロゴ、サイドバー、トップバー、など) | /usr/share/anaconda/pixmaps/ |
| インストール進捗画面のバナー | /usr/share/anaconda/pixmaps/rnotes/en/ |
| GUI スタイルシート | /usr/share/anaconda/anaconda-gtk.css |
| Installclasses (製品名変更用) | /run/install/product/pyanaconda/installclasses/ |
| Anaconda アドオン | /usr/share/anaconda/addons/ |
product.img ファイルを作成します。
手順2 product.img の作成
/tmpなどの作業ディレクトリーに移動し、product/という名前のサブディレクトリーを作成します。$cd /tmp$mkdir product/- 置換するファイルの場所と同じディレクトリー構造を作成します。例えば、アドオンをテストする場合、これがインストールシステムの
/usr/share/anaconda/addonsに配置されているとすると、作業ディレクトリー内で同じ構造を作成します。$mkdir -p product/usr/share/anaconda/addons注記
インストーラーのランタイムファイルシステムをブラウズするには、インストールを起動し、仮想コンソール1 に切り替え(Ctrl+Alt+F1) その後に 2 つ目の tmux ウィンドウに切り替えます (Ctrl+b 2)。これでファイルシステムをブラウズするためのシェルプロンプトが開きます。 - カスタマイズしたファイル (この例では、Anaconda 用のカスタムアドオン) を新規作成したディレクトリーに配置します。
$cp -r ~/path/to/custom/addon/ product/usr/share/anaconda/addons/ - インストーラーに追加するすべてのファイルについて、上記の 2 つのステップを繰り返します (ディレクトリー構造の作成および変更済みファイルの移動)。
product/ディレクトリーに移動し、product.imgアーカイブを作成します。$cd product$find . | cpio -c -o | gzip -9cv > ../product.imgこれでproduct.imgファイルがproduct/ディレクトリーの 1 つ上のレベルに作成されます。product.imgファイルを抽出した ISO イメージのimages/ディレクトリーに移動します。
product.img ファイルはインストーラーの起動時に自動的に読み込まれます。
注記
product.img ファイルを追加する代わりに、このファイルを別の場所に配置して、ブートメニューで inst.updates= ブートオプションを使用してこれを読み込むこともできます。この場合、イメージファイルの名前は好きなものにすることができ、配置場所はどこでも構いません (USB フラッシュドライブ、ハードディスク、HTTP、FTP または NFS サーバーなど。ただし、インストールシステムからアクセスできること)。
2.3. カスタムブートイメージの作成
手順3 ISO イメージの作成
- 加えた変更がすべて作業ディレクトリーに含まれていることを確認します。例えば、アドオンをテストする場合は、
product.imgをimages/ディレクトリーに配置します。 - 作業ディレクトリーが抽出した ISO イメージのトップレベルのディレクトリーであることを確認します。例えば、
/tmp/ISO/iso。 genisoimageを使って新規 ISO イメージを作成します。#genisoimage -U -r -v -T -J -joliet-long -V "RHEL-7.1 Server.x86_64" -volset "RHEL-7.1 Server.x86_64" -A "RHEL-7.1 Server.x86_64" -b isolinux/isolinux.bin -c isolinux/boot.cat -no-emul-boot -boot-load-size 4 -boot-info-table -eltorito-alt-boot -e images/efiboot.img -no-emul-boot -o ../NEWISO.iso .上記の例を以下で説明します。- オプションに
LABEL=ディレクティブを使用する場合は同一ディスク上のファイルを読み込む場所が必要となります。この場合、-V、-volset、および-Aの各オプションの値がイメージのブートローダー設定と一致するようにします。ブートローダー設定 (BIOS ではisolinux/isolinux.cfg、UEFI ではEFI/BOOT/grub.cfg) でinst.stage2=LABEL=disk_labelの節を使用して同一ディスクからインストーラーの第 2 ステージを読み込む場合は、ディスクのラベルが一致する必要があります。重要
ブートローダー設定ファイルでは、ディスクラベルの空白をすべて\x20で置き換えます。例えば、RHEL 7.1というラベルの ISO イメージを作成する場合は、ブートローダー設定ではRHEL\x207.1を使ってこのラベルを参照します。 -oオプションの値 (-o ../NEWISO.iso) を新規イメージのファイル名で置き換えます。この例の値では、NEWISO.isoというファイルを現在のディレクトリーの上のディレクトリーに作成します。
このコマンドに関する詳細は、genisoimage(1)man ページを参照してください。- MD5 チェックサムをイメージに埋め込みます。このステップを実行しないと、イメージ検証チェック (ブートローダー設定の
rd.live.checkオプション) に失敗し、インストールを続行できなくなります。#implantisomd5 ../NEWISO.isoこの例では、../NEWISO.iso をこの前のステップで作成した ISO イメージのファイル名と場所で置き換えます。
4. グラフィカルユーザーインターフェースのブランド化と色調節
product.img ファイルを作成し、これに installclass (インストーラーで表示される製品名を変更する) と独自のブランド化資料を格納します。この product.img ファイルはインストールイメージではありません。これは完全インストール ISO イメージを補完するもので、カスタマイズを読み込んで使用することで、デフォルトでブートイメージに含まれているファイルを上書きします。
product.img ファイルの作成、およびこのファイルを ISO イメージに追加する方法については、「ISO イメージを使った作業」 を参照してください。
4.1. グラフィカル要素のカスタマイズ
/usr/share/anaconda/pixmaps/ ディレクトリーに保存されています。このディレクトリーには以下のファイルが格納されています。
pixmaps ├─ anaconda-selected-icon.svg ├─ dialog-warning-symbolic.svg ├─ right-arrow-icon.png ├─ rnotes │ └─ en │ ├─ RHEL_7_InstallerBanner_Andreas_750x120_11649367_1213jw.png │ ├─ RHEL_7_InstallerBanner_Blog_750x120_11649367_1213jw.png │ ├─ RHEL_7_InstallerBanner_CPAccess_CommandLine_750x120_11649367_1213jw.png │ ├─ RHEL_7_InstallerBanner_CPAccess_Desktop_750x120_11649367_1213jw.png │ ├─ RHEL_7_InstallerBanner_CPAccess_Help_750x120_11649367_1213jw.png │ ├─ RHEL_7_InstallerBanner_Middleware_750x120_11649367_1213jw.png │ ├─ RHEL_7_InstallerBanner_OPSEN_750x120_11649367_1213cd.png │ ├─ RHEL_7_InstallerBanner_RHDev_Program_750x120_11649367_1213cd.png │ ├─ RHEL_7_InstallerBanner_RHELStandardize_750x120_11649367_1213jw.png │ └─ RHEL_7_InstallerBanner_Satellite_750x120_11649367_1213cd.png ├─ sidebar-bg.png ├─ sidebar-logo.png └─ topbar-bg.png
/usr/share/anaconda/ ディレクトリーには anaconda-gtk.css という名前の CSS スタイルシートが格納されており、これがファイル名や、ロゴおよびサイドバー/トップバーの背景といったメインの UI 要素のパラメーターを決定します。このファイルには以下のコンテンツが含まれます。
/* vendor-specific colors/images */
@define-color redhat #021519;
/* logo and sidebar classes for RHEL */
.logo-sidebar {
background-image: url('/usr/share/anaconda/pixmaps/sidebar-bg.png');
background-color: @redhat;
background-repeat: no-repeat;
}
.logo {
background-image: url('/usr/share/anaconda/pixmaps/sidebar-logo.png');
background-position: 50% 20px;
background-repeat: no-repeat;
background-color: transparent;
}
AnacondaSpokeWindow #nav-box {
background-color: @redhat;
background-image: url('/usr/share/anaconda/pixmaps/topbar-bg.png');
background-repeat: no-repeat;
color: white;
}
AnacondaSpokeWindow #layout-indicator {
color: black;
}
@define-color の行で一致する背景色を定義します。このため、背景の イメージ は背景色に「紛れる」ことになり、すべての解像度において背景はイメージを拡大縮小せずに機能します。
background-repeat パラメーターを変更して背景をタイル表示にしたり、インストールしているすべてのシステムで同一の解像度を使用することが分かっている場合は、バー全体を占める背景イメージを使用することもできます。
rnotes/ ディレクトリーにはバナーのセットを格納します。インストール中は、ほぼ 1 分ごとにバナー画像が画面下部で循環します。
product.img を作成し、「カスタムブートイメージの作成」 にあるように変更を含めた新規の起動可能な ISO イメージを作成します。
4.2. 製品名のカスタマイズ
custom.py という名前の新規ファイルを作成します。
例1 カスタム Installclass の作成
from pyanaconda.installclass import BaseInstallClass
from pyanaconda.product import productName
from pyanaconda import network
from pyanaconda import nm
class CustomBaseInstallClass(BaseInstallClass):
name = "My Distribution"
sortPriority = 30000
if not productName.startswith("My Distribution"):
hidden = True
defaultFS = "xfs"
bootloaderTimeoutDefault = 5
bootloaderExtraArgs = []
ignoredPackages = ["ntfsprogs"]
installUpdates = False
_l10n_domain = "comps"
efi_dir = "redhat"
help_placeholder = "RHEL7Placeholder.html"
help_placeholder_with_links = "RHEL7PlaceholderWithLinks.html"
def configure(self, anaconda):
BaseInstallClass.configure(self, anaconda)
BaseInstallClass.setDefaultPartitioning(self, anaconda.storage)
def setNetworkOnbootDefault(self, ksdata):
if ksdata.method.method not in ("url", "nfs"):
return
if network.has_some_wired_autoconnect_device():
return
dev = network.default_route_device()
if not dev:
return
if nm.nm_device_type_is_wifi(dev):
return
network.update_onboot_value(dev, "yes", ksdata)
def __init__(self):
BaseInstallClass.__init__(self)
class CustomBaseInstallClass(BaseInstallClass):
name = "My Distribution"
sortPriority = 30000
if not productName.startswith("My Distribution"):
hidden = True
sortPriority 属性を 20000 以上に設定し、新規の installation class が最初に読み込まれるようにします。
警告
product.img ファイルを作成し、「カスタムブートイメージの作成」 にあるように変更を含めた新規の起動可能な ISO ファイルを作成します。
5. インストーラーアドオンの開発
5.1. Anaconda とアドオンについて
5.1.1. Anaconda の概要
Gtk ウィジェット (C で作成)、systemd ユニット、および dracut ライブラリーといったファイルも含まれています。これらが一体となることで、このツールを使用するとターゲットとなるシステムのパラメーターを設定でき、そのシステムをマシンにセットアップします。インストールプロセスは以下の 4 つの主要ステップで構成されます。
- インストール先の準備 (通常はディスクのパーティション設定)
- パッケージおよびデータのインストール
- ブートローダーのインストールと設定
- 新規にインストールされたシステムの設定
VNC を使ったリモートアクセスもサポートしており、これを使用するとグラフィックスカードやモニターのないシステムでも GUI の使用が可能になります。ただし、この方法が望ましくない場合もあり、また対話式インストールを希望する場合もあるでしょう。そのようなケースでは、テキストモード (TUI) が使用できます。TUI はモノクロのラインプリンターのように動作し、カーソルやカラー、他の高度な機能に対応していないシリアルコンソールでも機能します。テキストモードは、ネットワーク設定や言語オプション、インストール (パッケージ) ソースなどの非常に一般的なオプションしかカスタマイズできないという制限があります。手動によるパーティション設定といったような高度な機能はこのインターフェースでは利用できません。
5.1.2. 初回起動と初期設定
Gtk2 や pygtk2などの既にメンテナンス対象外となったツールに依存しています[1]。このため、新規ツールである Initial Setup が開発されました。これは Anaconda からのコードを再利用します。このため、Anaconda 用に開発されたアドオンがスムーズに Initial Setup で再利用できます。これについては 「Anaconda アドオンの作成」 で詳述しています。
5.1.3. Anaconda と初期設定アドオン
%addon com_redhat_kdump コマンドとそのオプションを使用)、テキストベースおよびグラフィカルインターフェースで追加画面として完全に統合されます。本ガイドで詳述している手順を使用すると、他のアドオンも同様に開発して、デフォルトのインストーラーに追加することができます。
5.1.4. 追加情報
- Fedora Project Wiki の Anaconda ページ にはインストーラーについての詳細情報があります。
- Anaconda を現行バージョンに開発することについての情報は、Anaconda/NewInstaller Wiki page にあります。
- 『Red Hat Enterprise Linux 7 インストールガイド』 の キックスタートを使ったインストール の章には Kickstart についての完全なドキュメントがあり、サポートされているコマンドとオプションの全一覧もあります。
- 『Red Hat Enterprise Linux 7 インストールガイド』 の Anaconda を使用したインストール の章では、グラフィカルおよびテキストユーザーインターフェースでのインストールプロセスを説明しています。
- インストール後の設定に使用するツールについての情報は、初期設定 の章を参照してください。
5.2. Anaconda のアーキテクチャー
pykickstart- Kickstart ファイルを解析、検証するとともに、インストールを実行する値を保存するデータ構造も提供します。yum- パッケージのインストールと依存関係の解決を処理するパッケージマネージャーです。blivet- 元々は anaconda パッケージから pyanaconda.storage として分岐したものでした。ストレージ管理に関するアクティビティーを処理します。pyanaconda- キーボードとタイムゾーンの選択、ネットワーク設定、ユーザー作成、さらには多数のユーティリティーやシステム指向の機能など、Anaconda 固有の機能向けのユーザーインターフェースやモジュールを含むパッケージです。python-meh- クラッシュ時に追加のシステム情報を収集、保存し、この情報をlibreportライブラリーに渡す例外ハンドラーを含みます。このライブラリー自体は ABRT Project の一部です。
pykickstart モジュールがこれを処理してツリー構造としてメモリーにインポートします。Kickstart ファイルが提供されない場合は、代わりに空のツリー構造が作成されます。インストールが対話式の場合 (必須の Kickstart コマンドの一部しか使用されなかった場合)、この構造は対話式インターフェースでユーザーが選択したもので更新されます。
/root/ ディレクトリーに保存されます。このため、この自動生成の Kickstart ファイルを再使用することで、このインストールを自動複製することが可能になります。
pyanaconda.kickstart モジュールからの修正されたバージョンでいくつかは上書きすることができます。この動作を決定する重要なルールは、選択データの保存場所がなく、インストールプロセスはデータ駆動型であり、最大限この処理に依存しているということです。このため、以下の点が確保されます。
- インストーラーの全機能が Kickstart でサポートされる 必要がある
- インストールプロセスで、変更がターゲットシステムに書き込まれる単一の明確な時点がある。この時点の前では、永続的な変更 (例: ストレージのフォーマット) はなされません
- ユーザーインターフェースでなされた手動での変更は作成される Kickstart ファイルに反映され、複製が可能
setup メソッド)、それを実行することで (execute メソッド) ターゲットシステム上で変更がなされます。これらのメソッドについては、「Anaconda アドオンの作成」 で詳述しています。
5.3. ハブ & スポークモデル
- ユーザーは画面上でインストールを進める際に決まった順序に従う必要がない。
- 設定するオプションについて理解しているかどうかに関わらず全画面を開く必要がない。
- 特定のボタンがクリックされるまで、希望する値を設定してもマシンには実際には何もされないというトランザクションモードが機能する。
- 設定された値の概要を表示する方法がある。
- 並び替えや複雑な並び順の依存関係を解決せずに新たなスポークをハブに追加できるので、拡張性が高い。
- インストーラーのグラフィカルとテキストと両方のモードに使用可能。
図2 ハブ & スポークモデルの図
注記
- インストールの概要 ハブ。インストール前に設定したオプションの概要を表示します。
- 設定および進捗状況 ハブ。インストールの概要 で インストールの開始 をクリックすると表示され、インストールプロセスの進捗状況が確認できるほか、追加オプションの設定ができます (root パスワードの設定やユーザーアカウントの作成など)。
- ready - スポークが開けるかどうかを指定します。例えば、あるパッケージソースをインストーラーが設定している場合、そのスポークは準備ができておらずグレー表示され、設定が完了するまでアクセスできません。
- completed - スポークが完了 (必須の値がすべて設定済み) か未完了かをマークします。
- mandatory - インストールの続行にそのスポークを開いてユーザーが確認する 必要がある かどうかを決定します。例えば、自動ディスクパーティション設定を使用する場合でも、インストール先 スポークは開く必要があります。
- status - スポーク内で設定された値の概要を提供します (ハブのスポーク名の下に表示)。
5.4. スレッドと連絡
GLib.idle_add を使うことですが、これは必ずしも容易ではなく、推奨されません。この問題を緩和するために、pyanaconda.ui.gui.utils モジュール内でいくつかのヘルパー関数とデコレーターが定義されています。
@gtk_action_wait と @gtk_action_nowait デコレーターです。これらは、装飾された関数やメソッドを変更し、これらが呼び出されると自動的に Gtk のメインループにキュー登録され、戻り値が発信者に返されるか切断されるようにします。
hubQ と呼ばれるメッセージキューで処理され、メインイベントループで定期的にチェックされます。スポークがアクセス可能になると、その旨とブロックされないべきであるとのメッセージをこのキューに送信します。
progressQ と呼ばれる別のキューがあり、インストールの進捗状況更新を送信する役割を果たします。
5.5. Anaconda アドオンの構造
__init__.py と他のソースディレクトリー (サブパッケージ) を格納しているディレクトリーが含まれています。Python は各パッケージ名のインポートを 1 回しか許可しないので、パッケージのトップレベルのディレクトリー名は一意のものである必要があります。同時に、アドオンはその名前に関係なく読み込まれるため、名前は任意のものにすることができます。唯一の要件は、特定のディレクトリー内に格納される必要があるということです。
_) を使用することでディレクトリー名が Python パッケージの有効な識別子となります。この規則を適用したディレクトリー名の例は、com_example_hello_world となります。この規則は、Python パッケージおよびモジュール名の recommended naming scheme (推奨命名スキーム) に準拠しています。
重要
__init__.py ファイルを作成することを忘れないでください。このファイルがないディレクトリーは、有効な Python パッケージとはみなされません。
ks、グラフィカルインターフェースでは gui、テキストベースのインターフェースでは tui と命名する必要があります。gui と tui のパッケージは、spokes サブパッケージを格納している必要もあります。[3]
ks/、gui/ および tui/ のディレクトリー内の Python モジュール名はどんなものでも構いません。
例2 アドオン構造のサンプル
com_example_hello_world ├─ ks │ └─ __init__.py ├─ gui │ ├─ __init__.py │ └─ spokes │ └─ __init__.py └─ tui ├─ __init__.py └─ spokes └─ __init__.py
5.6. Anaconda アドオンの作成
5.6.1. キックスタートのサポート
com_example_hello_world/ks/ ディレクトリーに移動し、__init__.py ファイルがあることを確認して、さらに hello_world.py という名前の Python スクリプトを追加します。
%addon ステートメントで開始され、%end で終了します。%addon 行にはアドオンの名前 (%addon com_example_hello_world など) と、オプションで引数一覧も含めます (アドオンがこれらに対応している場合)。
例3 Kickstart ファイルでのアドオンの使用
%addon ADDON_NAME [arguments] first line second line ... %end
AddonData と呼ばれるものです。このクラスは pyanaconda.addons 内で定義され、Kickstart ファイルからのデータを解析、保存するオブジェクトを表します。
AddonData クラスから継承したアドオンクラスのインスタンスにリストとして渡されます。最初の行と最後の行の間にあるものはすべて、一度に一行ずつアドオンのクラスに渡されます。Hello World のアドオンサンプルをシンプルにするために、このブロック内のすべての行を単一行にまとめ、元の行を空白で区切ります。
%addon 行からの引数リストの処理のメソッドとセクション内の行を処理するメソッドのあるクラスを AddonData から継承する必要があります。pyanaconda/addons.py モジュールにはこれに使用可能な以下の 2 つのメソッドが含まれています。
handle_header-%addon行のリスト (およびエラー報告用の行番号) を取ります。handle_line-%addonと%endのステートメント間のコンテンツの単一行を取ります。
例4 handle_header と handle_line の使用
from pyanaconda.addons import AddonData
from pykickstart.options import KSOptionParser
# export HelloWorldData class to prevent Anaconda's collect method from taking
# AddonData class instead of the HelloWorldData class
# :see: pyanaconda.kickstart.AnacondaKSHandler.__init__
__all__ = ["HelloWorldData"]
HELLO_FILE_PATH = "/root/hello_world_addon_output.txt"
class HelloWorldData(AddonData):
"""
Class parsing and storing data for the Hello world addon.
:see: pyanaconda.addons.AddonData
"""
def __init__(self, name):
"""
:param name: name of the addon
:type name: str
"""
AddonData.__init__(self, name)
self.text = ""
self.reverse = False
def handle_header(self, lineno, args):
"""
The handle_header method is called to parse additional arguments in the
%addon section line.
:param lineno: the current linenumber in the kickstart file
:type lineno: int
:param args: any additional arguments after %addon <name>
:type args: list
"""
op = KSOptionParser()
op.add_option("--reverse", action="store_true", default=False,
dest="reverse", help="Reverse the display of the addon text")
(opts, extra) = op.parse_args(args=args, lineno=lineno)
# Reject any additoinal arguments. Since AddonData.handle_header
# rejects any arguments, we can use it to create an error message
# and raise an exception.
if extra:
AddonData.handle_header(self, lineno, extra)
# Store the result of the option parsing
self.reverse = opts.reverse
def handle_line(self, line):
"""
The handle_line method that is called with every line from this addon's
%addon section of the kickstart file.
:param line: a single line from the %addon section
:type line: str
"""
# simple example, we just append lines to the text attribute
if self.text is "":
self.text = line.strip()
else:
self.text += " " + line.strip()
__all__ 変数を定義します。これは Anaconda の収集したメソッドがアドオン固有の HelloWorldData ではなく AddonData クラスを取らないようにするために必要なものです。
AddonData から継承した HelloWorldData クラスの定義が表示されています。これには、親の __init__ を呼び出し、属性 self.text と self.reverse を False に初期化する __init__ メソッドがあります。
self.reverse 属性は handle_header メソッド内に、self.text は handle_line 内に設定されます。handle_header メソッドは pykickstart が提供する KSOptionParser のインスタンスを使用して、%addon 行で使用される追加オプションを解析します。handle_line は各行の最初と最後にある空白のコンテンツ行を取り去り、これらを self.text に追加します。
setup- インストール処理の開始前に呼び出され、インストールランタイム環境の変更に使用されます。execute- 処理の最後に呼び出され、ターゲットシステムの変更に使用されます。
例5 setup および execute メソッドのインポート
import os.path from pyanaconda.addons import AddonData from pyanaconda.constants import ROOT_PATH HELLO_FILE_PATH = "/root/hello_world_addon_output.txt"
setup と execute メソッドが追加されると以下のようになります。
例6 setup および execute メソッドの使用
def setup(self, storage, ksdata, instclass, payload):
"""
The setup method that should make changes to the runtime environment
according to the data stored in this object.
:param storage: object storing storage-related information
(disks, partitioning, bootloader, etc.)
:type storage: blivet.Blivet instance
:param ksdata: data parsed from the kickstart file and set in the
installation process
:type ksdata: pykickstart.base.BaseHandler instance
:param instclass: distribution-specific information
:type instclass: pyanaconda.installclass.BaseInstallClass
:param payload: object managing packages and environment groups
for the installation
:type payload: any class inherited from the pyanaconda.packaging.Payload
class
"""
# no actions needed in this addon
pass
def execute(self, storage, ksdata, instclass, users, payload):
"""
The execute method that should make changes to the installed system. It
is called only once in the post-install setup phase.
:see: setup
:param users: information about created users
:type users: pyanaconda.users.Users instance
"""
hello_file_path = os.path.normpath(ROOT_PATH + HELLO_FILE_PATH)
with open(hello_file_path, "w") as fobj:
fobj.write("%s\n" % self.text)
setup メソッドは何もせず、Hello World アドオンはインストールランタイム環境に変化を加えません。execute メソッドは、保存済みのテキストをターゲットシステムの root (/) ディレクトリーに作成されたファイルに書き込みます。
__str__ メソッドを、インストールデータを保存しているツリー構造に反復して呼び出すことで実行されます。つまり、AddonData から継承されたクラスが、有効な Kickstart 構文の保存済みデータを返す独自の __str__ メソッドを定義する必要があることになります。この返されるデータは、pykickstart を使用して解析可能である必要があります。
__str__ メソッドは以下のようになります。
例7 __str__ メソッドの定義
def __str__(self):
"""
What should end up in the resulting kickstart file, i.e. the %addon
section containing string representation of the stored data.
"""
addon_str = "%%addon %s" % self.name
if self.reverse:
addon_str += "--reverse"
addon_str += "\n%s\n%%end" % self.text
return addon_str
handle_header、handle_line、setup、execute および __str__) がすべて格納されたら、有効な Anaconda アドオンになります。以下のセクションに従ってグラフィカルおよびテキストベースのユーザーインターフェースのサポートを追加するか、「Anaconda アドオンのデプロイとテスト」 に進んでアドオンをテストします。
5.6.2. グラフィカルユーザーインターフェース
注記
SpokeWindow のような Anaconda 固有の Gtk ウィジェットが含まれています。
5.6.2.1. 基本的機能
NormalSpoke で、これは pyanaconda.ui.gui.spokes で定義されます。このクラス名が示すように、これは 「ハブ & スポークモデル」 で説明されている 通常のスポーク の画面向けのクラスです。
NormalSpoke から継承した新たなクラスを実装するには、API が必要とする以下のクラス属性を定義する必要があります。
builderObjects- スポークの.gladeファイルからすべてのトップレベルオブジェクトを一覧表示します。これらのオブジェクトは、その子オブジェクトとともに (反復的に) スポークに公開されるべきものです。ただし、すべてをスポークに公開する場合 (非推奨) は、空のリストにします。uiFile-.gladeファイルの名前を指定します。category- スポークが所属するカテゴリーのクラスを指定します。icon- スポークまたはハブに使用されるアイコンの識別子を指定します。title‐ スポークまたはハブに使用されるタイトルを定義します。
例8 Normalspoke クラスに必須の属性の定義
# will never be translated
_ = lambda x: x
N_ = lambda x: x
# the path to addons is in sys.path so we can import things from org_fedora_hello_world
from org_fedora_hello_world.gui.categories.hello_world import HelloWorldCategory
from pyanaconda.ui.gui.spokes import NormalSpoke
# export only the spoke, no helper functions, classes or constants
__all__ = ["HelloWorldSpoke"]
class HelloWorldSpoke(NormalSpoke):
"""
Class for the Hello world spoke. This spoke will be in the Hello world
category and thus on the Summary hub. It is a very simple example of
a unit for the Anaconda's graphical user interface.
:see: pyanaconda.ui.common.UIObject
:see: pyanaconda.ui.common.Spoke
:see: pyanaconda.ui.gui.GUIObject
"""
### class attributes defined by API ###
# list all top-level objects from the .glade file that should be exposed
# to the spoke or leave empty to extract everything
builderObjects = ["helloWorldSpokeWindow", "buttonImage"]
# the name of the main window widget
mainWidgetName = "helloWorldSpokeWindow"
# name of the .glade file in the same directory as this source
uiFile = "hello_world.glade"
# category this spoke belongs to
category = HelloWorldCategory
# spoke icon (will be displayed on the hub)
# preferred are the -symbolic icons as these are used in Anaconda's spokes
icon = "face-cool-symbolic"
# title of the spoke (will be displayed on the hub)
title = N_("_HELLO WORLD")
__all__ 属性はスポーククラスのエクスポートに使用され、これまでに説明した属性の定義を含む定義の最初の行がその後に続きます。これらの属性値は、com_example_hello_world/gui/spokes/hello.glade ファイルで定義されるウィジェットを参照します。
category で、これには com_example_hello_world.gui.categories モジュールからの HelloWorldCategory クラスからインポートされた値があります。HelloWorldCategory クラスについては後述します。ここでは、アドオンへのパスは sys.path にあるので、com_example_hello_world パッケージからインポート可能になっていることに留意してください。
title です。これにはアンダースコアが 2 つ含まれています。1 つ目のアンダースコアは N_ 関数名の一部で、これは変換する文字列をマークしますが、返されるのは変換されていない文字列のバージョンです (変換は後でなされます)。2 つ目のアンダースコアはタイトル自体の始まりをマークし、Alt+H キーボードのショートカットを使用してスポークに移動できるようにします。
__init__ と initialize の 2 つのメソッドが新規インスタンスを初期化します。
__init__ メソッドは親の __init__ メソッドを呼び出し、(例えば) GUI 以外の属性を初期化します。initialize メソッドはインストーラーのグラフィカルユーザーインターフェースが初期化する際に呼び出され、スポークの完全な初期化を完了します。
__init__ メソッドに渡される属性の数と記述に注意):
例9 __init__ および initialize メソッドの定義
def __init__(self, data, storage, payload, instclass):
"""
:see: pyanaconda.ui.common.Spoke.__init__
:param data: data object passed to every spoke to load/store data
from/to it
:type data: pykickstart.base.BaseHandler
:param storage: object storing storage-related information
(disks, partitioning, bootloader, etc.)
:type storage: blivet.Blivet
:param payload: object storing packaging-related information
:type payload: pyanaconda.packaging.Payload
:param instclass: distribution-specific information
:type instclass: pyanaconda.installclass.BaseInstallClass
"""
NormalSpoke.__init__(self, data, storage, payload, instclass)
def initialize(self):
"""
The initialize method that is called after the instance is created.
The difference between __init__ and this method is that this may take
a long time and thus could be called in a separated thread.
:see: pyanaconda.ui.common.UIObject.initialize
"""
NormalSpoke.initialize(self)
self._entry = self.builder.get_object("textEntry")
data パラメーターが __init__ メソッドに渡されていることに留意してください。これは、すべてのデータが保存されている Kickstart ファイルのインメモリーのツリー構造を表します。親の __init__ メソッドの 1 つでは self.data 属性に保存され、これによりクラス内の他のすべてのメソッドが構造を読み取り、修正できるようになります。
HelloWorldData クラスは 「キックスタートのサポート」 で既に定義済みであることから、self.data 内にアドオン向けのサブツリーが既にあり、その root (クラスのインスタンス) は self.data.addons.com_example_hello_world として利用可能になっています。
__init__ は他にも、スポークの .glade ファイルがある GtkBuilder のインスタンスを初期化し、これを self.builder として保存します。これは、kickstart ファイルの %addon セクションからのテキストの表示およびその修正に使用される GtkTextEntry の取得に initialize で使用されます。
__init__ と initialize のメソッドは両方ともスポークの作成時に重要なものです。ただし、スポークの主要な役割は、ユーザーがこのスポークで表示、設定される値を変更または見直すことです。これを実行可能とするためには、以下の 3 つのメソッドが利用できます。
refresh- スポークをユーザーが表示する際に呼び出されます。このメソッドはスポークの状態を更新し (主に UI 要素)、self.data構造に保存されている現行値を表示します。apply- ユーザーがスポークを離れ、UI 要素からの値をself.data構造に保存し直す際に呼び出されます。execute- ユーザーがスポークを離れ、スポークの新規状態に基づいたランタイム変更を実行するために使用されます。
例10 refresh、apply および execute メソッドの定義
def refresh(self):
"""
The refresh method that is called every time the spoke is displayed.
It should update the UI elements according to the contents of
self.data.
:see: pyanaconda.ui.common.UIObject.refresh
"""
self._entry.set_text(self.data.addons.org_fedora_hello_world.text)
def apply(self):
"""
The apply method that is called when the spoke is left. It should
update the contents of self.data with values set in the GUI elements.
"""
self.data.addons.org_fedora_hello_world.text = self._entry.get_text()
def execute(self):
"""
The excecute method that is called when the spoke is left. It is
supposed to do all changes to the runtime environment according to
the values set in the GUI elements.
"""
# nothing to do here
pass
ready- スポークを表示する準備ができているかどうかを判断します。値が false の場合は、スポークにアクセスできません (例: パッケージソース設定前の Package Selection スポークなど)。completed- スポークが完了したかどうかを判断します。mandatory- スポークが必須かどうかを判断します (例: 自動パーティション設定を使用する場合でも、インストール先 スポークは表示する必要があります)。
HelloWorldData クラスのtext 属性で設定する必要があるものもあります。
例11 ready、completed および mandatory メソッドの定義
@property
def ready(self):
"""
The ready property that tells whether the spoke is ready (can be visited)
or not. The spoke is made (in)sensitive based on the returned value.
:rtype: bool
"""
# this spoke is always ready
return True
@property
def completed(self):
"""
The completed property that tells whether all mandatory items on the
spoke are set, or not. The spoke will be marked on the hub as completed
or uncompleted acording to the returned value.
:rtype: bool
"""
return bool(self.data.addons.org_fedora_hello_world.text)
@property
def mandatory(self):
"""
The mandatory property that tells whether the spoke is mandatory to be
completed to continue in the installation process.
:rtype: bool
"""
# this is an optional spoke that is not mandatory to be completed
return False
status と呼ばれる別のプロパティーがあり、これには設定した値の簡潔な概要がテキスト 1 行で含まれています。これはハブ内のスポークタイトルの下で表示することができます。
status プロパティーを以下のように定義します。
例12 status プロパティーの定義
@property
def status(self):
"""
The status property that is a brief string describing the state of the
spoke. It should describe whether all values are set and if possible
also the values themselves. The returned value will appear on the hub
below the spoke's title.
:rtype: str
"""
text = self.data.addons.org_fedora_hello_world.text
# If --reverse was specified in the kickstart, reverse the text
if self.data.addons.org_fedora_hello_world.reverse:
text = text[::-1]
if text:
return _("Text set: %s") % text
else:
return _("Text not set")
SpokeWindow ウィジェットのインスタンスのメインウィンドウが必要になるという点です。このウィジェットは Anaconda 固有の他のウィジェットとともに、anaconda-widgets パッケージにあります。(Glade 定義のような) GUI サポートのあるアドオンの開発に必要な他のファイルは、anaconda-widgets-devel パッケージにあります。
5.6.2.2. 高度な機能
pyanaconda にはスポークやハブが使用するヘルパーやユーティリティー関数、コンストラクトが含まれており、これらはここまでのセクションで説明されていません。ほとんどのものは、pyanaconda.ui.gui.utils にあります。
englightbox コンテンツマネージャーの使用方法が説明されています。このマネージャーはウィンドウをライトボックスにおいて視認性を高めてフォーカスし、ユーザーが下層のウィンドウと対話しないようにします。この機能を示すために、サンプルのアドオンには新規ダイアログのウィンドウを開くボタンが含まれています。ダイアログ自体は、GUIObject クラスを継承した特別な HelloWorldDialog で、これは pyanaconda.ui.gui.__init__ で定義されます。
dialog クラスは、self.window でアクセス可能な内部の Gtk ダイアログを実行および破棄する run メソッドを定義します。これは mainWidgetName クラス属性を使用して同じ意味で設定されます。このため、このダイアログを定義するコードは以下のように非常にシンプルなものになります。
例13 englightbox ダイアログの定義
# every GUIObject gets ksdata in __init__
dialog = HelloWorldDialog(self.data)
# show dialog above the lightbox
with enlightbox(self.window, dialog.window):
dialog.run()
enlightbox コンテキストマネージャーを使用してライトボックス内でそのダイアログを実行します。コンテキストマネージャーは、スポークのウィンドウとダイアログのウィンドウのライトボックスをインスタンス化するためにこれらのウィンドウへの参照を必要とします。
FirstbootSpokeMixIn (より正確には mixin) を、pyanaconda.ui.common モジュールで定義されている最初の継承クラスとして継承する必要があります。
FirstbootOnlySpokeMixIn クラスを継承します。
@gtk_action_wait や @gtk_action_nowait デコレーターのように) pyanaconda パッケージは他にも多くの高度な機能を提供しますが、それらは本ガイドの対象外となります。例については インストーラーのソース を参照してください。
5.6.3. テキスト形式のユーザーインターフェース
tui ディレクトリー下にサブパッケージの新規セットを作成します。
simpleline ユーティリティーをベースとしており、これは非常にシンプルなユーザーの対話のみを可能にするものです。これはカーソルの操作はできず (ラインプリンターのような動作になります)、色およびフォントのカスタマイズといった視覚的拡張機能もありません。
simpleline ツールキットには App、UIScreen および Widget の 3 つのメインクラスがあります。画面に表示 (プリント) する情報を格納しているユニットである Widget は、App クラスの単一インスタンスで切り替えられる UIScreens に配置されます。基本的な要素のほかには hubs、spokes および dialogs があり、これらはすべてグラフィカルインターフェースと同様の各種ウィジェットを格納しています。
NormalTUISpoke と pyanaconda.ui.tui.spokes パッケージで定義される他の各種クラスです。これらのクラスはすべて TUIObject クラスをベースとしており、これ自体は前の章で説明した GUIObject クラスと同等のものです。各 TUI スポークは NormalTUISpoke クラスから継承した Python クラスで、API が定義する特別な引数やメソッドを上書きします。テキストインターフェースは GUI よりもシンプルなので、引数は以下の 2 つのみになります。
title- GUI のtitleの場合と同様に、スポークのタイトルを指定します。category- 文字列としてスポークのカテゴリーを指定します。カテゴリー名はどこにも表示されず、グループ化にのみ使用されます。注記
カテゴリーは GUI の場合とは異なる方法で処理されます。[5] 新規スポークには既存のカテゴリーを割り当てることが推奨されます。新規のカテゴリーを作成すると Anaconda にパッチが必要になりますが、大きな利点はありません。
__init__、initialize、refresh、refresh、apply、execute、input、および prompt と、プロパティー (ready、completed、mandatory、および status)。これらについては 「グラフィカルユーザーインターフェース」 で説明しています。
例14 シンプルな TUI スポークの定義
def __init__(self, app, data, storage, payload, instclass):
"""
:see: pyanaconda.ui.tui.base.UIScreen
:see: pyanaconda.ui.tui.base.App
:param app: reference to application which is a main class for TUI
screen handling, it is responsible for mainloop control
and keeping track of the stack where all TUI screens are
scheduled
:type app: instance of pyanaconda.ui.tui.base.App
:param data: data object passed to every spoke to load/store data
from/to it
:type data: pykickstart.base.BaseHandler
:param storage: object storing storage-related information
(disks, partitioning, bootloader, etc.)
:type storage: blivet.Blivet
:param payload: object storing packaging-related information
:type payload: pyanaconda.packaging.Payload
:param instclass: distribution-specific information
:type instclass: pyanaconda.installclass.BaseInstallClass
"""
NormalTUISpoke.__init__(self, app, data, storage, payload, instclass)
self._entered_text = ""
def initialize(self):
"""
The initialize method that is called after the instance is created.
The difference between __init__ and this method is that this may take
a long time and thus could be called in a separated thread.
:see: pyanaconda.ui.common.UIObject.initialize
"""
NormalTUISpoke.initialize(self)
def refresh(self, args=None):
"""
The refresh method that is called every time the spoke is displayed.
It should update the UI elements according to the contents of
self.data.
:see: pyanaconda.ui.common.UIObject.refresh
:see: pyanaconda.ui.tui.base.UIScreen.refresh
:param args: optional argument that may be used when the screen is
scheduled (passed to App.switch_screen* methods)
:type args: anything
:return: whether this screen requests input or not
:rtype: bool
"""
self._entered_text = self.data.addons.org_fedora_hello_world.text
return True
def apply(self):
"""
The apply method that is called when the spoke is left. It should
update the contents of self.data with values set in the spoke.
"""
self.data.addons.org_fedora_hello_world.text = self._entered_text
def execute(self):
"""
The excecute method that is called when the spoke is left. It is
supposed to do all changes to the runtime environment according to
the values set in the spoke.
"""
# nothing to do here
pass
def input(self, args, key):
"""
The input method that is called by the main loop on user's input.
:param args: optional argument that may be used when the screen is
scheduled (passed to App.switch_screen* methods)
:type args: anything
:param key: user's input
:type key: unicode
:return: if the input should not be handled here, return it, otherwise
return True or False if the input was processed succesfully or
not respectively
:rtype: bool|unicode
"""
if key:
self._entered_text = key
# no other actions scheduled, apply changes
self.apply()
# close the current screen (remove it from the stack)
self.close()
return True
def prompt(self, args=None):
"""
The prompt method that is called by the main loop to get the prompt
for this screen.
:param args: optional argument that can be passed to App.switch_screen*
methods
:type args: anything
:return: text that should be used in the prompt for the input
:rtype: unicode|None
"""
return _("Enter a new text or leave empty to use the old one: ")
__init__ のみを呼び出す場合は __init__ メソッドを上書きする必要はありませんが、この例でのコメントではスポーククラスのコンストラクターに渡す引数を分かりやすい方法で記述してあります。
initialize メソッドはスポークの内部引数のデフォルト値を設定し、これは refresh メソッドで更新され、 Kickstart データの更新に apply メソッドが使用します。この 2 つのメソッドが GUI のものと違う点は、refresh メソッドの戻り値のタイプ (None ではなく bool) と、それらが取る追加の args 引数のみです。戻り値の意味についてはコメントで説明されています。アプリケーション (App クラスインスタンス) にこのスポークがユーザー入力を必要とするかどうかを指示します。追加の args 引数は、スポークに追加情報を渡す予定の場合に使用されます。
execute メソッドは、GUI の同等のメソッドと同じ目的で、この場合はメソッドは何もしません。
input と prompt のメソッドは、テキストインターフェース固有のもので、Kickstart や GUI には同等のものはありません。この 2 つのメソッドはユーザーの対話に使用されます。
prompt メソッドは、スポークのコンテンツがプリントされた後に表示されるプロンプトを返します。このプロンプトに文字列を入力すると、これが input メソッドに渡されて処理されます。input メソッドはこの文字列のタイプと値に応じてアクションを実行します。上記の例では いかなる 値でもよく、これが内部属性 (key) として保存されます。より複雑なアドオンでは通常、c を "continue" または r を "refresh" として解析する、数字を整数に変換する、新たな画面を表示するもしくはブール値を切り替えるなど、簡単ではないアクションを実行する必要があります。
input クラスの戻り値は、INPUT_PROCESSED か INPUT_DISCARDED の定数 (これらは両方とも pyanaconda.constants_text モジュールで定義) となるか、もしくは 入力文字列そのもの (この入力が別の画面で処理される場合) である必要があります。
apply メソッドは自動的に呼び出されません。これは input メソッドから明示的に呼び出す必要があります。同じことがスポークの画面閉鎖 (非表示) にも該当し、これは close メソッドの呼び出しで実行します。
TUIObject をインスタンス化し、App の self.app.switch_screen* メソッドの 1 つを呼び出します。
pyanaconda.ui.tui.spokes パッケージの EditTUISpoke クラスを使用することでも実行できます。このクラスを継承すると、設定するフィールドと属性を指定するだけで通常の TUI スポークを実装できます。以下の例ではこの方法を示しています。
例15 EditTUISpoke を使ったテキストインターフェースのスポークの定義
class _EditData(object):
"""Auxiliary class for storing data from the example EditSpoke"""
def __init__(self):
"""Trivial constructor just defining the fields that will store data"""
self.checked = False
self.shown_input = ""
self.hidden_input = ""
class HelloWorldEditSpoke(EditTUISpoke):
"""Example class demonstrating usage of EditTUISpoke inheritance"""
title = _("Hello World Edit")
category = "localization"
# simple RE used to specify we only accept a single word as a valid input
_valid_input = re.compile(r'\w+')
# special class attribute defining spoke's entries as:
# Entry(TITLE, ATTRIBUTE, CHECKING_RE or TYPE, SHOW_FUNC or SHOW)
# where:
# TITLE specifies descriptive title of the entry
# ATTRIBUTE specifies attribute of self.args that should be set to the
# value entered by the user (may contain dots, i.e. may specify
# a deep attribute)
# CHECKING_RE specifies compiled RE used for deciding about
# accepting/rejecting user's input
# TYPE may be one of EditTUISpoke.CHECK or EditTUISpoke.PASSWORD used
# instead of CHECKING_RE for simple checkboxes or password entries,
# respectively
# SHOW_FUNC is a function taking self and self.args and returning True or
# False indicating whether the entry should be shown or not
# SHOW is a boolean value that may be used instead of the SHOW_FUNC
#
# :see: pyanaconda.ui.tui.spokes.EditTUISpoke
edit_fields = [
Entry("Simple checkbox", "checked", EditTUISpoke.CHECK, True),
Entry("Always shown input", "shown_input", _valid_input, True),
Entry("Conditioned input", "hidden_input", _valid_input,
lambda self, args: bool(args.shown_input)),
]
def __init__(self, app, data, storage, payload, instclass):
EditTUISpoke.__init__(self, app, data, storage, payload, instclass)
# just populate the self.args attribute to have a store for data
# typically self.data or a subtree of self.data is used as self.args
self.args = _EditData()
@property
def completed(self):
# completed if user entered something non-empty to the Conditioned input
return bool(self.args.hidden_input)
@property
def status(self):
return "Hidden input %s" % ("entered" if self.args.hidden_input
else "not entered")
def apply(self):
# nothing needed here, values are set in the self.args tree
pass
_EditData は、ユーザーが入力した値を保存するデータコンテナーとして機能します。HelloWorldEditSpoke クラスはチェックボックス 1 つとエントリー 2 つの簡単なスポークを定義し、これらはすべて Entry クラスとしてインポートされる EditTUISpokeEntry のインスタンスになります。最初のインスタンスはスポークが表示されるたびに表示され、2 つ目のインスタンスは 1 つ目に空以外の値が含まれる場合にのみ表示されます。
EditTUISpoke クラスについての詳細は、上記の例にあるコメントを参照してください。
5.7. Anaconda アドオンのデプロイとテスト
/usr/share/anaconda/addons/ ディレクトリーから収集します。独自のアドオンをこのディレクトリーに追加するには、同じディレクトリー構造で product.img ファイルを作成し、ブートメディアに配置する必要があります。
product.img ファイルの作成方法、およびイメージの再パッケージ化方法についての詳細は、「ISO イメージを使った作業」 を参照してください。
A. 改訂履歴
| 改訂履歴 | ||||||
|---|---|---|---|---|---|---|
| 改訂 2-3.1 | Thu Jul 20 2017 | |||||
| ||||||
| 改訂 2-3 | Mon May 15 2017 | |||||
| ||||||
| 改訂 2-2 | Mon Nov 16 2015 | |||||
| ||||||
| 改訂 2-1 | Mon Jun 22 2015 | |||||
| ||||||
| 改訂 2-0 | Fri Jun 19 2015 | |||||
| ||||||
| 改訂 1-0 | Wed Jan 15 2014 | |||||
| ||||||
| 改訂 0-0 | Fri Dec 28 2012 | |||||
| ||||||
索引
シンボル
- ブートメニュー
- BIOS システムのカスタマイズ, BIOS ファームウェアのシステム
- UEFI システムのカスタマイズ, UEFI ファームウェアのシステム
G
- grub2
- カスタム設定, UEFI ファームウェアのシステム
I
- ISO イメージ
- ISO イメージの作成, カスタムブートイメージの作成
- isolinux
- カスタム設定, BIOS ファームウェアのシステム
M
- MD5sum
- ISO イメージに埋め込み, カスタムブートイメージの作成
P
- product.img
inst.kdump_addon=on オプションを使用するとこれを有効にできます。

