doc/en_US.ISO8859-1/articles/fbsd-from-scratch/article.sgml
Jens Schweikhardt e1f0bf4fc3 New article: FreeBSD From Scratch
This article describes my efforts at FreeBSD From Scratch: a fully
automated installation of a customized FreeBSD system compiled from
source, including compilation of all your favorite ports and configured
to match your idea of the perfect system. If you think "make world" is a
wonderful concept, FreeBSD From Scratch extends it to "make evenmore".
2003-03-08 08:26:10 +00:00

571 lines
25 KiB
Text

<!DOCTYPE article PUBLIC "-//FreeBSD//DTD DocBook V4.1-Based Extension//EN" [
<!ENTITY % man PUBLIC "-//FreeBSD//ENTITIES DocBook Manual Page Entities//EN">
%man;
<!ENTITY % freebsd PUBLIC "-//FreeBSD//ENTITIES DocBook Miscellaneous FreeBSD Entities//EN">
%freebsd;
<!ENTITY scratch.ap "<application>FreeBSD From Scratch</application>">
]>
<article>
<articleinfo>
<title>FreeBSD From Scratch</title>
<author>
<firstname>Jens</firstname>
<surname>Schweikhardt</surname>
<affiliation>
<address><email>schweikh@FreeBSD.org</email></address>
</affiliation>
</author>
<copyright>
<year>2002</year>
<holder>Jens Schweikhardt</holder>
</copyright>
<pubdate>$FreeBSD$</pubdate>
</articleinfo>
<abstract>
<para>This article describes my efforts at &scratch.ap;: a fully
automated installation of a customized &os; system compiled from
source, including compilation of all your favorite ports and
configured to match your idea of the perfect system. If you
think <command>make world</command> is a wonderful concept,
&scratch.ap; extends it to <command>make evenmore</command>.</para>
</abstract>
<sect1 id="introduction">
<title>Introduction</title>
<para>Have you ever upgraded your system with <command>make world</command>?
There is a problem if you have only one system on your disks. If
the <maketarget>installworld</maketarget> fails partway through,
you are left with a broken system that might not even boot any
longer. Or maybe the <maketarget>installworld</maketarget> runs
smoothly but the new kernel does not boot. Then it is time to
reach for the Fixit CD and dig for those backups you have taken
half a year ago.</para>
<para>I believe in the <quote>wipe your disks when upgrading systems</quote>
paradigm. Wiping disks, or rather partitions, makes sure there is no
old cruft left lying around, something which most upgrade procedures
just do not care about. But wiping the partitions means you have to
also recompile/reinstall all your ports and packages and then
redo all your carefully crafted configuration tweaks.
If you think that this task should be automated as well, read on.</para>
</sect1>
<sect1 id="why">
<title>Why would I (not) want &scratch.ap;?</title>
<para>This is a legitimate question. We have
<application>sysinstall</application> and the well known way to
compile the kernel and the userland tools.</para>
<para>The problem with <application>sysinstall</application> is
that it is severely limited in what, where and how it can install.</para>
<itemizedlist>
<listitem>
<para>It is normally used to install pre-built distribution sets and
packages from some other source (CD, DVD, FTP). It cannot install
the result of a <literal>make buildworld</literal>.</para>
</listitem>
<listitem>
<para>It cannot install a second system under a directory
in a running system.</para>
</listitem>
<listitem>
<para>It cannot install in <application>Vinum</application>
partitions.</para>
</listitem>
<listitem>
<para>It cannot compile ports, only install precompiled packages.</para>
</listitem>
<listitem>
<para>It is hard to script or to make arbitrary post-installation
changes.</para>
</listitem>
<listitem>
<para>Last but not least, <application>sysinstall</application>
is semi-officially at its End-Of-Life.</para>
</listitem>
</itemizedlist>
<para>The well known way to build and install the world, as
described in <ulink url="http://www.freebsd.org/doc/en_US.ISO8859-1/books/handbook/makeworld.html">the
Handbook</ulink>, by default replaces
the existing system. Only the kernel and modules are saved.
System binaries, headers and a lot of other files are overwritten;
obsolete files are still present and can cause surprises. If the
upgrade fails for any reason, it may be hard or even impossible to
restore the previous state of the system.</para>
<para>&scratch.ap; solves all these problems. The strategy is
simple: use a running system to install a new system under an empty
directory tree, while new partitions are mounted appropriately
in that tree. Many config files can be copied to the appropriate
place and &man.mergemaster.8; can take care of those that cannot.
Arbitrary post-configuration of the new system can be
done from within the old system, up to the point where you can
chroot to the new system. In other words, we go through three
stages, where each stage consists of either running a shell
script or invoke <command>make</command>:</para>
<orderedlist>
<listitem>
<para><filename>stage_1.sh</filename>:
Create a new bootable system under an empty directory and merge
or copy as many files as are necessary.
Then boot the new system.</para>
</listitem>
<listitem>
<para><filename>stage_2.sh</filename>:
Install desired ports.</para>
</listitem>
<listitem>
<para><filename>stage_3.mk</filename>:
Do post-configuration for software installed in previous stage.</para>
</listitem>
</orderedlist>
<para>Once you have used &scratch.ap; to build a second system and
found it works satisfactorily for a couple of weeks, you can then
use it again to reinstall the original system. From now on, whenever
you feel like an update is in order, you simply toggle the
partitions you want to wipe and reinstall.</para>
<para>Maybe you have heard of or even tried <ulink
url="http://www.linuxfromscratch.org/">Linux From Scratch</ulink>,
or LFS for short. LFS also describes how to build and install a
system from scratch in empty partitions using a running system.
The focus in LFS seems to be to show the role of each system
component (such as kernel, compiler, devices, shell, terminal database,
etc) and the details of each component's installation.
&scratch.ap; does not go into that much detail. My goal is to
provide an automated and complete installation, not explaining all
the gory details that go on under the hood when making the world.
In case you want to explore &os; at this level of detail, start
looking at <filename>/usr/src/Makefile</filename> and follow the
actions of a <command>make buildworld</command>.</para>
<para>There are also downsides in the approach taken by &scratch.ap;
that you should bear in mind.</para>
<!-- XXX: A nice idea would be to write stage2.sh using a jail
that runs into the newly installed world from stage1. Having
properly set up a network address as the jail's primary IP
address, it might even be possible to build ports in a chroot
without uninstalling anything from the 'host' system. But
keep in mind that even jails run on the 'host' kernel. -->
<itemizedlist>
<listitem>
<para>While compiling the ports during stage two the system can
not be used for its usual duties. If you run a production server
you have to consider the downtime caused by stage two. The ports
compiled by <filename>stage_2.sh</filename> below require about 4
hours to build on an AMD1800+ SCSI system with 10krpm disks and
1GB of RAM.</para>
</listitem>
</itemizedlist>
</sect1>
<sect1 id="prerequisites">
<title>Prerequisites</title>
<para>For going the &scratch.ap; way, you need to have:</para>
<itemizedlist>
<listitem>
<para>A running &os; system with sources and a ports tree.</para>
</listitem>
<listitem>
<para>At least one unused partition where the new system will be
installed.</para>
</listitem>
<listitem>
<para>Experience with running &man.mergemaster.8;. Or at least no fear
doing so.</para>
</listitem>
<listitem>
<para>If you have no or only a slow link to the Internet: the distfiles
for your favorite ports.</para>
</listitem>
<listitem>
<para>Basic knowledge of shell scripting with the Bourne shell,
&man.sh.1;.</para>
</listitem>
<listitem>
<para>Finally, you should also be able to tell your boot
loader how to boot the new system, either interactively, or
by means of a config file.</para>
</listitem>
</itemizedlist>
</sect1>
<sect1 id="stage1">
<title>Stage One: System Installation</title>
<para>The following is my <filename>stage_1.sh</filename>. You
need to customize it in various places to match your idea of
the <quote>perfect system</quote>. I have tried to
extensively comment the places you should adapt. The points to
ponder are:</para>
<itemizedlist>
<listitem>
<para>Partition layout.</para>
<para>I do not subscribe to the idea of a single huge partition
for the whole system. My systems generally have at least
one partition for
<filename>/</filename>,
<filename>/usr</filename> and
<filename>/var</filename> with
<filename>/tmp</filename> symlinked to
<filename>/var/tmp</filename>.
In addition I share the file systems for
<filename>/home</filename> (user homes),
<filename>/home/ncvs</filename> (&os; CVS repository replica),
<filename>/usr/ports</filename> (the ports tree),
<filename>/src</filename> (various checked out src trees) and
<filename>/share</filename> (other shared data without the need
for backups, like the news spool).</para>
</listitem>
<listitem>
<para>Luxury items.</para>
<para>What you want immediately after booting the new system and
even before starting stage two. In my case this is <filename
role="package">shells/zsh</filename> because this is the login
shell for my account as specified in <filename>/etc/passwd</filename>.
Strictly speaking you can do without luxury items, because all you
need to do is log in as root and run the next stage.</para>
<para>The reason for not simply installing all my beloved ports
during stage one is that in theory and in practice there are
bootstrap and consistency issues: stage one has your old kernel
running, but the chrooted environment consists of new binaries
and headers. If the new system for example supports a new system
call (according to its headers), some configure-type script
might try to use it and get killed because it executes on the
old kernel. I have seen other issues when I tried building
<filename role="package">lang/perl5</filename>.</para>
</listitem>
</itemizedlist>
<para>Before you run <filename>stage_1.sh</filename> make sure
you have completed the usual tasks in preparation for
<command>make installworld installkernel</command>, like:</para>
<itemizedlist>
<listitem>
<para>configured your kernel config file</para>
</listitem>
<listitem>
<para>successfully completed <command>make buildworld</command></para>
</listitem>
<listitem>
<para>successfully completed <command>make buildkernel
KERNCONF=<replaceable>whatever</replaceable></command></para>
</listitem>
</itemizedlist>
<para>When you run <filename>stage_1.sh</filename> for the first
time, and the config files copied from your running system to the
new system are not up-to-date with respect to what is under
<filename>/usr/src</filename>, <command>mergemaster</command> will
ask you how to proceed. I recommend merging the changes. If you get
tired of going through the dialogues you can simply update the files
on your <emphasis>running</emphasis> system once (Only if this is an
option. You probably do not want to do this if one of your systems
runs <literal>-STABLE</literal> and the other
<literal>-CURRENT</literal>. The changes may be incompatible).
Subsequent <command>mergemaster</command> invocations will detect
that the RCS version IDs match those under
<filename>/usr/src</filename> and skip the file.</para>
<para>The <filename>stage_1.sh</filename> script will stop at the
first command that fails (returns a non-zero exit status) due to
<command>set -e</command>, so you cannot overlook errors. You should
correct any errors in your version of
<filename>stage_1.sh</filename> before you go on.</para>
<para>In <filename>stage_1.sh</filename> we invoke
<command>mergemaster</command>. Even if none of the files requires a
merge, it will display and ask at the end</para>
<screen>*** Comparison complete
Do you wish to delete what is left of /var/tmp/temproot.stage1? [no] <userinput>no</userinput></screen>
<para>Please answer <literal>no</literal> or just hit
<keycap>Enter</keycap>. The reason is that <command>mergemaster</command>
will have left a few zero sized files below
<filename>/var/tmp/temproot.stage1</filename> which will be copied to the
new system later (unless already there).</para>
<para>After that it will list the files it installed, making use of
a pager (&man.more.1; by default, optionally &man.less.1;):</para>
<screen>*** 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)</screen>
<para>Type <keycap>q</keycap> to quit the pager. Then you will
be informed about <filename>login.conf</filename>:</para>
<screen>*** 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]</screen>
<para>The answer does not matter since we will run &man.cap.mkdb.1; in any
case.</para>
<para>Everything <filename>stage_1.sh</filename> does is logged to
<filename>stage_1.log</filename> for you to examine if you wish to
do so.</para>
<para>Here is the author's <filename>stage_1.sh</filename>, which
you need to modify substantially, especially steps 1, 2, 5 and 6.</para>
<warning>
<para>Please pay attention to the &man.newfs.8; commands.
While you can not create new file systems on mounted partitions, the
script will happily erase any unmounted
<filename>/dev/da3s1a</filename>, <filename>/dev/vinum/var_a</filename>
and <filename>/dev/vinum/usr_a</filename>. This can be enough to ruin
your day, so be sure to modify the device names.</para>
</warning>
<programlisting><inlinegraphic fileref="stage_1.sh" format="linespecific"></programlisting>
<para>Download <ulink
url="stage_1.sh"><filename>stage_1.sh</filename></ulink>.</para>
<para>Running this script installs a system that when booted
provides:</para>
<itemizedlist>
<listitem>
<para>Inherited users and groups.</para>
</listitem>
<listitem>
<para>Firewalled Internet connectivity over Ethernet and PPP.</para>
</listitem>
<listitem>
<para>Correct time zone and NTP.</para>
</listitem>
<listitem>
<para>Some more minor configuration, like
<filename>/etc/ttys</filename> and
<command>inetd</command>.</para>
</listitem>
</itemizedlist>
<para>Other areas are prepared for configuration, but will not work
until stage two is completed. For example we have copied files to
configure printing and X11. Printing however is likely to need
applications not found in the base system, like PostScript
utilities. X11 will not run before we have compiled the server,
libraries and programs.</para>
</sect1>
<sect1 id="stage2">
<title>Stage Two: Ports Installation</title>
<note>
<para>It is also possible to install the (precompiled)
packages at this stage, instead of compiling ports. In this case,
<filename>stage_2.sh</filename> would be nothing more than a list of
<command>pkg_add</command> commands. I trust you know how to write
such a script. Here we concentrate on the more flexible and
traditional way of using the ports.</para>
</note>
<para>The following <filename>stage_2.sh</filename> script is how I
install my favorite ports. It can be run any number of times and
will skip all ports that are already installed. It supports the
<emphasis>dryrun</emphasis> option (<option>-n</option>) to just
show what would be done. You will certainly want to edit the list of
ports, and possibly change a few environment variables.</para>
<para>The list of ports consists of lines with two or more space
separated words: the category and the port, optionally followed by
an installation command that will compile and install the port
(default: <command>make install</command>). Empty lines and lines
starting with # are ignored. Most of the time it suffices to only
name category and port. A few ports however can be fine tuned by
specifying <command>make</command> variables, e.g.:</para>
<programlisting>www mozilla make WITHOUT_MAILNEWS=yes WITHOUT_CHATZILLA=yes install
mail procmail make BATCH=yes install</programlisting>
<para>In fact you can specify arbitrary shell commands, so you are
not restricted to simple <command>make</command> invocations:</para>
<programlisting>java linux-sun-jdk13 yes | make install
news inn-stable CONFIGURE_ARGS="--enable-uucp-rnews --enable-setgid-inews" make install</programlisting>
<para>Note that the line for
<filename role="package">news/inn-stable</filename> is an example
for a one-shot shell variable assignment to
<literal>CONFIGURE_ARGS</literal>. The port
<filename>Makefile</filename> will use this as an initial value
and augment some other essential args. The difference to
specifying a <filename>make</filename> variable on the command line
with</para>
<programlisting>news inn-stable make CONFIGURE_ARGS="--enable-uucp-rnews --enable-setgid-inews" install</programlisting>
<para>is that the latter will override instead of augment. It depends on
the particular port which method you want.</para>
<para>Be careful that your ports do not use an interactive install, i.e.
they should not try to read from stdin other than what you explicitly
give them on stdin. If they do, they will read the next line(s) from
your list of ports in the here-document and get confused. If
<filename>stage_2.sh</filename> mysteriously skips a port or stops
processing, this is likely the reason.</para>
<para>Here is <filename>stage_2.sh</filename>. It creates a log file
named <filename>LOGDIR/category+port</filename> for each port
it actually installs. If you do not have
<filename>stage_2.sh</filename> on a shared partition make sure you
copy it to the new system before you boot it.</para>
<programlisting><inlinegraphic fileref="stage_2.sh" format="linespecific"></programlisting>
<para>Download <ulink
url="stage_2.sh"><filename>stage_2.sh</filename></ulink>.</para>
</sect1>
<sect1 id="stage3">
<title>Stage Three</title>
<para>You have installed your beloved ports during stage two. Some
ports require a little bit of configuration. This is what stage three,
the post-configuration is for. I could have integrated this
post-configuration at the end of the <filename>stage_2.sh</filename>
script. However, I think there is a conceptual difference between
installing a port and modifying its out-of-the-box configuration
that warrants a separate stage.</para>
<para>I have chosen to implement stage three as a
<filename>Makefile</filename> because this allows easy selection of
what you want to configure simply by running:</para>
<informalexample>
<screen>&prompt.root; <userinput>make -f stage_3.mk <replaceable>target</replaceable></userinput></screen>
</informalexample>
<para>As with <filename>stage_2.sh</filename> make sure you have
<filename>stage_3.mk</filename> available after booting the new
system, either by putting it on a shared partition or copying it
somewhere on the new system.</para>
<programlisting><inlinegraphic fileref="stage_3.mk" format="linespecific"></programlisting>
<para>Download <ulink
url="stage_3.mk"><filename>stage_3.mk</filename></ulink>.</para>
</sect1>
<sect1 id="limitations">
<title>Limitations</title>
<para>The automated installation of a port may prove difficult if it
is interactive and does not support <command>make BATCH=YES
install</command>. For a few ports the interaction is nothing more
than typing <literal>yes</literal> when asked to accept some license.
If such input is read from the standard input, we simply pipe the
appropriate answers to the installation command (usually <command>make
install</command>; this is how we deal with <filename
role="package">java/linux-sun-jdk13</filename> in
<filename>stage_2.sh</filename>).</para>
<para>This strategy for example does not work for <filename
role="package">editors/staroffice52</filename>, which requires that
X11 is running. The installation procedure involves a fair amount of
clicking and typing, so it cannot be automated like other ports can.
However the following workaround does the trick for me: first I
create a staroffice package on the old system with</para>
<informalexample>
<screen>&prompt.root; <userinput>cd /usr/ports/editors/staroffice52</userinput>
&prompt.root; <userinput>make package</userinput>
===> 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'</screen>
</informalexample>
<para>and during stage two I simply use:</para>
<informalexample>
<screen>&prompt.root; <userinput>pkg_add /usr/ports/editors/staroffice52/staroffice-5.2_1.tbz</userinput></screen>
</informalexample>
<para>You should also be aware of upgrade issues for config files.
In general you do not know when and if the format or contents of a
config file changes. A new group may be added to
<filename>/etc/group</filename>, or <filename>/etc/passwd</filename>
may gain another field. All of this has happened in the past. Simply
copying a config file from the old to the new system may be enough
most of the time, but in these cases it was not. If you update a
system the canonical way (by overwriting the old files) you are
expected to use <command>mergemaster</command> to deal with changes
where you effectively want to merge your local config with
potentially new items. Unfortunately, <command>mergemaster</command>
is only available for base system files, not for anything installed
by ports. Some third party software seems to be especially designed
to keep me on my toes by changing the config file format every
fortnight. All you can do is be alert, especially when the major
version number bumps. In the past I had to tweak or rewrite files
for web servers, news servers and readers. All software actively
maintained is a prime candidate for config file scrutiny.</para>
<para>I have used &scratch.ap; several times to update a
<literal>5-CURRENT</literal> to <literal>5-CURRENT</literal>, i.e.
I have never tried to install a <literal>5-CURRENT</literal> from
a <literal>4-STABLE</literal> system or vice versa. Due to the
number of changes between different major release numbers I would
expect this process to be a bit more involved. Using &scratch.ap;
for upgrades within the realm of <literal>4-STABLE</literal>
should work painlessly (although I have not yet tried it.) Users of
<literal>4-STABLE</literal> may want to consider the following
areas:</para>
<itemizedlist>
<listitem>
<para>If you do not use the device file system
(<literal>devfs</literal>) you may want to create devices for
some of your hardware with &man.MAKEDEV.8; in stage one, step
six.</para>
</listitem>
</itemizedlist>
</sect1>
</article>