FreeBSD From Scratch

Jens Schweikhardt

schweikh@FreeBSD.org

$FreeBSD$


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 universe.


1 Introduction

Have you ever upgraded your system with make world? There is a problem if you have only one system on your disks. If the installworld fails partway through, you are left with a broken system that might not even boot any longer. Or maybe the installworld 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.

I believe in the ``wipe your disks when upgrading systems'' 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.


2 Why would I (not) want FreeBSD From Scratch?

This is a legitimate question. We have sysinstall and the well known way to make the world.

The problem with sysinstall is that it is severely limited in what, where and how it can install.

The well known way to build and install the world, as described in the Handbook, 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.

FreeBSD From Scratch 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 for those that cannot, mergemaster(8) is used to take care of them. 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 make:

  1. stage_1.sh: Create a new bootable system under an empty directory and merge or copy as many files as are necessary. Then boot the new system.

  2. stage_2.sh: Install desired ports.

  3. stage_3.mk: Do post-configuration for software installed in previous stage.

Once you have used FreeBSD From Scratch 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.

Maybe you have heard of or even tried Linux From Scratch, 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. FreeBSD From Scratch 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 FreeBSD at this level of detail, start looking at /usr/src/Makefile and follow the actions of a make buildworld.

There are also downsides in the approach taken by FreeBSD From Scratch that you should bear in mind.


3 Prerequisites

For going the FreeBSD From Scratch way, you need to have:


4 Stage One: System Installation

The following is my stage_1.sh. You need to customize it in various places to match your idea of the ``perfect system''. I have tried to extensively comment the places you should adapt. The points to ponder are:

Before you run stage_1.sh make sure you have completed the usual tasks in preparation for make installworld installkernel, like:

When you run stage_1.sh 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 /usr/src, mergemaster 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 running system once (Only if this is an option. You probably do not want to do this if one of your systems runs -STABLE and the other -CURRENT. The changes may be incompatible). Subsequent mergemaster invocations will detect that the RCS version IDs match those under /usr/src and skip the file.

The stage_1.sh script will stop at the first command that fails (returns a non-zero exit status) due to set -e, so you cannot overlook errors. You should correct any errors in your version of stage_1.sh before you go on.

In stage_1.sh we invoke mergemaster. Even if none of the files requires a merge, it will display and ask at the end

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

Please answer no or just hit Enter. The reason is that mergemaster will have left a few zero sized files below /var/tmp/temproot.stage1 which will be copied to the new system later (unless already there).

After that it will list the files it installed, making use of a pager (more(1) by default, optionally 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)

Type q to quit the pager. Then you will be informed about 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]

The answer does not matter since we will run cap_mkdb(1) in any case.

Everything stage_1.sh does is logged to stage_1.log for you to examine if you wish to do so.

Here is the author's stage_1.sh, which you need to modify substantially, especially steps 1, 2, 5 and 6.

Warning: Please pay attention to the newfs(8) commands. While you can not create new file systems on mounted partitions, the script will happily erase any unmounted /dev/da3s1a, /dev/vinum/var_a and /dev/vinum/usr_a. This can be enough to ruin your day, so you better modify the device names.

    #!/bin/sh
    #
    # stage_1.sh - FreeBSD From Scratch, Stage 1: System Installation.
    #              Usage: ./stage_1.sh
    #
    # $Id: stage_1.sh,v 1.2 2002/12/08 13:38:51 toor Exp toor $
    
    set -x -e
    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.)
    
    # 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
    SRC=/usr/src         # Where your src tree is.
    
    # ---------------------------------------------------------------------------- #
    # Step 1: Create an empty directory tree below $DESTDIR.
    # ---------------------------------------------------------------------------- #
    
    step_one () {
      # The new root file system. Mandatory.
      # Change device names (DEV_*) or risk foot shooting.
      DEV_ROOT=/dev/da3s1a
      mkdir -p ${DESTDIR}
      newfs ${DEV_ROOT}
      tunefs -n enable ${DEV_ROOT}
      mount -o noatime ${DEV_ROOT} ${DESTDIR}
    
      # Additional file systems and initial mount points. Optional.
      DEV_VAR=/dev/vinum/var_a
      newfs ${DEV_VAR}
      tunefs -n enable ${DEV_VAR}
      mkdir -m 755 ${DESTDIR}/var
      mount -o noatime ${DEV_VAR} ${DESTDIR}/var
    
      DEV_USR=/dev/vinum/usr_a
      newfs ${DEV_USR}
      tunefs -n enable ${DEV_USR}
      mkdir -m 755 ${DESTDIR}/usr
      mount -o noatime ${DEV_USR} ${DESTDIR}/usr
    
      mkdir -m 755 -p ${DESTDIR}/usr/ports
      mount /dev/vinum/ports ${DESTDIR}/usr/ports
    
      # Now create all the other directories. Mandatory.
      cd ${SRC}/etc; make distrib-dirs DESTDIR=${DESTDIR}
      # My personal preference is to symlink tmp -> var/tmp. Optional.
      cd ${DESTDIR}; rmdir tmp; ln -s var/tmp
    }
    
    # ---------------------------------------------------------------------------- #
    # Step 2: Fill the empty /etc directory tree and put a few files in /.
    # ---------------------------------------------------------------------------- #
    
    step_two () {
      # Add or remove from this list at your discretion. Mostly mandatory.
      for f in \
        /.profile \
        /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/start_if.xl0 \
        /etc/ttys \
        /etc/ppp/* \
        /etc/mail/aliases \
        /etc/mail/aliases.db \
        /etc/mail/hal9000.mc \
        /etc/mail/service.switch \
        /etc/ssh/*key* \
        /etc/ssh/*_config \
        /etc/X11/XF86Config-4 \
        /boot/splash.bmp \
        /boot/loader.conf \
        /boot/device.hints ; do
        cp -p ${f} ${DESTDIR}${f}
      done
      # Delete mergemaster's temproot, if any.
      TEMPROOT=/var/tmp/temproot.stage1
      if test -d ${TEMPROOT}; then
        chflags -R 0 ${TEMPROOT}
        rm -rf ${TEMPROOT}
      fi
      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 in the loop above.
      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}
    }
    
    # ---------------------------------------------------------------------------- #
    # 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/i386/conf/GENERIC.hints ${DESTDIR}/boot/device.hints
      make installkernel DESTDIR=${DESTDIR} KERNCONF=HAL9000
    }
    
    # ---------------------------------------------------------------------------- #
    # Step 5: Install or modify a few essential files.
    # ---------------------------------------------------------------------------- #
    
    step_five () {
      # Create /etc/fstab; mandatory. Modify to match your devices.
      cat <<EOF >${DESTDIR}/etc/fstab
    # Device         Mountpoint          FStype    Options              Dump Pass#
    /dev/da3s1b      none                swap      sw                   0    0
    /dev/da4s2b      none                swap      sw                   0    0
    /dev/da3s1a      /                   ufs       rw                   1    1
    /dev/da1s2a      /src                ufs       rw                   0    2
    /dev/da2s2f      /share              ufs       rw                   0    2
    /dev/vinum/var_a /var                ufs       rw                   0    2
    /dev/vinum/usr_a /usr                ufs       rw                   0    2
    /dev/vinum/home  /home               ufs       rw                   0    2
    /dev/vinum/ncvs  /home/ncvs          ufs       rw,noatime           0    2
    /dev/vinum/ports /usr/ports          ufs       rw,noatime           0    2
    #
    /dev/cd0         /dvd                cd9660    ro,noauto            0    0
    /dev/cd1         /cdrom              cd9660    ro,noauto            0    0
    proc             /proc               procfs    rw                   0    0
    EOF
    
      # More directories; optional.
      mkdir -m 755 -p ${DESTDIR}/src;       chown root:wheel ${DESTDIR}/src
      mkdir -m 755 -p ${DESTDIR}/share;     chown root:wheel ${DESTDIR}/share
      mkdir -m 755 -p ${DESTDIR}/dvd;       chown root:wheel ${DESTDIR}/dvd
      mkdir -m 755 -p ${DESTDIR}/home;      chown root:wheel ${DESTDIR}/home
      mkdir -m 755 -p ${DESTDIR}/usr/ports; chown root:wheel ${DESTDIR}/usr/ports
      # Setup time zone info; pretty much mandatory.
      cp ${DESTDIR}/usr/share/zoneinfo/Europe/Berlin ${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: Things important to me when I first login to a 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.
    # ---------------------------------------------------------------------------- #
    
    step_six () {
      chroot ${DESTDIR} sh -c "cd /usr/ports/shells/zsh; make clean install clean"
      chroot ${DESTDIR} sh -c "cd /etc/mail; make install"  # configure sendmail
    
      # 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
      mkdir -m 755 boot/grub
    
      # 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
    
      # More files I want to inherit from the old system.
      for f in \
        /var/cron/tabs/root \
        /var/mail/* \
        /boot/grub/*; do
        cp -p ${f} ${DESTDIR}${f}
      done
    
      # 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 -)
    
      # Starting with FreeBSD 5.x, perl lives in /usr/local/bin but many scripts
      # use a hardcoded #!/usr/bin/perl; use a symlink to make them work.
      cd ${DESTDIR}/usr/bin; ln -s ../local/bin/perl
      cd ${DESTDIR}/usr; rmdir src; ln -s ../src/current src
    }
    
    do_steps () {
      step_one
      step_two
      step_three
      step_four
      step_five
      step_six
    }
    
    do_steps 2>&1 | tee stage_1.log
    
    # EOF $RCSfile: stage_1.sh,v $    vim: tabstop=2:expandtab:

Download stage_1.sh.

Running this script installs a system that when booted provides:

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.


5 Stage Two: Ports Installation

Note: It is also possible to install the (precompiled) packages at this stage, instead of compiling ports. In this case, stage_2.sh would be nothing more than a list of pkg_add 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.

The following stage_2.sh 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 dryrun option (-n) to just show what would be done. You will certainly want to edit the list of ports, and possibly change a few environment variables.

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: make install). 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 make variables, e.g.:

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

In fact you can specify arbitrary shell commands, so you are not restricted to simple make invocations:

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

Note that the line for news/inn-stable is an example for a one-shot shell variable assignment to CONFIGURE_ARGS. The port Makefile will use this as an initial value and augment some other essential args. The difference to specifying a make variable on the command line with

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

is that the latter will override instead of augment. It depends on the particular port which method you want.

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 stage_2.sh mysteriously skips a port or stops processing, this is likely the reason.

Here is stage_2.sh. It creates a log file named LOGDIR/category+port for each port it actually installs. If you do not have stage_2.sh on a shared partition make sure you copy it to the new system before you boot it.

    #!/bin/sh
    #
    # stage_2.sh - FreeBSD From Scratch, Stage 2: Ports Installation.
    #              Usage: ./stage_2.sh
    #
    # $Id: stage_2.sh,v 1.2 2002/12/08 12:28:21 toor Exp toor $
    
    DBDIR=/var/db/pkg
    PORTS=/usr/ports
    LOGDIR=/home/root/setup/ports.log; mkdir -p ${LOGDIR}
    
    # Set some variables used by more than one port.
    PAPERSIZE=a4;    export PAPERSIZE
    USA_RESIDENT=NO; export USA_RESIDENT
    
    MYNAME=$(basename $0)
    usage () {
        exec >&2
        echo "usage: ${MYNAME} [-hn]"
        echo ""
        echo "  Options:"
        echo "  -h    Print this help text."
        echo "  -n    Dryrun: just show what would be done."
        echo ""
        exit 1
    }
    
    args=`getopt hn $*`
    if test $? != 0; then
        usage
    fi
    set -- $args
    DRYRUN=
    for i; do
        case "$i" in
        -n) DRYRUN=yes;;
        --) break;;
        *) usage;;
        esac
    done
    
    cat << EOF |
    lang perl5
    security sudo
    x11-servers XFree86-4-Server
    x11 wrapper
    x11 XFree86-4-libraries
    x11 XFree86-4-clients
    x11-fonts XFree86-4-font75dpi
    x11-fonts XFree86-4-font100dpi
    x11-fonts XFree86-4-fontScalable
    x11-fonts urwfonts
    x11-fonts webfonts
    x11-toolkits open-motif
    x11 rxvt
    x11-wm ctwm
    security openssh-askpass
    astro xplanet
    astro setiathome make BATCH=yes install
    astro xephem
    editors vim
    print ghostscript-gnu make A4=yes BATCH=yes install
    print a2ps-a4
    print psutils-a4
    print gv
    print acroread5
    print transfig
    archivers zip
    archivers unzip
    java linux-sun-jdk13 yes | make install
    java jdk13
    www apache2
    www weblint
    www amaya
    www mozilla make WITHOUT_MAILNEWS=yes WITHOUT_CHATZILLA=yes install
    www netscape48-navigator
    www checkbot
    www privoxy
    graphics xfig
    graphics xv
    graphics fxtv
    lang expect
    news tin
    net freebsd-uucp
    net cvsup-without-gui
    net pathchar make NO_CHECKSUM=yes install
    ftp wget
    ftp ncftp3
    textproc ispell
    german ispell-neu
    german ispell-alt
    textproc docproj make JADETEX=yes HAVE_MOTIF=yes install
    sysutils samefile
    sysutils pstree
    sysutils mkisofs
    sysutils cdrtools
    sysutils grub
    devel ddd
    devel ctags
    devel ElectricFence
    mail procmail make BATCH=yes install
    mail metamail
    mail mutt
    mail spamoracle
    emulators mtools
    sysutils portupgrade
    news inn-stable CONFIGURE_ARGS="--enable-uucp-rnews --enable-setgid-inews" make install
    misc figlet-fonts
    textproc gmat
    EOF
    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 -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"
        if test -n "${DRYRUN}"; then
            echo "${CMD}"
            continue
        fi
        date "++++++++++ %v %T +++++++++" > ${LOG}
        echo "CMD: ${CMD}" >> ${LOG}
        (
            make clean
            eval "${CMD}"
            # make clean # Uncomment if diskspace is tight under ${PORTS}.
        ) 2>&1 | tee -a ${LOG}
    done
    
    # Install StarOffice as a package, created on old the system with
    # "make package", because the port uses an interactive X11 install.
    #pkg_add ${PORTS}/editors/staroffice52/staroffice-*.tbz
    
    # EOF $RCSfile: stage_2.sh,v $    vim: tabstop=4:

Download stage_2.sh.


6 Stage Three

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 stage_2.sh 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.

I have chosen to implement stage three as a Makefile because this allows easy selection of what you want to configure simply by running:

    # make -f stage_3.mk target

As with stage_2.sh make sure you have stage_3.mk available after booting the new system, either by putting it on a shared partition or copying it somewhere on the new system.

    # stage_3.mk - FreeBSD From Scratch, Stage 3: Ports Post-Configuration.
    #              Usage: make -f stage_3.mk all     (config everything)
    #                or   make -f stage_3.mk target  (to just config target)
    #
    # 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.1 2002/12/08 11:32:39 toor Exp toor $
    
    .POSIX:
    
    message:
        @echo "Please use one of the following targets:"
        @echo "config_apache"
        @echo "config_inn"
        @echo "config_javaplugin"
        @echo "config_privoxy"
        @echo "config_setiathome"
        @echo "config_sgml"
        @echo "config_sudo"
        @echo "config_TeX"
        @echo "config_tin"
        @echo "config_uucp"
        @echo "all -- all of the above"
    
    all: config_apache \
        config_inn \
        config_javaplugin \
        config_privoxy \
        config_setiathome \
        config_sgml \
        config_sudo \
        config_TeX \
        config_tin \
        config_uucp
    
    config_apache:
        # 1. Modify httpd.conf.
        perl -pi \
        -e 's/#ServerName new.host.name/ServerName hal9000.s.shuttle.de/;' \
        -e 's/^ServerAdmin.*/ServerAdmin schweikh\@schweikhardt.net/;' \
        -e 's,/usr/local/www/cgi-bin/,/home/opt/www/cgi-bin/,;' \
          /usr/local/etc/apache2/httpd.conf
        # 2. Restore symlinks to web pages.
        cd /usr/local/www/data; \
        ln -fs /home/schweikh/prj/homepage schweikhardt.net; \
        ln -fs /home/opt/www/test .
    
    config_inn:
        pw usermod -n news -d /usr/local/news -s /bin/sh
        # Give the news system its initial configuration.
        cd /home/root/setup; \
        install -C -o news -g news -m 664 active newsgroups /usr/local/news/db
        # The innd.sh that comes with the port is broken, it
        # checks for history.pag which does not exist.
        cd /home/root/setup; \
        install -C -o root -g wheel -m 555 innd.sh /usr/local/etc/rc.d
        # 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/^(organization:\s*).*/$$1 An Open Pod Bay Door/;'    \
        -e 's/^(pathhost:\s*).*/$$1 hal9000.schweikhardt.net/;'    \
        -e 's/^(server:).*/$$1 localhost/;'                        \
        -e 's/^(domain:).*/$$1 schweikhardt.net/;'                 \
        -e 's/^(fromhost:).*/$$1 schweikhardt.net/;'               \
        -e 's,^(moderatormailer:).*,$$1 \%s\@moderators.isc.org,;' \
        -e 's,/usr/local/news/spool,/share/news/spool,;'           \
        /usr/local/news/etc/inn.conf
    
    config_javaplugin:
        cd /usr/local/lib/netscape-linux/plugins; \
        if ! test -h javaplugin.so; then \
            ln -s ../../../linux-sun-jdk1.3.1/jre/plugin/i386/ns4/javaplugin.so; \
        fi; \
        ls -l javaplugin.so
    
    config_privoxy:
        install -C -o root -g wheel -m 644 config /usr/local/etc/privoxy
    
    config_setiathome:
        perl -pi \
        -e 's,^.*seti_wrkdir.*#,seti_wrkdir=/home/nobody/setiathome #,;' \
        /usr/local/etc/rc.setiathome.conf
    
    config_sgml:
        cp -p /usr/local/share/gmat/sgml/ISO_8879-1986/entities/* \
              /usr/local/share/sgml/docbook/4.1
    
    config_sudo:
        if ! grep -q schweikh /usr/local/etc/sudoers; then \
            echo 'schweikh ALL = (ALL) NOPASSWD: ALL' >> /usr/local/etc/sudoers; \
        fi
    
    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*=).*/$$1 60000/;'     \
        -e 's/^(pool_size\s*=).*/$$1 1000000/;'    \
        -e 's/^(max_strings\s*=).*/$$1 70000/;'    \
        -e 's/^(save_size\s*=).*/$$1 10000/;'      \
        /usr/local/share/texmf/web2c/texmf.cnf
    
    config_tin:
        # Point tin to our files.
        printf "%s\n%s\n%s\n"                              \
            "activefile=/usr/local/news/db/active"         \
            "newsgroupsfile=/usr/local/news/db/newsgroups" \
            "spooldir=/share/news/spool/articles"          \
        >/usr/local/etc/tin.defaults
    
    config_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%s\n%s\n%s\n%s\n%s\n%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
    
    # EOF $RCSfile: stage_3.mk,v $    vim: tabstop=4:

Download stage_3.mk.


7 Limitations

The automated installation of a port may prove difficult if it is interactive and does not support make BATCH=YES install. For a few ports the interaction is nothing more than typing yes 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 make install; this is how we deal with java/linux-sun-jdk13 in stage_2.sh).

This strategy for example does not work for editors/staroffice52, 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

    # 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'

and during stage two I simply use:

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

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 /etc/group, or /etc/passwd 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 mergemaster to deal with changes where you effectively want to merge your local config with potentially new items. Unfortunately, mergemaster 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.

I have used FreeBSD From Scratch several times to update a 5-CURRENT to 5-CURRENT, i.e. I have never tried to install a 5-CURRENT from a 4-STABLE 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 FreeBSD From Scratch for upgrades within the realm of 4-STABLE should work painlessly (although I have not yet tried it.) Users of 4-STABLE may want to consider the following areas:


This, and other documents, can be downloaded from ftp://ftp.FreeBSD.org/pub/FreeBSD/doc/.

For questions about FreeBSD, read the documentation before contacting <questions@FreeBSD.org>.
For questions about this documentation, e-mail <doc@FreeBSD.org>.