#!/bin/sh
#
# HOWTO/script to create a ZFS pool mounted as an almost bootable "root"
# partition.
#
# Adapted by Yarema <yds@CoolRat.org> from
#	http://People.FreeBSD.org/~rse/mirror/
#	http://Wiki.FreeBSD.org/ZFSOnRoot
#	http://Wiki.FreeBSD.org/ZFS
#	http://Wiki.FreeBSD.org/ZFSTuningGuide
#
# This script will create a 512M gmirror(8) boot partition.
# A swap 'b' partition on each drive if more than one is used.
# And a 'd' partition for the zpool.
# When using more than one drive the zpool can be 'mirror'.
# When using three or more drives the zpool type can be 'raidz'.
#
# The gmirror(8) boot partition will be mounted to the /strap mountpoint
# on the zpool. /strap/boot will be nullfs mounted to the /boot mountpoint.
# /strap/rescue will be nullfs mounted to the /rescue mountpoint.
# /strap/bin and /strap/sbin will be soft-linked to /rescue.
#
# Normally even single user boot will still mount the zfs <pool> as root.
# Putting the statically linked /rescue utilities on the boot/'a'
# partition seems like a good idea in the unlikely case that the zpool
# is unmountable.  Albeit zpool(1M) and zfs(1M) are not (yet?) built or
# installed in /rescue.
#
# WARNING: Due to the way ZFS is currently implemented there is a known
# issue (in FreeBSD and OpenSolaris) where ZFS needs to allocate memory
# to send an I/O request.  When there is no memory, ZFS cannot allocate
# any and thus cannot swap a process out and free it.  Such low memory
# conditions can cause the machine to effectively freeze -- unable to
# swap out to disk right when it is most crucial to do so.
# http://Lists.FreeBSD.org/pipermail/freebsd-current/2007-September/076831.html

#===#===#===#===#===#===#===#===#===#===#===#===#===#===#===#===#===#===#===#
program=`basename $0`

if [ $# -lt 1 ]; then
  echo "Usage: ${program} [mirror|raidz] <vdev> ..."
  exit 1
fi

case "$1" in
mirror|raidz|raidz[12])
  type="$1"; shift
  if [ "${type}" = 'mirror' -a $# -lt 2 ]; then
    echo "Usage:	${program} ${type} <vdev> <vdev> ..."
    exit 1
  elif [ "${type}" = 'raidz2' -a $# -lt 4 ]; then
    echo "Usage:	${program} ${type} <vdev> <vdev> <vdev> <vdev> ..."
    exit 1
  elif [ "${type}" != 'mirror' -a $# -lt 3 ]; then
    echo "Usage:	${program} ${type} <vdev> <vdev> <vdev> ..."
    exit 1
  fi
  ;;
esac
vdev1="$1"
vdevs="$@"

#===#===#===#===#===#===#===#===#===#===#===#===#===#===#===#===#===#===#===#
if [ -f "$0.rc" ];then
  . "$0.rc"
fi

# The recommended <pool> name is simply the letter 'z' which makes output
# form mount(8), zfs(8M) and zpool(8M) easy to read for us humans.
: ${POOL:='z'}
DESTDIR="/${POOL}"

# file describing the ZFS datasets to create
: ${DATASETS:="$0.fs"}

# FreeBSD release directory to install from
: ${RELEASE:="7.1-RELEASE"}

# Server hostname to net install from
: ${SERVER:="server"}

# Maximum installed Megabytes of RAM the system is likely to have.
# Used for calculating the size of the swap partision(s).
if [ -z "${MAXRAM}" ]; then
  MAXRAM=$(expr $(expr $(sysctl -n hw.physmem) / 1024 / 1024 / 1024 + 1) \* 1024)
fi

# Set swapsize to double the ${MAXRAM}
swapsize=$(expr ${MAXRAM} \* 2)

#===#===#===#===#===#===#===#===#===#===#===#===#===#===#===#===#===#===#===#
ask() {
  local question default answer
  question=$1
  default=$2
  read -p "${question} [${default}]? " answer
  if [ -z "${answer}" ]; then
    answer=${default}
  fi
  echo ${answer}
}

yesno() {
  local question default answer
  question=$1
  default=$2
  while :; do
    answer=$(ask "${question}" "${default}")
    case "${answer}" in
    [Yy]*)	return 0;;
    [Nn]*)	return 1;;
    esac
    echo "Please answer yes or no."
  done
}

#===#===#===#===#===#===#===#===#===#===#===#===#===#===#===#===#===#===#===#
if yesno "Create one slice covering the entire disk(s)" n; then
  { # Wipe out any prior zpool, gmirror, fdisk and bsdlabel meta data.
    umount ${DESTDIR}/strap/boot
    umount ${DESTDIR}/strap/rescue
    umount ${DESTDIR}/strap
    zpool destroy ${POOL}
    rm -f /boot/zfs/zpool.cache
    eval "gmirror remove -v boot `echo ${vdevs} | sed -E 's/<span class="createlink"><a href="http://yds.coolrat.org/ikiwiki.cgi?page=%3A__60__%3A&amp;from=zfsboot%2Fzfsboot.sh&amp;do=create" rel="nofollow">?</a>:<:</span><span class="createlink"><a href="http://yds.coolrat.org/ikiwiki.cgi?page=%3Aalnum%3A&amp;from=zfsboot%2Fzfsboot.sh&amp;do=create" rel="nofollow">?</a>:alnum:</span>+<span class="createlink"><a href="http://yds.coolrat.org/ikiwiki.cgi?page=%3A__62__%3A&amp;from=zfsboot%2Fzfsboot.sh&amp;do=create" rel="nofollow">?</a>:>:</span>/&s1a/g'`"
    eval "gmirror remove -v swap `echo ${vdevs} | sed -E 's/<span class="createlink"><a href="http://yds.coolrat.org/ikiwiki.cgi?page=%3A__60__%3A&amp;from=zfsboot%2Fzfsboot.sh&amp;do=create" rel="nofollow">?</a>:<:</span><span class="createlink"><a href="http://yds.coolrat.org/ikiwiki.cgi?page=%3Aalnum%3A&amp;from=zfsboot%2Fzfsboot.sh&amp;do=create" rel="nofollow">?</a>:alnum:</span>+<span class="createlink"><a href="http://yds.coolrat.org/ikiwiki.cgi?page=%3A__62__%3A&amp;from=zfsboot%2Fzfsboot.sh&amp;do=create" rel="nofollow">?</a>:>:</span>/&s1b/g'`"
  } > /dev/null 2>&1
  for d in ${vdevs}; do
    dd if=/dev/zero of=/dev/${d} bs=32m count=17
    fdisk -BIv /dev/${d}
  done
fi

if yesno "Initialize the BSD label" y; then
  for d in ${vdevs}; do
    bsdlabel -wB /dev/${d}s1
  done
fi

# Create a bsdlabel where the 'a' bootable partition is 512M plus one
# extra sector for gmirror metadata.  The default /boot directory with a
# FreeBSD 7.0 GENERIC kernel and all the modules takes up about 178MB.
# 256MB can be a little tight if you need to keep a backup kernel or two.
# 512MB is still a good size for the boot partition.
#
# Create a large enough 'b' swap partition to be used for a swap backed
# /tmp mdmfs, /usr/obj mdmfs, and/or any other partitions you do not care
# to keep between reboots which might benefit from the extra speed of mfs.
# According to zfs(1M), using a ZFS volume as a dump device is not supported.
# Setting 'dumpdev' to the swap partition requires that it be at least as
# big as the installed RAM. When setting up a single drive, the swapsize
# will default to double the maximum installed RAM the system is likely to
# have plus what's left from rounding the 'd' partition down to the nearest
# Gigabyte. With more than one drive swap will be striped across all drives
# and only needs to be as large on each drive as the installed RAM so that
# a coredump can fit.
#
# Allocate the 'd' partition to be used by the "root" ZFS pool rounded down to
# the nearest Gigabyte with the remainder going to the 'b' swap partition.
bsdlabel /dev/${vdev1}s1 | awk -v s=${swapsize} '
/^ +c: / {
  a = 512 * 1024 * 2 + 1
  b = s * 1024 * 2
  c = $2
  d = c - 16 - a - b
}
END {
  print "8 partitions:"
  print "#        size   offset    fstype"
  printf("  a:  %d       16    4.2BSD\n", a)
  print  "  b:        *        *      swap"
  print  "  c:        *        0    unused"
  printf("  d:     %dG        *    unused\n", d / 2 / 1024 / 1024 )
}
' - > /tmp/bsdlabel.txt
bsdlabel -R /dev/${vdev1}s1 /tmp/bsdlabel.txt
bsdlabel /dev/${vdev1}s1
while yesno "(Re)Edit the BSD label" n; do
  bsdlabel -e /dev/${vdev1}s1
  bsdlabel /dev/${vdev1}s1
done
bsdlabel /dev/${vdev1}s1 > /tmp/bsdlabel.txt
if yesno "Apply the same BSD label to all mirrored devices" y; then
  for d in ${vdevs}; do
    bsdlabel -R /dev/${d}s1 /tmp/bsdlabel.txt
  done
fi

# Create a gmirror of the 'a' and 'b' partitions across all the devices
eval "gmirror label -v -b load boot `echo ${vdevs} | sed -E 's/<span class="createlink"><a href="http://yds.coolrat.org/ikiwiki.cgi?page=%3A__60__%3A&amp;from=zfsboot%2Fzfsboot.sh&amp;do=create" rel="nofollow">?</a>:<:</span><span class="createlink"><a href="http://yds.coolrat.org/ikiwiki.cgi?page=%3Aalnum%3A&amp;from=zfsboot%2Fzfsboot.sh&amp;do=create" rel="nofollow">?</a>:alnum:</span>+<span class="createlink"><a href="http://yds.coolrat.org/ikiwiki.cgi?page=%3A__62__%3A&amp;from=zfsboot%2Fzfsboot.sh&amp;do=create" rel="nofollow">?</a>:>:</span>/&s1a/g'`"
eval "gmirror label -v -b load swap `echo ${vdevs} | sed -E 's/<span class="createlink"><a href="http://yds.coolrat.org/ikiwiki.cgi?page=%3A__60__%3A&amp;from=zfsboot%2Fzfsboot.sh&amp;do=create" rel="nofollow">?</a>:<:</span><span class="createlink"><a href="http://yds.coolrat.org/ikiwiki.cgi?page=%3Aalnum%3A&amp;from=zfsboot%2Fzfsboot.sh&amp;do=create" rel="nofollow">?</a>:alnum:</span>+<span class="createlink"><a href="http://yds.coolrat.org/ikiwiki.cgi?page=%3A__62__%3A&amp;from=zfsboot%2Fzfsboot.sh&amp;do=create" rel="nofollow">?</a>:>:</span>/&s1b/g'`"

#===#===#===#===#===#===#===#===#===#===#===#===#===#===#===#===#===#===#===#
if yesno "Create the ZFS pool on the 'd' partition(s)" y; then
  eval "zpool create -f ${POOL} ${type} `echo ${vdevs} | sed -E 's/<span class="createlink"><a href="http://yds.coolrat.org/ikiwiki.cgi?page=%3A__60__%3A&amp;from=zfsboot%2Fzfsboot.sh&amp;do=create" rel="nofollow">?</a>:<:</span><span class="createlink"><a href="http://yds.coolrat.org/ikiwiki.cgi?page=%3Aalnum%3A&amp;from=zfsboot%2Fzfsboot.sh&amp;do=create" rel="nofollow">?</a>:alnum:</span>+<span class="createlink"><a href="http://yds.coolrat.org/ikiwiki.cgi?page=%3A__62__%3A&amp;from=zfsboot%2Fzfsboot.sh&amp;do=create" rel="nofollow">?</a>:>:</span>/&s1d/g'`"
  zfs set atime=off ${POOL}
  while read filesystem options; do
    case "${filesystem}" in
    /*)
      command="zfs create"
      for option in ${options};do
	command="${command} -o ${option}"
      done
      eval `echo "${command} ${POOL}${filesystem}" | sed -E 's/-<span class="createlink"><a href="http://yds.coolrat.org/ikiwiki.cgi?page=%3A__60__%3A&from=zfsboot%2Fzfsboot.sh&do=create" rel="nofollow">?</a>:<:</span>o<span class="createlink"><a href="http://yds.coolrat.org/ikiwiki.cgi?page=%3A__62__%3A&from=zfsboot%2Fzfsboot.sh&do=create" rel="nofollow">?</a>:>:</span><span class="createlink"><a href="http://yds.coolrat.org/ikiwiki.cgi?page=%3Aspace%3A&from=zfsboot%2Fzfsboot.sh&do=create" rel="nofollow">?</a>:space:</span>+-<span class="createlink"><a href="http://yds.coolrat.org/ikiwiki.cgi?page=%3A__60__%3A&from=zfsboot%2Fzfsboot.sh&do=create" rel="nofollow">?</a>:<:</span>V<span class="createlink"><a href="http://yds.coolrat.org/ikiwiki.cgi?page=%3A__62__%3A&from=zfsboot%2Fzfsboot.sh&do=create" rel="nofollow">?</a>:>:</span><span class="createlink"><a href="http://yds.coolrat.org/ikiwiki.cgi?page=%3Aspace%3A&from=zfsboot%2Fzfsboot.sh&do=create" rel="nofollow">?</a>:space:</span>+-<span class="createlink"><a href="http://yds.coolrat.org/ikiwiki.cgi?page=%3A__60__%3A&from=zfsboot%2Fzfsboot.sh&do=create" rel="nofollow">?</a>:<:</span>o<span class="createlink"><a href="http://yds.coolrat.org/ikiwiki.cgi?page=%3A__62__%3A&from=zfsboot%2Fzfsboot.sh&do=create" rel="nofollow">?</a>:>:</span>/-V/g'`
      zfs get mountpoint,compression,exec,setuid ${POOL}${filesystem}
      # Only set mountpoint for top level filesysytem
      # Let the child filesystem(s) inherit the moutpoint
      if echo "${filesystem}" | egrep '^/[^/]+$' > /dev/null 2>&1; then
	# Exclude volumes since they do not have the moutpoint property
	if echo "${command}" | egrep -v '<span class="createlink"><a href="http://yds.coolrat.org/ikiwiki.cgi?page=%3Aspace%3A&from=zfsboot%2Fzfsboot.sh&do=create" rel="nofollow">?</a>:space:</span>+-V<span class="createlink"><a href="http://yds.coolrat.org/ikiwiki.cgi?page=%3Aspace%3A&from=zfsboot%2Fzfsboot.sh&do=create" rel="nofollow">?</a>:space:</span>+' > /dev/null 2>&1; then
	  mountpoints="${mountpoints} ${filesystem}"
	fi
      fi
      ;;
    *)
      continue
      ;;
    esac
  done < ${DATASETS}
fi

#===#===#===#===#===#===#===#===#===#===#===#===#===#===#===#===#===#===#===#
# Create bootstrap directory where the boot file system will be mounted
mkdir -p ${DESTDIR}/strap
newfs -U /dev/mirror/boot
mount /dev/mirror/boot ${DESTDIR}/strap
mkdir -p ${DESTDIR}/strap/dev ${DESTDIR}/strap/boot ${DESTDIR}/strap/rescue ${DESTDIR}/boot ${DESTDIR}/rescue
# mount_nullfs here is the same as "ln -s strap/boot ${DESTDIR}/boot"
# but more resiliant to install scripts unlinking files before untarring.
mount -t nullfs ${DESTDIR}/strap/boot ${DESTDIR}/boot
mount -t nullfs ${DESTDIR}/strap/rescue ${DESTDIR}/rescue
ln -s rescue ${DESTDIR}/strap/bin
ln -s rescue ${DESTDIR}/strap/sbin

#===#===#===#===#===#===#===#===#===#===#===#===#===#===#===#===#===#===#===#
dists="base games manpages info dict"
for i in ${dists}; do
  echo "  Extracting the ${i} distribution into ${DESTDIR}..."
  cat /dist/${RELEASE}/${i}/${i}.??	| tar --unlink -xpzf - -C ${DESTDIR}
done

echo "  Extracting the GENERIC kernel into ${DESTDIR}/boot/"
cat /dist/${RELEASE}/kernels/generic.??	| tar --unlink -xpzf - -C ${DESTDIR}/boot && ln -f ${DESTDIR}/boot/GENERIC/* ${DESTDIR}/boot/kernel/

if yesno "Install /usr/src ?" n; then
  dists="base bin cddl contrib crypto etc games gnu include krb5 lib libexec release rescue sbin secure share sys tools ubin usbin"
  for i in ${dists}; do
    echo "  Extracting source component: ${i}"
    cat /dist/${RELEASE}/src/s${i}.??	| tar --unlink -xpzf - -C ${DESTDIR}/usr/src
  done
fi

#===#===#===#===#===#===#===#===#===#===#===#===#===#===#===#===#===#===#===#
if [ -f $0.local ];then
  . $0.local
fi

#===#===#===#===#===#===#===#===#===#===#===#===#===#===#===#===#===#===#===#
echo "dumpdev=\"${vdev1}s1b\"	# Set device for crash dumps" >> ${DESTDIR}/boot/loader.conf.local
echo -n "vfs.root.mountfrom=\"zfs:${POOL}\"" >> ${DESTDIR}/boot/loader.conf.local
echo "	# Specify root partition in a way the kernel understands" >> ${DESTDIR}/boot/loader.conf.local
echo "vfs.zfs.arc_max=\"192M\"" >> ${DESTDIR}/boot/loader.conf.local
echo "nullfs_load=\"YES\"		# Null filesystem" >> ${DESTDIR}/boot/loader.conf.local
echo "zfs_load=\"YES\"			# ZFS" >> ${DESTDIR}/boot/loader.conf.local
echo "geom_eli_load=\"YES\"		# Disk encryption driver (see geli(8))" >> ${DESTDIR}/boot/loader.conf.local
echo "geom_mirror_load=\"YES\"		# RAID1 disk driver (see gmirror(8))" >> ${DESTDIR}/boot/loader.conf.local

echo "# Device		Mountpoint	FStype	Options			Dump	Pass#" > ${DESTDIR}/etc/fstab
echo "/dev/mirror/swap.eli	none		swap	sw			0	0" >> ${DESTDIR}/etc/fstab
echo "${POOL}			/		zfs	rw,noatime		0	0" >> ${DESTDIR}/etc/fstab
echo "/dev/mirror/boot	/strap		ufs	rw,noatime,nosuid	1	1" >> ${DESTDIR}/etc/fstab
echo "/strap/boot		/boot		nullfs	rw			0	0" >> ${DESTDIR}/etc/fstab
echo "/strap/rescue		/rescue		nullfs	rw			0	0" >> ${DESTDIR}/etc/fstab
echo "tmpfs			/tmp		tmpfs	rw,nosuid		0	0" >> ${DESTDIR}/etc/fstab
echo "proc			/proc		procfs	rw			0	0" >> ${DESTDIR}/etc/fstab
echo "# Device		Mountpoint	FStype	Options			Dump	Pass#" >> ${DESTDIR}/etc/fstab
echo "/dev/acd0		/cdrom		cd9660	ro,noauto,nosuid	0	0" >> ${DESTDIR}/etc/fstab
echo "/dev/fd0		/media/fd	ufs	rw,noauto,nosuid	0	0" >> ${DESTDIR}/etc/fstab
echo "/dev/fd0		/media/floppy	msdosfs	rw,noauto,nosuid	0	0" >> ${DESTDIR}/etc/fstab

# Ensure that zfs_enable="YES" is set for /etc/rc.d/zfs
mkdir -p ${DESTDIR}/etc/rc.conf.d
echo '# ZFS support' > ${DESTDIR}/etc/rc.conf.d/zfs
echo 'zfs_enable="YES"	# Set to YES to automatically mount ZFS file systems' >> ${DESTDIR}/etc/rc.conf.d/zfs

#===#===#===#===#===#===#===#===#===#===#===#===#===#===#===#===#===#===#===#
umount ${DESTDIR}/strap/boot
umount ${DESTDIR}/strap/rescue
umount ${DESTDIR}/strap

#===#===#===#===#===#===#===#===#===#===#===#===#===#===#===#===#===#===#===#
zfs set readonly=on ${POOL}/var/empty
zfs unshare -a
zfs unmount -a -f
# At the end, set mount point to 'legacy' so ZFS won't try to mount it automatically
zfs set mountpoint=legacy ${POOL}
# After install, set the real mountpoint(s)
for mp in ${mountpoints}; do
  zfs set mountpoint=${mp} ${POOL}${mp}
done
zfs list
zfs unmount -a -f
zfs volfini

#===#===#===#===#===#===#===#===#===#===#===#===#===#===#===#===#===#===#===#
# Ensure /boot/zfs/zpool.cache is up to date on the boot filesystem
mount /dev/mirror/boot /media
mv /boot/zfs/zpool.cache /media/boot/zfs/
umount /dev/mirror/boot

#===#===#===#===#===#===#===#===#===#===#===#===#===#===#===#===#===#===#===#
# EOF