3 # Copyright (c) 2016 Will Andrews
6 # Redistribution and use in source and binary forms, with or without
7 # modification, are permitted provided that the following conditions
9 # 1. Redistributions of source code must retain the above copyright
10 # notice, this list of conditions and the following disclaimer
11 # in this position and unchanged.
12 # 2. Redistributions in binary form must reproduce the above copyright
13 # notice, this list of conditions and the following disclaimer in the
14 # documentation and/or other materials provided with the distribution.
16 # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17 # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18 # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19 # IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
21 # NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25 # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 # Install a boot environment using the current FreeBSD source tree.
31 # Requires a fully built world & kernel.
33 # Non-base tools required: beadm, pkg
35 # In a sandbox for the new boot environment, this script also runs etcupdate
36 # and pkg upgrade automatically in the sandbox. Upon successful completion,
37 # the system will be ready to boot into the new boot environment. Upon
38 # failure, the target boot environment will be destroyed. In all cases, the
39 # running system is left untouched.
42 # beinstall [optional world/kernel flags e.g. KERNCONF]
44 ## User modifiable variables - set these in the environment if desired.
45 # If not empty, 'pkg upgrade' will be skipped.
46 NO_PKG_UPGRADE="${NO_PKG_UPGRADE:-""}"
47 # Config updater - 'etcupdate' and 'mergemaster' are supported. Set to an
48 # empty string to skip.
49 CONFIG_UPDATER="${CONFIG_UPDATER:-"etcupdate"}"
50 # Flags for etcupdate if used.
51 ETCUPDATE_FLAGS="${ETCUPDATE_FLAGS:-"-F"}"
52 # Flags for mergemaster if used.
53 MERGEMASTER_FLAGS="${MERGEMASTER_FLAGS:-"-iFU"}"
56 ########################################################################
58 ETCUPDATE_CMD="etcupdate"
59 MERGEMASTER_CMD="mergemaster"
63 [ -z "${cleanup_commands}" ] && return
64 echo "Cleaning up ..."
65 for command in ${cleanup_commands}; do
77 chflags -R noschg ${BE_MNTPT}
82 mount | grep " on ${BE_MNTPT}" | awk '{print $3}' | sort -r | xargs -t umount -f
86 # Before destroying, unmount any child filesystems that may have
87 # been mounted under the boot environment. Sort them in reverse
88 # order so children are unmounted first.
90 # Finally, clean up any directories that were created by the
91 # operation, via cleanup_be_dirs().
92 if [ -n "${created_be_dirs}" ]; then
93 chroot ${BE_MNTPT} /bin/rm -rf ${created_be_dirs}
95 beadm destroy -F ${BENAME}
99 echo "${BE_MNTPT}: Inspecting dirs $*"
104 [ -e "${BE_MNTPT}${curdir}" ] && break
106 curdir=$(dirname ${curdir})
108 [ "$curdir" = "$dir" ] && continue
110 # Add the top-most nonexistent directory to the list, then
111 # mkdir -p the innermost directory specified by the argument.
112 # This way the least number of directories are rm'd directly.
113 created_be_dirs="${topdir} ${created_be_dirs}"
114 echo "${BE_MNTPT}: Created ${dir}"
115 mkdir -p ${BE_MNTPT}${dir} || return $?
120 update_mergemaster_pre() {
121 mergemaster -p -m ${srcdir} -D ${BE_MNTPT} -t ${BE_MM_ROOT} ${MERGEMASTER_FLAGS}
124 update_mergemaster() {
126 mergemaster -m ${srcdir} -t ${BE_MM_ROOT} ${MERGEMASTER_FLAGS}
129 update_etcupdate_pre() {
130 etcupdate -p -s ${srcdir} -D ${BE_MNTPT} ${ETCUPDATE_FLAGS} || return $?
131 etcupdate resolve -D ${BE_MNTPT} || return $?
136 etcupdate -s ${srcdir} ${ETCUPDATE_FLAGS} || return $?
137 chroot ${BE_MNTPT} etcupdate resolve
141 # Special command-line subcommand that can be used to do a full cleanup
142 # after a manual post-mortem has been completed.
144 [ -n "${BENAME}" ] || errx "Must specify BENAME"
145 [ -n "${BE_MNTPT}" ] || errx "Must specify BE_MNTPT"
146 echo "Performing post-mortem on BE ${BENAME} at ${BE_MNTPT} ..."
149 echo "Post-mortem cleanup complete."
150 echo "To destroy the BE (recommended), run: beadm destroy ${BENAME}"
151 echo "To instead continue with the BE, run: beadm activate ${BENAME}"
154 if [ -n "$BEINSTALL_CMD" ]; then
161 trap 'errx "Interrupt caught"' HUP INT TERM
163 [ "$(whoami)" != "root" ] && errx "Must be run as root"
165 [ ! -f "Makefile.inc1" ] && errx "Must be in FreeBSD source tree"
167 objdir=$(make -V .OBJDIR 2>/dev/null)
168 [ ! -d "${objdir}" ] && errx "Must have built FreeBSD from source tree"
170 # May be a worktree, in which case .git is a file, not a directory.
171 if [ -e .git ] ; then
172 commit_time=$(git show --format='%ct' 2>/dev/null | head -1)
173 [ $? -ne 0 ] && errx "Can't lookup git commit timestamp"
174 commit_ts=$(date -r ${commit_time} '+%Y%m%d.%H%M%S')
175 elif [ -d .svn ] ; then
176 if [ -e /usr/bin/svnlite ]; then
178 elif [ -e /usr/local/bin/svn ]; then
179 svn=/usr/local/bin/svn
181 errx "Unable to find subversion"
183 commit_ts="$( "$svn" info --show-item last-changed-date | sed -e 's/\..*//' -e 's/T/./' -e 's/-//g' -e s'/://g' )"
184 [ $? -ne 0 ] && errx "Can't lookup Subversion commit timestamp"
186 errx "Unable to determine source control type"
189 commit_ver=$(${objdir}/bin/freebsd-version/freebsd-version -u 2>/dev/null)
190 [ -z "${commit_ver}" ] && errx "Unable to determine FreeBSD version"
192 BENAME="${commit_ver}-${commit_ts}"
194 BE_TMP=$(mktemp -d /tmp/beinstall.XXXXXX)
195 [ $? -ne 0 -o ! -d ${BE_TMP} ] && errx "Unable to create mountpoint"
196 [ -z "$NO_CLEANUP_BE" ] && cleanup_commands="rmdir_be ${cleanup_commands}"
197 BE_MNTPT=${BE_TMP}/mnt
198 BE_MM_ROOT=${BE_TMP}/mergemaster # mergemaster will create
201 beadm create ${BENAME} >/dev/null || errx "Unable to create BE ${BENAME}"
202 [ -z "$NO_CLEANUP_BE" ] && cleanup_commands="cleanup_be ${cleanup_commands}"
204 beadm mount ${BENAME} ${BE_TMP}/mnt || errx "Unable to mount BE ${BENAME}."
206 echo "Mounted ${BENAME} to ${BE_MNTPT}, performing install/update ..."
207 make "$@" DESTDIR=${BE_MNTPT} installkernel || errx "Installkernel failed!"
208 if [ -n "${CONFIG_UPDATER}" ]; then
209 "update_${CONFIG_UPDATER}_pre"
210 [ $? -ne 0 ] && errx "${CONFIG_UPDATER} (pre-world) failed!"
213 # Mount the source and object tree within the BE in order to account for any
214 # changes applied by the pre-installworld updater. Cleanup any directories
215 # created if they didn't exist previously.
216 create_be_dirs "${srcdir}" "${objdir}" || errx "Unable to create BE dirs"
217 mount -t nullfs "${srcdir}" "${BE_MNTPT}${srcdir}" || errx "Unable to mount src"
218 mount -t nullfs "${objdir}" "${BE_MNTPT}${objdir}" || errx "Unable to mount obj"
220 chroot ${BE_MNTPT} make "$@" -C ${srcdir} installworld || \
221 errx "Installworld failed!"
223 if [ -n "${CONFIG_UPDATER}" ]; then
224 "update_${CONFIG_UPDATER}"
225 [ $? -ne 0 ] && errx "${CONFIG_UPDATER} (post-world) failed!"
228 BE_PKG="chroot ${BE_MNTPT} env ASSUME_ALWAYS_YES=true pkg"
229 if [ -z "${NO_PKG_UPGRADE}" ]; then
230 ${BE_PKG} update || errx "Unable to update pkg"
231 ${BE_PKG} upgrade || errx "Unable to upgrade pkgs"
234 if [ -n "$NO_CLEANUP_BE" ]; then
235 echo "Boot Environment ${BENAME} may be examined in ${BE_MNTPT}."
236 echo "Afterwards, run this to cleanup:"
237 echo " env BENAME=${BENAME} BE_MNTPT=${BE_MNTPT} BEINSTALL_CMD=postmortem $0"
241 unmount_be || errx "Unable to unmount BE"
242 rmdir_be || errx "Unable to cleanup BE"
243 beadm activate ${BENAME} || errx "Unable to activate BE"
247 echo "Boot environment ${BENAME} setup complete; reboot to use it."