# FreeBSD をゼロから設定するには

Jens
Schweikhardt

    schweikh@FreeBSD.org

2002
Jens Schweikhardt
$FreeBSD$
&tm-attrib.freebsd; &tm-attrib.adobe; &tm-attrib.general;

この記事は、「&scratch.ap; (FreeBSD From Scratch)」という、 わたしの個人的な経験をまとめたものです。 カスタマイズした &os; システムをソースからコンパイルし、 さらに好みの ports のコンパイルして、 あなたが望む構成のシステムの、 完全に自動化されたインストールを実現します。 `make world` がすばらしい考え方だとお思いの方にとって、 「&scratch.ap;」は、まさに `make world` を `make evenmore` (さらにその先) へと広げるものになることでしょう。

## はじめに

今までに `make world` を使ってシステムをアップグレードした経験はあるでしょうか? もしディスクに一つのシステムしか入れていない場合は問題です。 installworld が途中で止まってしまったら、 あなたのシステムは壊れたまま、もう起動しなくなってしまうかも知れません。 あるいは、installworld が正常に終了しても、 新しいカーネルは起動に失敗してしまうかも知れません。 さて、そうなってしまったら、Fixit CD を取り出して半年前のバックアップを戻す、 なんてはめになってしまうかも知れませんよね。

わたしは、アップグレードの時はディスクを初期化する という方法がよいと考えています。パーティションではなくディスク全体のデータを 消去することで、アップグレードの手順では無視されるような古いデータが 残ってしまうことを防ぐことができます。ただ、 パーティションを全部初期化するということは、 ports/packages をすべて再コンパイル・再インストールしなければならず、 設定ファイルも注意深く作成し直さなければならないということです。 こういう作業を自動化したいと思いませんか? そう思う人は、この先を読み進めましょう。

## どうして「&scratch.ap;」(あるいは「〜しない」) ことが必要なのか

これはもっともな質問です。 すでに sysinstall がありますし、 カーネルとユーザランドツールをコンパイルする方法には、 もっと有名な方法が他にもあるからです。

sysinstall の問題は、「何を、どこに、 どうやってインストールするのか」が非常に限定されているという点です。

- sysinstall は通常、構築ずみの配布物セットと packages を (CD, DVD, FTP などの) 別の場所からインストールする時に使われるものであり、 `make buildworld` の結果をインストールできるようにはできていません。

- 現在稼働中のシステム中にあるディレクトリに、 新しいシステムをインストールすることはできません。

- Vinum パーティションへのインストールはできません。

- 構築ずみの packages はインストールできますが、 ports を構築することはできません。

- スクリプトを使ったり、 インストール後に変更するための処理を自由に入れることは困難です。

- 最後の大きな理由として、sysinstall が、公式にもう積極的に使わないプログラムと考えられている、 ということがあげられます。

システム全体を構築してインストールする方法は、 ハンドブックにある方法が有名です。 これはデフォルトで既存のシステムを置き換えるもので、 カーネルとモジュールだけが保存され、 システムバイナリ、ヘッダ、その他の多くのファイルは上書きされます。 使われなくなった古いファイルはそのまま残り、 動作に問題が出ることもあります。 何らかの理由でアップグレードに失敗すると、 システムを元の状態に戻することは不可能か、できても非常に困難です。

「&scratch.ap;」方法は、これらの問題をすべて解決できます。 考え方は単純です。 稼働中のシステムを使って空のディレクトリにシステムをインストールします。 その時、その新しいシステムのディレクトリツリーには、 新しいパーティションを適切にマウントしておaきます。 数多くある設定ファイルは、コピーできるものは適切な場所にコピーし、 それができないものには &man.mergemaster.8; を使います。 新しいシステムに対するインストール後の設定は、 古いシステムを動作させながら、新しいシステムに対して chroot して 自由に行なうことができます。具体的には、 シェルスクリプト、もしくは `make` の実行で構成される、次の 3 段階でこれらを実現します。

1. `stage_1.sh`: 新しい起動可能なシステムを空のディレクトリ以下に作成し、 必要なファイルをマージ、もしくはコピーします。 そして、新しいシステムを起動します。

2. `stage_2.sh`: 必要な ports をインストールします。

3. `stage_3.mk`: ひとつ前の段階でインストールしたソフトウェアの、 インストール後の設定を行ないます。

新しいシステムを構築するために「&scratch.ap;」方法を使い、 それが数週間、満足する程度に動作していることを確認したら、 もう一度それを使って、大元のシステムを再インストールすることができます。 これからはいつでも好きな時にシステムを更新して、 初期化・再インストールしたパーティションに切り替えるだけでよくなるわけです。

Linux From Scratch (もしくは省略して LFS) について耳にしたり、試された方がいらっしゃるかも知れません。 LFS も同じように、稼働中のシステムを使ってシステムをゼロから構築し、 空のパーティションにインストールする方法が書かれています。 LFS が話題の中心としているのは、(カーネル、コンパイラ、デバイス、 シェル、端末データベースなどの) 各システムコンポーネントの役割と、 それらのインストールの詳細を見せることのようです。 この「&scratch.ap;」では、そのような詳細には触れません。 わたしの目的は、インストールを終わりまで自動化することであり、 システム構築時の泥くさい過程を全部説明することではありません。 &os; をそのようなレベルで掘り下げてみたい人は、 `/usr/src/Makefile` を読んで、 `make buildworld` の動作を追いかけるところから始めましょう。

また、「&scratch.ap;」方法にも、 次のような欠点があることを心に留めておいてください。

- 第 2 段階で ports をコンパイルしている間、 システムは通常の用途に使用することができません。 もしプロダクションサーバを運用しているなら、 第 2 段階でダウンタイムが発生することを考慮に入れなければなりません。 `stage_2.sh` の ports のコンパイルには、 AMD1800+、10,000rpm SCSI、1GB の RAM を搭載したシステムで、 約 4 時間かかります。

## 前提とする環境

「&scratch.ap;」方法を実行するには、 次のものが必要です。

- ソースと ports ツリーを含む、稼働中の &os; システム

- 新しいシステムをインストールするための、 最低 1 個の未使用パーティション

- &man.mergemaster.8; を実行した経験。もしくは、 それを実行する勇気。

- インターネット接続環境がない、あるいは遅い場合には、 インストールしたい ports の配布ファイル

- Bourne シェル (&man.sh.1;) を使ってシェルスクリプトを作成するための基礎知識

- 新しいシステムを起動する方法を、 対話的あるいは設定ファイルを使ってブートローダに 教えることができること

## 第 1 段階: システムのインストール

次に紹介するのは、わたしが作成した `stage_1.sh` です。あなたが求めているシステムに合うように、 カスタマイズしてください。カスタマイズしなければならないところには、 なるべく詳細なコメントを付けてあります。重要なポイントは、 以下のとおりです。

- パーティションの配置

  わたしは、システム全体を一つの大きな パーティションに入れるという考え方が好きではないので、 普通は `/`、 `/usr`、 `/var` の パーティションを分割し、`/tmp` を `/var/tmp` のシンボリックリンクにしています。 また、`/home` (ユーザのホームディレクトリ)、 `/home/ncvs` (&os; CVS リポジトリの複製), `/usr/ports` (ports ツリー), `/src` (チェックアウトした src ツリー)、 `/share` (news スプールなど、バックアップする必要がない、 その他の共有データ) といったファイルシステムを、 古いシステムと新しいシステムで共有しています。

- その他の項目

  これは、新しいシステムの起動後にすぐに実行したいことや、 第 2 段階の前に実行したい内容のことです。 わたしの場合は、`/etc/passwd` に ログインシェルとして `shells/zsh` が登録してあるので、それになります。 厳密には、この「その他の項目」を実行する必要は必ずしもありません。 root ユーザでログインして、次の段階を実行できさえすればよいからです。

  第 1 段階で単純に好みの ports をすべてインストールしないのは、 理論的、あるいは実践的な面においてブートストラップ問題と依存問題があるからです。 第 1 段階では古いカーネルが動作しているのですが、 chroot した環境では新しいバイナリとヘッダが含まれています。 たとえば、新しいシステムが (新しいヘッダに従って) 新しいシステムコールをサポートしていた場合、 configure スクリプトがそれを使おうとしてしまうかも知れません。 そうすると、古いカーネルは対応していないので異常終了してしまうでしょう。 わたしが `lang/perl5` をコンパイルした時には、他にも問題が発生するのを確認しています。

`stage_1.sh` を実行する前に、 `make installworld installkernel` を実行するために通常行なう作業を完了させておいてください。 これらは、たとえば次のようなものです。

- カーネルコンフィグファイルの設定

- `make buildworld` を正常終了させておくこと

- `make buildkernel KERNCONF=whatever` を正常終了させておくこと

初めて `stage_1.sh` を実行した場合は、 稼働中のシステムから新しいシステムへとコピーされる設定ファイルは `/usr/src` のものと比べると古いので、 `mergemaster` がどうするかを聞いてきます。 おすすめは、ここで変更点を統合しておくことです。 もし、何度も質問に答えるのが面倒であれば、 稼働中のシステムのファイルを更新しておきましょう (ただしこれは、そうできればの話です。 `-STABLE` のシステムを実行していて、 `-CURRENT` を構築する、 もしくはその逆のようなケースでは、そうしてはいけません)。 次に `mergemaster` を実行した時、 RCS バージョン ID が `/usr/src` にあるファイルと一致しているものは、処理が飛ばされるようになります。

`stage_1.sh` スクリプトは `set -e` が指定されており、 最初のコマンドが失敗 (終了コードが 0 以外) すると停止します。 そのため、エラーを見逃してしまうということはないでしょう。 次に進む前に、`stage_1.sh` にあるエラーを全部修正しておいてください。

`stage_1.sh` では `mergemaster` が実行されます。 統合作業をしなければならないファイルが一つもない状態でも、 実行の終わりに次のメッセージが表示されます。

    *** Comparison complete

    Do you wish to delete what is left of /var/tmp/temproot.stage1? [no] no

`no` と答えるか、 単に Enter を押してください。 なぜかと言うと、`mergemaster` は `/var/tmp/temproot.stage1` にサイズが 0 のファイルをいくつか残すからです。 これは、後で新しいシステムに (存在しなければ) コピーされます。

この後、インストールされたファイルのリストがページャ (デフォルトでは &man.more.1; です。&man.less.1; を使うこともできます) に表示されます。

    *** You chose the automatic install option for files that did not
        exist on your system.  The following were installed for you:
          /newroot/etc/defaults/rc.conf
          ...
          /newroot/COPYRIGHT

    (END)

q を入力してページャを終了します。 すると `login.conf` に関して、次のように表示されます。

    *** You installed a login.conf file, so make sure that you run
        '/usr/bin/cap_mkdb /newroot/etc/login.conf'
        to rebuild your login.conf database

        Would you like to run it now? y or n [n]

これに対する答えはどちらでも構いません。 どう答えても、スクリプトから &man.cap.mkdb.1; が実行されます。

ちゃんと予想どおりに動いているかチェックできるよう、 `stage_1.sh` で行なわれたことは、 すべて `stage_1.log` に記録されます。

次に示すのは、筆者の使っている `stage_1.sh` です。 特にステップ 1, 2, 5, 6 は書き換える必要があるでしょう。

&man.newfs.8; コマンドには注意してください。 マウントずみのパーティションに新しいファイルシステムを作成することはできないものの、 このスクリプトはマウントされていない `/dev/da3s1a`, `/dev/vinum/var_a`, `/dev/vinum/usr_a` をすべて削除します。 ひとつ間違えれば、あなたの環境を破壊してしまう可能性がありますので、 デバイス名の変更は注意深く行なってください。

このスクリプトを実行すると、 起動した時に次のような状態になっているシステムがインストールされます。

- 稼働中のシステムと同じユーザとグループ

- Ethernet と PPP を経由した、 ファイアウォールありのインターネット接続環境

- 正しいタイムゾーンと NTP 設定

- `/etc/ttys` や `inetd` など、その他の細かな設定。

他の部分に対する設定は、第 2 段階が終わるまで動作しません。 たとえば、プリンタや X11 の設定ファイルもコピーされますが、 プリンタは &postscript; ユーティリティなど、 ベースシステムに含まれないアプリケーションを使うことが多いでしょう。 X11 はサーバ、ライブラリ、プログラムをコンパイルしないと動作しません。

## 第 2 段階: ports のインストール

この段階で ports をコンパイルするのではなく、 (コンパイルずみの) packages をインストールすることもできます。 その場合、`stage_2.sh` は 単に `pkg_add` コマンドを羅列するだけになるでしょう。 読者のみなさんにとって、そういうスクリプトを書くのは難しくないと思いますので、 ここではもっと柔軟で、ports を使った伝統的な方法について考えることにします。

次に紹介する `stage_2.sh` スクリプトは、 わたしが好みの ports をインストールするために使ったものです。 これは何度でも実行でき、インストールずみの ports があれば、 飛ばして処理されます。スクリプトは 実行せず、実行される内容だけ を表示する (dryrun) オプション (`-n`) があります。ports リストの編集や、環境変数の設定を変更しましょう。

ports リストは、空白で区切られた 2 個以上のキーワードからなっています。 カテゴリ、port 名に始まり、オプションとして port をコンパイルしてインストールするためのコマンド (デフォルトは `make install`) が続きます。 空白行と # から始まる行は無視されます。 おそらく多くの場合に考えなければならないのは、カテゴリ名と port 名だけでしょう。 ports によっては、たとえば次のように `make` 変数を使って微調整することができます。

    www mozilla make WITHOUT_MAILNEWS=yes WITHOUT_CHATZILLA=yes install
    mail procmail make BATCH=yes install

実際には任意のシェルコマンドを指定できますので、 `make` を使う以外にも応用は可能です。

    java linux-sun-jdk13 yes | make install
    news inn-stable CONFIGURE_ARGS="--enable-uucp-rnews --enable-setgid-inews" make install

`news/inn-stable` の行は、 `CONFIGURE_ARGS` という シェル変数を定義した例です。 この port の `Makefile` は、 この指定した値を変数の初期値として、その他の必須の引数と一緒に使います。 これと

    news inn-stable make CONFIGURE_ARGS="--enable-uucp-rnews --enable-setgid-inews" install

のようにして make 変数をコマンドラインに設定した場合との違いは、 こちらの場合に変数そのものを完全に上書きしてしまうという点です。 どの方法を使えばいいのかについては、各 port によります。

インストールしたい ports が、 対話的インストールを使っていないことを確認してください。 ports は、あなたが標準入力に明示的に指定したもの以外、 標準入力を読み込む動作をしてはいけません。 もし ports がそのように作られていると、ports はヒアドキュメントにある ports リストの次の行を読み込んで混乱してしまいます。 `stage_2.sh` を実行した時、 ある port が飛ばされたり、動作が止まってしまうようなことがあれば、 おそらくこれが原因でしょう。

次に示すのが、実際の `stage_2.sh` です。 これは、インストールされる port それぞれに対して `LOGDIR/category+port` という名前のログファイルを作成します。 `stage_2.sh` が共有パーティションになければ、 実行前に新しいシステムにこれをコピーするようにしてください。

## 第 3 段階

第 2 段階で、好みの ports がインストールされましたが、 ports には、設定を必要とするものがあります。 第 3 段階は、インストール後の設定を行なう段階です。 `stage_2.sh` の最後にこの段階を統合することもできたのですが、 わたしは port をインストールすることと初期設定を変更することが異なる工程であると考えたため、 独立した段階としています。

第 3 段階は、`Makefile` として実装しています。 これは、次のように実行することで、設定対象を簡単に選ぶことができるからです。

    &prompt.root; make -f stage_3.mk target

`stage_2.sh` の段階で、 `stage_3.mk` を共有パーティションに置くか、 新しいシステムのどこかにコピーするなどして、 新しいシステムが起動した時に `stage_3.mk` が使えるようにしておきましょう。

## 制限事項

対話的で、かつ `make BATCH=YES install` でのインストールに対応していない port の自動インストールは難しいかも知れません。 対話的にインストールする ports には、ライセンス条項の同意を尋ねられた時に `yes` と入力するだけのものがいくつかあります。 そのように入力が標準入力から読みとられる場合は、 適切な回答をインストールコマンド (通常は `make install`) にパイプで渡すことができます (`stage_2.sh` の `java/linux-sun-jdk13` でとった方法がそうです)。

しかしこの方法は、たとえば `editors/staroffice52` の場合にはうまく動きません。 これは X11 が実行されていることを要求するからです。 インストール手順には多くのクリックや文字入力が必要なので、 他の ports のように自動化することはできません。 わたしは、次のようにして問題を回避しました。 最初に古いシステムで staroffice の package を作成し、

    &prompt.root; cd /usr/ports/editors/staroffice52
    &prompt.root; make package
    ===>  Building package for staroffice-5.2_1
    Creating package /usr/ports/editors/staroffice52/staroffice-5.2_1.tbz
    Registering depends:.
    Creating bzip'd tar ball in '/usr/ports/editors/staroffice52/staroffice-5.2_1.tbz'

その後、第 2 段階で次のようにしたわけです。

    &prompt.root; pkg_add /usr/ports/editors/staroffice52/staroffice-5.2_1.tbz

その他に、設定ファイルのアップグレード問題に気をつける必要があります。 一般的に、設定ファイルの書式や内容がいつ変更されるかを知ることはできません。 新しいグループが `/etc/group` に追加されるかも知れませんし、`/etc/passwd` に新しいフィールドが追加されるかも知れません。 このような例は、実際に過去にありました。 単純に古いシステムから新しいシステムに設定ファイルをコピーするだけで ほとんどの場合は十分なのですが、時には不都合な場合もあります。 古いファイルを上書きする方法でシステムをアップグレードしたら、 ローカルにある設定ファイルに新しく追加されたかも知れない項目を統合する目的で `mergemaster` を使うと思います。 しかし残念なことに、`mergemaster` はベースシステムに存在するファイルだけで、インストールした ports については何も処理を行なってくれません。 サードパーティ製ソフトウェアには、 リリースのたびに設定ファイルのフォーマットが変更され、 わたしをイライラさせるようなものもあります。 注意すること以外にできることはありませんが、 特にメジャーバージョンがあがった時は気を付けてください。 わたしは以前、ウェブサーバ、 ニュースサーバ、ニュースリーダのファイルを書き換えたり、 書き直すはめになったことがあります。 活発に開発が進められているソフトウェアはすべて、 設定ファイルの書式が変更されていないか確認しておきましょう。

わたしは `5-CURRENT` から `5-CURRENT` に更新するために 「&scratch.ap;」方法を数回使いましたが、 `4-STABLE` と `5-CURRENT` の間で更新を行なった経験はありません。 異なるメジャーリリース番号の間は、非常の多数の変更が行なわれているため、 更新作業はもっと複雑なものになると思います。 (試したわけではないのですが) `4-STABLE` から `4-STABLE` への更新であれば、「&scratch.ap;」方法は問題なく動作するはずです。 `4-STABLE` のユーザは、次の点を考慮してください。

デバイスファイルシステム (&man.devfs.5;) を使ってなければ、 第 1 段階のステップ 6 で &man.MAKEDEV.8; を使い、 ハードウェア用のデバイスファイルを作成するとよいでしょう。