xref: /freebsd/tools/build/beinstall.sh (revision 7fde3e62231ac397a779e4fef729908c5ef6f53a)
1#!/bin/sh
2#
3# Copyright (c) 2016 Will Andrews
4# All rights reserved.
5#
6# Redistribution and use in source and binary forms, with or without
7# modification, are permitted provided that the following conditions
8# are met:
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.
15#
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.
26#
27#
28##
29# Install a boot environment using the current FreeBSD source tree.
30# Requires a fully built world & kernel.
31#
32# Non-base tools required: pkg
33#
34# In a sandbox for the new boot environment, this script also runs etcupdate
35# and pkg upgrade automatically in the sandbox.  Upon successful completion,
36# the system will be ready to boot into the new boot environment.  Upon
37# failure, the target boot environment will be destroyed.  In all cases, the
38# running system is left untouched.
39#
40## Usage:
41# beinstall [optional world/kernel flags e.g. KERNCONF]
42#
43## User modifiable variables - set these in the environment if desired.
44# Utility to manage ZFS boot environments.
45BE_UTILITY="${BE_UTILITY:-"bectl"}"
46# If not empty, 'pkg upgrade' will be skipped.
47NO_PKG_UPGRADE="${NO_PKG_UPGRADE:-""}"
48# Config updater - 'etcupdate' is supported.  Set to an empty string to skip.
49CONFIG_UPDATER="${CONFIG_UPDATER:-"etcupdate"}"
50# Flags for etcupdate if used.
51ETCUPDATE_FLAGS="${ETCUPDATE_FLAGS:-"-BF"}"
52
53
54########################################################################
55## Functions
56cleanup() {
57	[ -z "${cleanup_commands}" ] && return
58	echo "Cleaning up ..."
59	for command in ${cleanup_commands}; do
60		${command}
61	done
62}
63
64errx() {
65	cleanup
66	echo "error: $@"
67	exit 1
68}
69
70rmdir_be() {
71	chflags -R noschg ${BE_MNTPT}
72	rm -rf ${BE_MNTPT}
73}
74
75unmount_be() {
76	mount | grep " on ${BE_MNTPT}" | awk '{print $3}' | sort -r | xargs -t umount -f
77}
78
79copy_pkgs() {
80	# Before cleaning up, try to save progress in pkg(8) updates, to
81	# speed up future updates.  This is only called on the error path;
82	# no need to run on success.
83	echo "Rsyncing back newly saved packages..."
84	rsync -av --progress ${BE_MNTPT}/var/cache/pkg/. /var/cache/pkg/.
85}
86
87cleanup_be() {
88	# Before destroying, unmount any child filesystems that may have
89	# been mounted under the boot environment.  Sort them in reverse
90	# order so children are unmounted first.
91	unmount_be
92	# Finally, clean up any directories that were created by the
93	# operation, via cleanup_be_dirs().
94	if [ -n "${created_be_dirs}" ]; then
95		chroot ${BE_MNTPT} /bin/rm -rf ${created_be_dirs}
96	fi
97	${BE_UTILITY} destroy -F ${BENAME}
98}
99
100create_be_dirs() {
101	echo "${BE_MNTPT}: Inspecting dirs $*"
102	for dir in $*; do
103		curdir="$dir"
104		topdir="$dir"
105		while :; do
106			[ -e "${BE_MNTPT}${curdir}" ] && break
107			topdir=$curdir
108			curdir=$(dirname ${curdir})
109		done
110		[ "$curdir" = "$dir" ] && continue
111
112		# Add the top-most nonexistent directory to the list, then
113		# mkdir -p the innermost directory specified by the argument.
114		# This way the least number of directories are rm'd directly.
115		created_be_dirs="${topdir} ${created_be_dirs}"
116		echo "${BE_MNTPT}: Created ${dir}"
117		mkdir -p ${BE_MNTPT}${dir} || return $?
118	done
119	return 0
120}
121
122update_etcupdate_pre() {
123	${ETCUPDATE_CMD} -p -s ${srcdir} -D ${BE_MNTPT} ${ETCUPDATE_FLAGS} || return $?
124	${ETCUPDATE_CMD} resolve -D ${BE_MNTPT} || return $?
125}
126
127update_etcupdate() {
128	${ETCUPDATE_CMD} -s ${srcdir} ${ETCUPDATE_FLAGS} || return $?
129	${ETCUPDATE_CMD} resolve -D ${BE_MNTPT}
130}
131
132
133# Special command-line subcommand that can be used to do a full cleanup
134# after a manual post-mortem has been completed.
135postmortem() {
136	[ -n "${BENAME}" ] || errx "Must specify BENAME"
137	[ -n "${BE_MNTPT}" ] || errx "Must specify BE_MNTPT"
138	echo "Performing post-mortem on BE ${BENAME} at ${BE_MNTPT} ..."
139	unmount_be
140	rmdir_be
141	echo "Post-mortem cleanup complete."
142	echo "To destroy the BE (recommended), run: ${BE_UTILITY} destroy ${BENAME}"
143	echo "To instead continue with the BE, run: ${BE_UTILITY} activate ${BENAME}"
144}
145
146if [ -n "$BEINSTALL_CMD" ]; then
147	${BEINSTALL_CMD} $*
148	exit $?
149fi
150
151if [ "$(basename -- "${BE_UTILITY}")" = "bectl" ]; then
152	${BE_UTILITY} check || errx "${BE_UTILITY} sanity check failed"
153fi
154
155cleanup_commands=""
156trap 'errx "Interrupt caught"' HUP INT TERM
157
158[ "$(whoami)" != "root" ] && errx "Must be run as root"
159
160[ ! -f "Makefile.inc1" ] && errx "Must be in FreeBSD source tree"
161srcdir=$(pwd)
162objdir=$(make -V .OBJDIR 2>/dev/null)
163[ ! -d "${objdir}" ] && errx "Must have built FreeBSD from source tree"
164
165## Constants
166ETCUPDATE_CMD="${srcdir}/usr.sbin/etcupdate/etcupdate.sh"
167
168# May be a worktree, in which case .git is a file, not a directory.
169if [ -e .git ] ; then
170    commit_time=$(git show -s --format='%ct' 2>/dev/null)
171    [ $? -ne 0 ] && errx "Can't lookup git commit timestamp"
172    commit_ts=$(date -r ${commit_time} '+%Y%m%d.%H%M%S')
173elif [ -d .svn ] ; then
174      if [ -e /usr/bin/svnlite ]; then
175        svn=/usr/bin/svnlite
176      elif [ -e /usr/local/bin/svn ]; then
177        svn=/usr/local/bin/svn
178      else
179        errx "Unable to find subversion"
180      fi
181      commit_ts="$( "$svn" info --show-item last-changed-date | sed -e 's/\..*//' -e 's/T/./' -e 's/-//g' -e s'/://g' )"
182    [ $? -ne 0 ] && errx "Can't lookup Subversion commit timestamp"
183else
184    errx "Unable to determine source control type"
185fi
186
187commit_ver=$(${objdir}/bin/freebsd-version/freebsd-version -u 2>/dev/null)
188[ -z "${commit_ver}" ] && errx "Unable to determine FreeBSD version"
189
190BENAME="${commit_ver}-${commit_ts}"
191
192BE_TMP=$(mktemp -d /tmp/beinstall.XXXXXX)
193[ $? -ne 0 -o ! -d ${BE_TMP} ] && errx "Unable to create mountpoint"
194[ -z "$NO_CLEANUP_BE" ] && cleanup_commands="rmdir_be ${cleanup_commands}"
195BE_MNTPT=${BE_TMP}/mnt
196mkdir -p ${BE_MNTPT}
197
198${BE_UTILITY} create ${BENAME} >/dev/null || errx "Unable to create BE ${BENAME}"
199[ -z "$NO_CLEANUP_BE" ] && cleanup_commands="cleanup_be ${cleanup_commands}"
200
201${BE_UTILITY} mount ${BENAME} ${BE_TMP}/mnt || errx "Unable to mount BE ${BENAME}."
202
203echo "Mounted ${BENAME} to ${BE_MNTPT}, performing install/update ..."
204make "$@" DESTDIR=${BE_MNTPT} installkernel || errx "Installkernel failed!"
205if [ -n "${CONFIG_UPDATER}" ]; then
206	"update_${CONFIG_UPDATER}_pre"
207	[ $? -ne 0 ] && errx "${CONFIG_UPDATER} (pre-world) failed!"
208fi
209
210# Mount the source and object tree within the BE in order to account for any
211# changes applied by the pre-installworld updater.  Cleanup any directories
212# created if they didn't exist previously.
213create_be_dirs "${srcdir}" "${objdir}" || errx "Unable to create BE dirs"
214mount -t nullfs "${srcdir}" "${BE_MNTPT}${srcdir}" || errx "Unable to mount src"
215mount -t nullfs "${objdir}" "${BE_MNTPT}${objdir}" || errx "Unable to mount obj"
216mount -t devfs devfs "${BE_MNTPT}/dev" || errx "Unable to mount devfs"
217
218make "$@" DESTDIR=${BE_MNTPT} -DDB_FROM_SRC installworld || \
219	errx "Installworld failed!"
220
221if [ -n "${CONFIG_UPDATER}" ]; then
222	"update_${CONFIG_UPDATER}"
223	[ $? -ne 0 ] && errx "${CONFIG_UPDATER} (post-world) failed!"
224fi
225
226if which rsync >/dev/null 2>&1; then
227	cleanup_commands="copy_pkgs ${cleanup_commands}"
228fi
229
230BE_PKG="chroot ${BE_MNTPT} env ASSUME_ALWAYS_YES=true pkg"
231if [ -z "${NO_PKG_UPGRADE}" ]; then
232	${BE_PKG} update || errx "Unable to update pkg"
233	${BE_PKG} upgrade || errx "Unable to upgrade pkgs"
234fi
235
236if [ -n "$NO_CLEANUP_BE" ]; then
237	echo "Boot Environment ${BENAME} may be examined in ${BE_MNTPT}."
238	echo "Afterwards, run this to cleanup:"
239	echo "  env BENAME=${BENAME} BE_MNTPT=${BE_MNTPT} BEINSTALL_CMD=postmortem $0"
240	exit 0
241fi
242
243unmount_be || errx "Unable to unmount BE"
244rmdir_be || errx "Unable to cleanup BE"
245${BE_UTILITY} activate ${BENAME} || errx "Unable to activate BE"
246echo
247${BE_UTILITY} list
248echo
249echo "Boot environment ${BENAME} setup complete; reboot to use it."
250