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

Jens Schweikhardt

$FreeBSD: doc/ja_JP.eucJP/articles/fbsd-from-scratch/article.sgml,v 1.5 2004/12/29 17:28:47 hrs Exp $

FreeBSD は The FreeBSD Foundation の登録商標です。

Adobe, Acrobat, Acrobat Reader および PostScript は アメリカ合衆国および/またはその他の国の Adobe Systems Incorporated の登録商標または商標です。

製造者および販売者が製品を区別するのに 用いている表示の多くは、商標とされています。 この文書に登場する表示のうち FreeBSD Project がその商標を確認しているものには、その表示に続いて “™” または “®” 記号がおかれています。


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


1. はじめに

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

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


2. どうして「FreeBSD をゼロから設定する」(あるいは「〜しない」) ことが必要なのか

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

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

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

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

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

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

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

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

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

また、「FreeBSD をゼロから設定する」方法にも、 次のような欠点があることを心に留めておいてください。


3. 前提とする環境

FreeBSD をゼロから設定する」方法を実行するには、 次のものが必要です。


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

この文書の初版では、第 1 段階にひとつのシェルスクリプトを使っていました。 カスタマイズはすべて、そのスクリプトを編集する必要があったのですが、 利用者からの意見を参考にして、スクリプトのコードとデータを分離することにしました。 そのため新しいスクリプトでは、コードスクリプトを変更せずに、 複数の異なるシステムに異なる構成のデータを置くことが可能になりました。

第 1 段階のコードスクリプトは stage_1.sh であり、次のように 1 個の引数をつけて実行すると

# ./stage_1.sh default

設定ファイルとして stage_1.conf.default を読み込み、 ログファイルとして stage_1.log.default に書き込みます。

文末にわたしが使っている stage_1.conf.default が添付してあります。 あなたが考える “完璧なシステム” に合わせて、 各設定をカスタマイズしてください。あなたが変更しそうな設定には、 詳細なコメントを追加してあります。設定スクリプトでは、 create_file_systems, create_etc_fstab, copy_files, all_remaining_customization という、 4 個のシェル関数を提供しなければなりません (これは、 stage_1.sh から呼ばれる順に書いてあります)。

考慮すべき点は、以下のとおりです。

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

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

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

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 のファイルをいくつか残すからです。 これは、後で新しいシステムに (存在しなければ) コピーされます。

この後、インストールされたファイルのリストがページャ (デフォルトでは more(1) です。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]

これに対する答えはどちらでも構いません。 どう答えても、スクリプトから cap_mkdb(1) が実行されます。

次に示すのは、筆者の使っている stage_1.conf.default ですが、たくさんの部分を書き換える必要がありますので注意してください。 どこを書き換えればよいのかについては、コメントを読めば十分理解できると思います。

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

# This file: stage_1.conf.default, sourced by stage_1.sh.
#
# $Id: stage_1.conf.default,v 1.2 2004/01/03 13:55:06 toor Exp toor $
# $FreeBSD: doc/en_US.ISO8859-1/articles/fbsd-from-scratch/stage_1.conf.default,v 1.4 2008/12/03 21:59:51 schweikh Exp $

# Root mount point where you create the new system. Because it is only
# used as a mount point, no space will be used on that file system as all
# files are of course written to the mounted file system(s).
DESTDIR="/newroot"

# Where your src tree is.
SRC="/usr/src"

# Where your obj is.
MAKEOBJDIRPREFIX="/usr/obj"

# Your kernel config name as from make buildkernel KERNCONF=...
KERNCONF="HAL9000"

# Your target architecture as used for make buildworld TARGET=...
# If you did not specify a TARGET when building world, it defaulted
# to the build architecture (run "uname -m" to find out if you are unsure).
TARGET="i386"  # amd64 arm i386 ia64 mips pc98 powerpc sparc64 sun4v

# Available time zones are those under /usr/share/zoneinfo.
TIMEZONE="Europe/Berlin"


#
# The create_file_systems function must create the mountpoints under
# DESTDIR, create the file systems, and then mount them under DESTDIR.
#
create_file_systems () {
  # The new root file system. Mandatory.
  # Change DEVICE names.
  DEVICE=/dev/daXYZs1a
  mkdir -m 755 -p ${DESTDIR}
  chown root:wheel ${DESTDIR}
  newfs -U ${DEVICE}
  mount -o noatime ${DEVICE} ${DESTDIR}

  # Additional file systems and initial mount points. Optional.
  DEVICE=/dev/daXYZs1e
  mkdir -m 755 -p ${DESTDIR}/var
  chown root:wheel ${DESTDIR}/var
  newfs -U ${DEVICE}
  mount -o noatime ${DEVICE} ${DESTDIR}/var

  DEVICE=/dev/daXYZs1e
  mkdir -m 755 -p ${DESTDIR}/usr
  chown root:wheel ${DESTDIR}/usr
  newfs -U ${DEVICE}
  mount -o noatime ${DEVICE} ${DESTDIR}/usr
}

#
# The create_etc_fstab function must create an fstab matching the
# file systems created in create_file_systems.
#
create_etc_fstab () {
  cat <<EOF >${DESTDIR}/etc/fstab
# Device         Mountpoint          FStype    Options              Dump Pass#
/dev/da0s1b      none                swap      sw                   0    0
/dev/da1s1b      none                swap      sw                   0    0
/dev/da2s2b      none                swap      sw                   0    0
/dev/da3s2b      none                swap      sw                   0    0
/dev/da0s1a      /                   ufs       rw,noatime           1    1
/dev/da0s1e      /var                ufs       rw,noatime           1    1
/dev/da2s1e      /usr                ufs       rw,noatime           1    1
/dev/vinum/Share /share              ufs       rw,noatime           0    2
/dev/vinum/home  /home               ufs       rw,noatime           0    2
/dev/vinum/ncvs  /home/ncvs          ufs       rw,noatime           0    2
/dev/vinum/ports /usr/ports          ufs       rw,noatime           0    2
/dev/ad1s1a      /flash              ufs       rw,noatime           0    0
/dev/ad0s1       /2k                 ntfs      ro,noauto            0    0
/dev/ad0s6       /linux              ext2fs    ro,noauto            0    0
#
/dev/cd0         /cdrom              cd9660    ro,noauto            0    0
/dev/cd1         /dvd                cd9660    ro,noauto            0    0
proc             /proc               procfs    rw                   0    0
linproc          /compat/linux/proc  linprocfs rw                   0    0
EOF
  chmod 644 ${DESTDIR}/etc/fstab
  chown root:wheel ${DESTDIR}/etc/fstab
}

#
# The copy_files function is used to copy files before mergemaster is run.
#
copy_files () {
  # Add or remove from this list at your discretion. Mostly mandatory.
  for f in \
    /.profile \
    /etc/devd.conf \
    /etc/devd.rules \
    /etc/exports \
    /etc/group \
    /etc/hosts \
    /etc/inetd.conf \
    /etc/ipfw.conf \
    /etc/make.conf \
    /etc/master.passwd \
    /etc/nsswitch.conf \
    /etc/ntp.conf \
    /etc/printcap \
    /etc/profile \
    /etc/rc.conf \
    /etc/resolv.conf \
    /etc/src.conf \
    /etc/sysctl.conf \
    /etc/ttys \
    /etc/mail/aliases \
    /etc/mail/aliases.db \
    /etc/mail/hal9000.mc \
    /etc/mail/service.switch \
    /etc/ssh/*key* \
    /etc/ssh/*_config \
    /etc/X11/xorg.conf \
    /var/cron/tabs/* \
    /root/.profile \
    /boot/*.bmp \
    /boot/loader.conf \
    /boot/device.hints ; do
    cp -p ${f} ${DESTDIR}${f}
  done
}

#
# Everything else you want to tune in the new system.
# NOTE: Do not install too many binaries here. With the old system running and
# the new binaries and headers installed you are likely to run into bootstrap
# problems. Ports should be compiled after you have booted in the new system.
#
all_remaining_customization () {
  # Without the compat symlink the linux_base files end up on the root fs:
  cd ${DESTDIR}
  mkdir -m 755 usr/compat; chown root:wheel usr/compat; ln -s usr/compat
  mkdir -m 755 usr/compat/linux;      chown root:wheel usr/compat/linux
  mkdir -m 555 usr/compat/linux/proc; chown root:wheel usr/compat/linux/proc
  mkdir -m 755 boot/grub;             chown root:wheel boot/grub
  mkdir -m 755 linux 2k;              chown root:wheel linux 2k
  mkdir -m 755 src;                   chown root:wheel src
  mkdir -m 755 share;                 chown root:wheel share
  mkdir -m 755 dvd cdrom flash;       chown root:wheel dvd cdrom flash
  mkdir -m 755 home;                  chown root:wheel home
  mkdir -m 755 usr/ports;             chown root:wheel usr/ports

  # Create the ntp and slip log files.
  touch ${DESTDIR}/var/log/ntp ${DESTDIR}/var/log/slip.log

  # Make /usr/src point to the right directory. Optional.
  # Note: some ports need part of the src tree, e.g. emulators/kqemu,
  # sysutils/lsof, sysutils/fusefs, ...
  cd ${DESTDIR}/usr
  if test "${SRC}" != /usr/src; then
    rmdir src; ln -s ${SRC}
  fi
  if test "${MAKEOBJDIRPREFIX}" != /usr/obj; then
    rmdir obj; ln -s ${MAKEOBJDIRPREFIX}
  fi

  # My personal preference is to symlink tmp -> var/tmp. Optional.
  cd ${DESTDIR}; rmdir tmp; ln -s var/tmp

  # Make spooldirs for the printers in my /etc/printcap.
  cd ${DESTDIR}/var/spool/output/lpd; mkdir -p as od ev te lp da
  touch ${DESTDIR}/var/log/lpd-errs

  # If you do not have /home on a shared partition, you may want to copy it:
  # mkdir -p ${DESTDIR}/home
  # cd /home; tar cf - . | (cd ${DESTDIR}/home; tar xpvf -)
}

# vim: tabstop=2:expandtab:shiftwidth=2:syntax=sh:
# EOF $RCSfile: stage_1.conf.default,v $

ダウンロード: stage_1.conf.default .

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

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


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

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

次に紹介する stage_2.sh スクリプトは、 わたしが好みの ports をインストールするために使ったものです。 これは何度でも実行でき、インストールずみの ports があれば、 飛ばして処理されます。スクリプトは 実行せず、実行される内容だけ を表示する (dryrun) オプション (-n) があります。実行時には stage_1.sh と同様、 設定スクリプトを示すためのひとつの引数を指定します。

# ./stage_2.sh default

これは、ports のリストを stage_2.conf.default というファイルから読み込みます。

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

www mozilla make WITHOUT_MAILNEWS=yes WITHOUT_CHATZILLA=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.conf.default です。 これは、インストールされる port それぞれに対して LOGDIR/category+port という名前のログファイルが作成されます。

# vim: syntax=sh
# $Id: stage_2.conf.default,v 1.2 2004/03/06 12:50:30 toor Exp toor $
# $FreeBSD: doc/en_US.ISO8859-1/articles/fbsd-from-scratch/stage_2.conf.default,v 1.4 2008/12/03 21:59:51 schweikh Exp $
ports-mgmt portaudit
devel ccache
shells zsh
devel gettext
archivers unzip
archivers zip
security sudo
x11 xorg
x11-servers xorg-server
x11-fonts xorg-fonts-100dpi
x11-fonts xorg-fonts-75dpi
x11-fonts xorg-fonts-miscbitmaps
x11-fonts xorg-fonts-truetype
x11-fonts xorg-fonts-type1
x11-fonts gnu-unifont make install && mkfontdir /usr/local/lib/X11/fonts/local
x11-fonts urwfonts
x11-fonts webfonts
x11-toolkits open-motif
x11-wm ctwm
x11 wdm
security openssh-askpass
astro xplanet
astro xephem
editors vim
print ghostscript8
print psutils-a4
print a2ps-a4
print gv
print transfig
print teTeX
print cups-base
emulators linux_base-fc6
print acroread8 yes accept | make install PAGER=ls
java jdk16 echo true > files/license.sh; make install BATCH=yes < /dev/null
www apache22
www amaya
www firefox3
www checkbot
www p5-HTML-Parser
www validator
www mplayer-plugin
math p5-Math-Combinatorics
math p5-Bit-Vector
graphics evince
graphics xfig
graphics xv
graphics gphoto2
multimedia xawtv
lang expect
lang gawk
lang python
news tin
net freebsd-uucp
net cvsup-without-gui
net rsync
ftp wget
textproc ispell
german ispell-neu
german ispell-alt
textproc docproj
sysutils samefile
sysutils smartmontools
sysutils pstree
sysutils cdrtools
sysutils dvd+rw-tools
sysutils grub
sysutils lsof
devel subversion-freebsd
devel bcc
devel ddd
devel gindent
devel ctags
devel ElectricFence
devel strace
devel perltidy
mail procmail
mail metamail
mail mutt-devel
ports-mgmt portupgrade
news inn CONFIGURE_ARGS="--enable-uucp-rnews --enable-setgid-inews" make BATCH=yes install < /dev/null
misc figlet-fonts
security gpa
mail spamoracle
textproc rman
multimedia mplayer
multimedia mplayer-fonts
multimedia acidrip
multimedia ogle
multimedia ogle-gui
audio pacpl
audio p5-CDDB_get
audio cowbell
shells bash
editors openoffice.org-3-RC
java eclipse
java netbeans

ダウンロード: stage_2.conf.default.


6. 第 3 段階

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

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

# make -f stage_3.mk target

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


7. 制限事項

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

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

# cd /usr/ports/editors/staroffice52
# 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 段階で次のようにしたわけです。

# pkg_add /usr/ports/editors/staroffice52/staroffice-5.2_1.tbz

その他に、設定ファイルのアップグレード問題に気をつける必要があります。 一般的に、設定ファイルの書式や内容がいつ変更されるかを知ることはできません。 新しいグループが /etc/group に追加されるかも知れませんし、/etc/passwd に新しいフィールドが追加されるかも知れません。 このような例は、実際に過去にありました。 単純に古いシステムから新しいシステムに設定ファイルをコピーするだけで ほとんどの場合は十分なのですが、時には不都合な場合もあります。 古いファイルを上書きする方法でシステムをアップグレードしたら、 ローカルにある設定ファイルに新しく追加されたかも知れない項目を統合する目的で mergemaster を使うと思います。 しかし残念なことに、mergemaster はベースシステムに存在するファイルだけで、インストールした ports については何も処理を行なってくれません。 サードパーティ製ソフトウェアには、 リリースのたびに設定ファイルのフォーマットが変更され、 わたしをイライラさせるようなものもあります。 このような予告なしの変更を検出するために、 わたしは変更した設定ファイルを stage_3.mk と同じディレクトリにコピーしておき、 make ルールを使って結果を比較しています。 たとえば、apachehttpd.conf であれば、次のような config_apache というターゲットを用意しておきます。

@if ! cmp -s /usr/local/etc/apache2/httpd.conf httpd.conf; then \
    echo "ATTENTION: the httpd.conf has changed. Please examine if"; \
    echo "the modifications are still correct. Here is the diff:"; \
    diff -u /usr/local/etc/apache2/httpd.conf httpd.conf; \
fi


差分が無害なものであると確認できたら、 cp /usr/local/etc/apache2/httpd.conf httpd.conf を実行するわけです。

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

Note: デバイスファイルシステム devfs(5) を使ってなければ、 all_remaining_customization の中で MAKEDEV(8) を使い、 ハードウェア用のデバイスファイルを作成するとよいでしょう。


8. ファイル

ここでは、すでに説明した設定ファイルの他に必要な、 3 個のファイルを示します。

これは stage_1.sh スクリプトです。内容を変更する必要はないでしょう。

#!/bin/sh
#
# stage_1.sh - FreeBSD From Scratch, Stage 1: System Installation.
#              Usage: ./stage_1.sh profile
#              will read profile
#              and write ./stage_1.log.profile
#
# Author:      Jens Schweikhardt
# $Id: stage_1.sh,v 1.7 2004/01/03 13:50:41 toor Exp toor $
# $FreeBSD: doc/en_US.ISO8859-1/articles/fbsd-from-scratch/stage_1.sh,v 1.7 2008/12/11 19:48:21 schweikh Exp $

PATH=/bin:/usr/bin:/sbin:/usr/sbin

# Prerequisites:
#
# a) Successfully completed "make buildworld" and "make buildkernel"
# b) Unused partitions (at least one for the root fs, probably more for
#    the new /usr and /var, to your liking.)
# c) A customized profile file.

if test $# -ne 1; then
  echo "usage: stage_1.sh profile" 1>&2
  exit 1
fi

# ---------------------------------------------------------------------------- #
# Step 1: Create an empty directory tree below $DESTDIR.
# ---------------------------------------------------------------------------- #

step_one () {
  create_file_systems
  # Now create all the other directories. Mandatory.
  cd ${SRC}/etc; make distrib-dirs DESTDIR=${DESTDIR} TARGET=${TARGET}
}

# ---------------------------------------------------------------------------- #
# Step 2: Fill the empty /etc directory tree and put a few files in /.
# ---------------------------------------------------------------------------- #

step_two () {
  copy_files

  # Delete mergemaster's temproot, if any.
  TEMPROOT=/var/tmp/temproot.stage1
  if test -d ${TEMPROOT}; then
    chflags -R 0 ${TEMPROOT}
    rm -rf ${TEMPROOT}
  fi
  export MAKEDEVPATH="/bin:/sbin:/usr/bin"
  mergemaster -i -m ${SRC}/etc -t ${TEMPROOT} -D ${DESTDIR}
  cap_mkdb ${DESTDIR}/etc/login.conf
  pwd_mkdb -d ${DESTDIR}/etc -p ${DESTDIR}/etc/master.passwd

  # Mergemaster does not create empty files, e.g. in /var/log. Do so now,
  # but do not clobber files that may have been copied with copy_files.
  cd ${TEMPROOT}
  find . -type f | sed 's,^\./,,' |
  while read f; do
    if test -r ${DESTDIR}/${f}; then
      echo "${DESTDIR}/${f} already exists; not copied"
    else
      echo "Creating empty ${DESTDIR}/${f}"
      cp -p ${f} ${DESTDIR}/${f}
    fi
  done
  chflags -R 0 ${TEMPROOT}
  rm -rf ${TEMPROOT}
}

# ---------------------------------------------------------------------------- #
# Step 3: Install world.
# ---------------------------------------------------------------------------- #

step_three () {
  cd ${SRC}
  make installworld DESTDIR=${DESTDIR} TARGET=${TARGET}
}

# ---------------------------------------------------------------------------- #
# Step 4: Install kernel and modules.
# ---------------------------------------------------------------------------- #

step_four () {
  cd ${SRC}
  # The loader.conf and device.hints are required by the installkernel target.
  # If you have not copied them in Step 2, cp them as shown in the next 2 lines.
  #   cp sys/boot/forth/loader.conf ${DESTDIR}/boot/defaults
  #   cp sys/${TARGET}/conf/GENERIC.hints ${DESTDIR}/boot/device.hints
  make installkernel DESTDIR=${DESTDIR} KERNCONF=${KERNCONF} TARGET=${TARGET}
}

# ---------------------------------------------------------------------------- #
# Step 5: Install /etc/fstab and time zone info.
# ---------------------------------------------------------------------------- #

step_five () {
  create_etc_fstab

  # Setup time zone info; pretty much mandatory.
  cp ${DESTDIR}/usr/share/zoneinfo/${TIMEZONE} ${DESTDIR}/etc/localtime
  if test -r /etc/wall_cmos_clock; then
    cp -p /etc/wall_cmos_clock ${DESTDIR}/etc/wall_cmos_clock
  fi
}

# ---------------------------------------------------------------------------- #
# Step 6: All remaining customization.
# ---------------------------------------------------------------------------- #

step_six () {
  all_remaining_customization
}

do_steps () {
  echo "PROFILE=${PROFILE}"
  echo "TARGET=${TARGET}"
  echo "DESTDIR=${DESTDIR}"
  echo "SRC=${SRC}"
  echo "KERNCONF=${KERNCONF}"
  echo "TIMEZONE=${TIMEZONE}"
  echo "TYPE=${TYPE}"
  echo "REVISION=${REVISION}"
  echo "BRANCH=${BRANCH}"
  echo "RELDATE=${RELDATE}"
  step_one
  step_two
  step_three
  step_four
  step_five
  step_six
}

# ---------------------------------------------------------------------------- #
# The ball starts rolling here.
# ---------------------------------------------------------------------------- #

PROFILE="$1"
set -x -e -u # Stop for any error or use of an undefined variable.
. ${PROFILE}

# Determine a few variables from the sources that were used to make the
# world. The variables can be used to modify actions, e.g. depending on
# the system's version. The __FreeBSD_version numbers
# for RELDATE are documented in the Porter's Handbook,
# doc/en_US.ISO8859-1/books/porters-handbook/freebsd-versions.html.
# Scheme is:  <major><two digit minor><0 if release branch, otherwise 1>xx
# The result will be something like
#
#   TYPE="FreeBSD"
#   REVISION="8.0"
#   BRANCH="RC"      { "CURRENT", "STABLE", "RELEASE" }
#   RELDATE="800028"
#
eval $(awk '/^(TYPE|REVISION|BRANCH)=/' ${SRC}/sys/conf/newvers.sh)
RELDATE=$(awk '/^[ \t]*#[ \t]*define[ \t][ \t]*__FreeBSD_version[ \t]/ {
                print $3
              }' ${SRC}/sys/sys/param.h)

echo "=> Logging to stage_1.${PROFILE}.log"
do_steps 2>&1 | tee "stage_1.${PROFILE}.log"

# vim: tabstop=2:expandtab:shiftwidth=2:
# EOF $RCSfile: stage_1.sh,v $

ダウンロード: stage_1.sh.

これは stage_2.sh スクリプトです。最初の部分にある変数を変更しましょう。

#!/bin/sh
#
# stage_2.sh - FreeBSD From Scratch, Stage 2: Ports Installation.
#              Usage: ./stage_2.sh [-hnp] configname
#
# Author:      Jens Schweikhardt
# $Id: stage_2.sh,v 1.5 2004/01/23 22:09:19 toor Exp toor $
# $FreeBSD: doc/en_US.ISO8859-1/articles/fbsd-from-scratch/stage_2.sh,v 1.5 2004/07/19 21:02:26 schweikh Exp $

DBDIR="/var/db/pkg"
PORTS="/usr/ports"
: ${PACKAGES:=${PORTS}/packages}
LOGDIR="/home/root/setup/ports.log"; mkdir -p ${LOGDIR}
PKG_PATH="/cdrom/packages/All:/dvd/packages/All"
PKG=

MYNAME="$(basename $0)"
usage () {
    exec >&2
    echo "usage: ${MYNAME} [-hnp] configname"
    echo ""
    echo "  Options:"
    echo "  -h    Print this help text."
    echo "  -n    Dryrun: just show what would be done."
    echo "  -p    Install a precompiled package if one can be found."
    echo ""
    echo "  The config file (stage_2.conf.configname) is a list of"
    echo "  ports to install with one entry per line. Each line"
    echo "  consists of two or three space separated fields:"
    echo "  category, port, and optionally a build command."
    echo ""
    exit 1
}

# Look for a package in these locations in sequence.
# Returns as soon as the first is found. Result on stdout.
#
#   ${PORTS}/${CATEGORY}/${NAME}
#   ${PACKAGES}/All
#   ${PACKAGES}/${CATEGORY}
#   ${PKG_PATH}
#
find_package () {
    echo "${PORTS}/${CATEGORY}/${NAME}:${PACKAGES}/All:${PACKAGES}/${CATEGORY}:${PKG_PATH}" |
    tr : '\n' |
    while read d; do
        test -d "${d}" || continue
        PKG=$(ls ${d}/${PKGNAME}.* 2>/dev/null)
        test $? -eq 0 && echo "${PKG}" && return
    done
}

#
# Parse command line arguments.
#
args=`getopt hnp $*`
if test $? != 0; then
    usage
fi
set -- $args
DRYRUN=
CHKPKG=
for i; do
    case "$i" in
    -n) DRYRUN="yes"; shift;;
    -p) CHKPKG="yes"; shift;;
    --) shift; break;;
    *) usage;;
    esac
done
if test $# -eq 1; then
    DATAFILE="$1"
else
    usage
fi

#
# Loop over the ports list.
#
while read CATEGORY NAME CMD; do
    case "${CATEGORY}" in
    \#*) continue;;
    '') continue;;
    esac
    DIR="${PORTS}/${CATEGORY}/${NAME}"
    if ! test -d "${DIR}"; then
        echo "$DIR does not exist -- ignored"
        continue
    fi
    cd ${DIR}
    PKGNAME=`make -V PKGNAME`
    if test -n "${CHKPKG}"; then
        PKG=$(find_package)
    else
        PKG=""
    fi
    if test -d "${DBDIR}/${PKGNAME}"; then
        echo "${CATEGORY}/${NAME} already installed as ${PKGNAME}"
        continue
    fi
    LOG="${LOGDIR}/${CATEGORY}+${NAME}"
    echo "===> Installing ${CATEGORY}/${NAME}; logging to ${LOG}"
    test -n "${CMD}" || CMD="make install BATCH=yes < /dev/null"
    if test -n "${DRYRUN}"; then
        if test -n "${PKG}"; then
            echo pkg_add -v ${PKG}
        else
            echo "${CMD}"
        fi
        continue
    fi
    date "++++ Started %v %T +++" > ${LOG}
    STARTED=$(date +%s)
    (
        if test -n "${PKG}"; then
            echo "Found package ${PKG}"
            pkg_add -v ${PKG}
        else
            echo "CMD: ${CMD}"
            make clean
            eval "${CMD}"
            make clean # Uncomment if diskspace is tight under ${PORTS}.
        fi
    ) 2>&1 | tee -a ${LOG}
    FINISHED=$(date +%s)
    DURATION=$(dc -e "${FINISHED} ${STARTED} - p")
    date "++++ Finished %v %T after ${DURATION} secs +++" >> ${LOG}
done < stage_2.conf.${DATAFILE}

# vim: tabstop=4:
# EOF $RCSfile: stage_2.sh,v $

ダウンロード: stage_2.sh.

これは、わたしが使っている stage_3.mk です。 設定を自動的におこなうための手順を、ここに入れます。

# stage_3.mk - FreeBSD From Scratch, Stage 3: Ports Post-Configuration.
#              Usage: make -f stage_3.mk all     (configure everything)
#                or   make -f stage_3.mk target  (just configure target)
#
# Author:      Jens Schweikhardt
#
# It is a good idea to make sure any target can be made more than
# once without ill effect.
#
# $Id: stage_3.mk,v 1.8 2004/03/27 16:53:11 toor Exp toor $
# $FreeBSD: doc/en_US.ISO8859-1/articles/fbsd-from-scratch/stage_3.mk,v 1.5 2008/12/03 21:59:51 schweikh Exp $

.POSIX:

message:
    @echo "Please use one of the following targets:"
    @echo "config_apache"
    @echo "config_cups"
    @echo "config_firefox"
    @echo "config_inn"
    @echo "config_javaplugin"
    @echo "config_openoffice"
    @echo "config_sudo"
    @echo "config_TeX"
    @echo "config_tin"
    @echo "config_wdm"
    @echo "config_uucp"
    @echo "all -- all of the above"

all: \
    config_apache \
    config_cups \
    config_firefox \
    config_inn \
    config_javaplugin \
    config_openoffice \
    config_sudo \
    config_TeX \
    config_tin \
    config_wdm \
    config_uucp

APACHE = apache22
config_apache:
    # 1. Modify httpd.conf.
    perl -pi \
    -e 's/^\s*ServerAdmin.*/ServerAdmin schweikh\@schweikhardt.net/;' \
    -e 's/^#?ServerName .*/ServerName hal9000.schweikhardt.net:80/;' \
    -e 's/^\s*Listen.*/Listen 127.0.0.1:80/;' \
    -e 's/^\s*Deny from all/    Allow from 127.0.0.1/i;' \
    -e 's,/usr/local/www/$(APACHE)/cgi-bin/,/home/opt/www/cgi-bin/,;' \
      /usr/local/etc/$(APACHE)/httpd.conf
    cp w3c-validator.conf /usr/local/etc/$(APACHE)/Includes
    # 2. Restore symlinks to web pages.
    cd /usr/local/www/$(APACHE)/data && \
    ln -fs /home/schweikh/prj/homepage schweikhardt.net && \
    ln -fs /home/opt/www/test .
    # 3. Restore W3C Validator config.
    mkdir -p /etc/w3c
    cp /usr/local/www/validator/htdocs/config/validator.conf.sample \
        /etc/w3c/validator.conf
    perl -pi \
    -e 's/^Allow Private IPs.*/Allow Private IPs = yes/;' \
        /etc/w3c/validator.conf
    # Test if the httpd.conf has changed.
    @if ! cmp -s /usr/local/etc/$(APACHE)/httpd.conf httpd.conf; then \
        echo "ATTENTION: the httpd.conf has changed. Please examine if"; \
        echo "the modifications are still correct. If so you can simply"; \
        echo "cp /usr/local/etc/$(APACHE)/httpd.conf httpd.conf"; \
        echo "to make this message go away. Here is the diff:"; \
        diff -u /usr/local/etc/$(APACHE)/httpd.conf httpd.conf; \
    fi
    if test -f /var/run/httpd.pid; then \
        /usr/local/etc/rc.d/$(APACHE) stop; \
        /usr/local/etc/rc.d/$(APACHE) start; \
    else \
        /usr/local/etc/rc.d/$(APACHE) start; \
    fi

# The original ppd file is from http://www.cups.org/ppd.php?L63+I0+T+Q2300
# = http://www.cups.org/ppd/hp/de/hpc2325s.ppd.gz
config_cups:
    chmod 644 /usr/local/etc/cups/cupsd.conf
    cp printers.conf /usr/local/etc/cups/printers.conf
    cp LaserJet_2300d.ppd /usr/local/etc/cups/ppd/LaserJet_2300d.ppd

config_firefox:
    # Make this group wheel writable to allow extensions being installed.
    chmod -R g+w /usr/local/lib/firefox3/chrome

config_inn:
    pw usermod -n news -d /usr/local/news -s /bin/sh
    mkdir -p /share/news/spool/outgoing \
             /share/news/spool/incoming \
             /share/news/spool/articles \
             /share/news/spool/overview \
             /share/news/spool/tmp      \
             /share/news/db
    chown -R news:news /share/news
    # Give the news system its initial configuration.
    cd /home/root/setup && \
    if test ! -f /share/news/db/active; then \
        echo "installing /share/news/db/active"; \
        install -C -o news -g news -m 664 active /share/news/db; \
    fi; \
    if test ! -f /share/news/db/newsgroups; then \
        echo "installing /share/news/db/newsgroups"; \
        install -C -o news -g news -m 664 newsgroups /share/news/db; \
    fi
    # Configure storage method.
    cd /home/root/setup &&    \
    printf "%s\n%s\n%s\n%s\n" \
        "method tradspool {"  \
        "  newsgroups: *"     \
        "  class: 0"          \
        "}"                   \
    >storage.conf &&          \
    install -C -o news -g news -m 664 storage.conf /usr/local/news/etc
    # Configure newsfeeds.
    printf "%s\n%s\n" \
        "ME:*::"      \
        "shuttle/news2.shuttle.de:!junk,!control:B32768/512,Tf,Wfb:" \
    >/usr/local/news/etc/newsfeeds
    # Configure inn.conf.
    perl -pi                                                        \
    -e 's/^#*\s*(organization:\s*).*/$$1"An Open Pod Bay Door"/;'   \
    -e 's/^#*\s*(pathhost:\s*).*/$$1hal9000.schweikhardt.net/;'     \
    -e 's/^#*\s*(server:).*/$$1 localhost/;'                        \
    -e 's/^#*\s*(domain:).*/$$1 schweikhardt.net/;'                 \
    -e 's/^#*\s*(fromhost:).*/$$1 schweikhardt.net/;'               \
    -e 's,^#*\s*(moderatormailer:).*,$$1 \%s\@moderators.isc.org,;' \
    -e 's,^#*\s*(pathdb:\s*).*,$$1/share/news/db,;'                 \
    -e 's,/usr/local/news/spool,/share/news/spool,;'                \
    /usr/local/news/etc/inn.conf
    # Create empty history, if none there.
    # See post-install in /usr/ports/news/inn-stable/Makefile.
    set -e; cd /share/news/db; \
    if test ! -f history; then \
        touch history; \
        chmod 644 history; \
        chown news:news history; \
        su -fm news -c "/usr/local/news/bin/makedbz -i"; \
        for s in dir hash index; do \
            mv history.n.$${s} history.$${s}; \
        done; \
    fi
    # Configure send-uucp.
    echo shuttle:shuttle >/usr/local/news/etc/send-uucp.cf
    # Satisfy inncheck:
    set -e; cd /usr/local/news/etc; \
    chown news:news *; \
    chmod 640 control.ctl expire.ctl nntpsend.ctl readers.conf
    /usr/local/news/bin/inncheck
    # Test if the inn.conf has changed.
    @if ! cmp -s /usr/local/news/etc/inn.conf inn.conf; then \
        echo "ATTENTION: the inn.conf has changed. Please examine if"; \
        echo "the modifications are still correct. If so you can simply"; \
        echo "cp /usr/local/news/etc/inn.conf inn.conf"; \
        echo "to make this message go away. Here is the diff:"; \
        diff -u /usr/local/news/etc/inn.conf inn.conf; \
    fi
    if ! test -f /usr/local/news/run/innd.pid; then \
        /usr/local/etc/rc.d/innd start; \
    fi

config_javaplugin:
    cd /usr/local/lib/firefox3/plugins && \
      ln -fs /usr/local/jdk1.6.0/jre/plugin/$$(uname -m)/ns7/libjavaplugin_oji.so

config_openoffice:
    # Copy some truetype files so ooo can use them.
    find /usr/local/openoffice.org* -type d -name truetype \
        -exec echo cp *.ttf {} \; -exec cp *.ttf {} \;

config_sudo:
    if ! grep -q schweikh /usr/local/etc/sudoers; then \
        echo 'schweikh ALL = (ALL) NOPASSWD: ALL' >> /usr/local/etc/sudoers; \
    fi
    chmod 440 /usr/local/etc/sudoers

config_TeX:
    # textproc/docproj advises: to typeset the FreeBSD Handbook with JadeTeX,
    # change the following settings to the listed values:
    perl -pi                                      \
    -e 's/^% original texmf.cnf/% texmf.cnf/;'    \
    -e 's/^(hash_extra\s*=\s*).*/$${1}60000/;'    \
    -e 's/^(pool_size\s*=\s*).*/$${1}1000000/;'   \
    -e 's/^(max_strings\s*=\s*).*/$${1}70000/;'   \
    -e 's/^(save_size\s*=\s*).*/$${1}10000/;'     \
    /usr/local/share/texmf/web2c/texmf.cnf
    # Test if the texmf.cnf has changed.
    @if ! cmp -s /usr/local/share/texmf/web2c/texmf.cnf texmf.cnf; then \
        echo "ATTENTION: the texmf.cnf has changed. Please examine if"; \
        echo "the modifications are still correct. If so you can simply"; \
        echo "cp /usr/local/share/texmf/web2c/texmf.cnf texmf.cnf"; \
        echo "to make this message go away. Here is the diff:"; \
        diff -u /usr/local/share/texmf/web2c/texmf.cnf texmf.cnf; \
    fi

config_tin:
    # Point tin to our files.
    printf "%s\n%s\n%s\n"                          \
        "activefile=/share/news/db/active"         \
        "newsgroupsfile=/share/news/db/newsgroups" \
        "spooldir=/share/news/spool/articles"      \
    >/usr/local/etc/tin.defaults

config_wdm:
    cp daemon1-JS-1600x1200.jpg FreeBSD_small.png \
        /usr/local/lib/X11/wdm/pixmaps
    perl -pi \
    -e 's,^(DisplayManager\*wdmBg:).*,\1 pixmap:/usr/local/lib/X11/wdm/pixmaps/daemon1-JS-1600x1200.jpg,;' \
    -e 's,^(DisplayManager\*wdmLogo:).*,\1 /usr/local/lib/X11/wdm/pixmaps/FreeBSD_small.png,;' \
    -e 's,^(DisplayManager\*wdmWm:).*,\1 ctwm:icewm:xfce4:tvtwm,;' \
        /usr/local/lib/X11/wdm/wdm-config
    @if ! cmp -s /usr/local/lib/X11/wdm/wdm-config wdm-config; then \
        echo "ATTENTION: the wdm-config has changed. Please examine if"; \
        echo "the modifications are still correct. If so you can simply"; \
        echo "cp /usr/local/lib/X11/wdm/wdm-config wdm-config"; \
        echo "to make this message go away. Here is the diff:"; \
        diff -u /usr/local/lib/X11/wdm/wdm-config wdm-config; \
    fi

config_uucp:
    cd /etc/mail && make install SENDMAIL_MC=/etc/mail/hal9000.mc
    # Make the uucp user's shell the correct uucico, so su(1) works.
    chpass -s /usr/local/libexec/uucp/uucico uucp
    # UUCP expects to find /usr/bin/rnews.
    cd /usr/bin && ln -fs ../local/news/bin/rnews .
    # Actual UUCP configuration.
    echo nodename js2015           > /usr/local/etc/uucp/config
    echo shuttle js2015 `cat uucp` > /usr/local/etc/uucp/call
    printf 'port tcp\ntype tcp\n'  > /usr/local/etc/uucp/port
    printf "%s\n"                         \
        "call-login    *"                 \
        "call-password *"                 \
        "time          any"               \
        "system        shuttle"           \
        "address       mail.s.shuttle.de" \
        "commands      rmail rnews"       \
        "port          tcp"               \
    >/usr/local/etc/uucp/sys
    cd /usr/local/etc/uucp && chown uucp:uucp * && chmod o-rwx *
    # Trigger uucico after booting.
    mkdir -p /usr/local/etc/rc.d
    cp uucp.sh /usr/local/etc/rc.d
    # Rebuild the aliases.db.
    cp aliases /etc/mail/aliases
    newaliases

# vim: tabstop=4:
# EOF $RCSfile: stage_3.mk,v $

ダウンロード: stage_3.mk.