Skip to content

Instantly share code, notes, and snippets.

@rosstimson
Created October 4, 2013 14:43
Show Gist options
  • Save rosstimson/6827095 to your computer and use it in GitHub Desktop.
Save rosstimson/6827095 to your computer and use it in GitHub Desktop.
Makefile that automates installation of FreeBSD on ZFS root (compatible with BEADM boot environments).
# ---- Makefile for Vermaden's FreeBSD 9.1 root on ZFS manual install
# $Id: Makefile,v 1.18 2013/08/07 13:31:29 root Exp root $
#
# Copyright (c) 2013 Adriaan van Roosmalen <j65nko daemonforums.org>>
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#
# This BSD makefile merely automates Vermaden's "'ZFS madness / boot
# environments" mirrored disk setup.
#
# For a complete description of this particular ZFS only setup see below.
#
# The ZFS configuration described by Vermaden does not align the
# disk partitions for 'Avanced Format' 4K disks and also does not
# instruct ZFS to use 4K blocks for the write and read operations.
#
# The procedure implemented here does align the EFI/gpt partitions
# used on a 4K (8 x 512) # sector boundary. By using the 'gnop' trick
# the FreeBSD ZFS implementation is coached to use an 'ashift' value
# of 12 (2^12 = 4096)
# This value makes ZFS read and write on 4K boundaries
#
# Credits for the ZFS install procedure:
#
# Slawomir Wojciech Wojtczak (vermaden)
# http://forums.freebsd.org/showthread.php?t=31662
# http://www.daemonforums.org/showthread.php?t=7099
#
# Sources and credits for the 4K alignment issue and gnop hack:
#
# Warren Block (wblock@) for his numerous posts about partition alignment:
# http://forums.freebsd.org (Installation and Storage sections)
# http://www.wonkity.com/~wblock/docs/html/disksetup.html#_the_new_standard_gpt
# Ivan Voras for the gnop workaround:
# http://ivoras.sharanet.org/blog/tree/2011-01-01.freebsd-on-4k-sector-drives.html
# George Kontostanos (gkontos) for using gnop and a ZFS cache file:
# http://forums.freebsd.org/showthread.php?t=23544
# http://www.aisecure.net/2012/01/16/rootzfs/
#
# --- variables start
#DEBUG = echo
POOL = rpool
ROOT_SET = ${POOL}/ROOT
BOOT_SET = ${ROOT_SET}/default
# --- installation file sets
SETS = base.txz kernel.txz
DIR = /usr/freebsd-dist # On FreeBSD liveCD/DVD/memstick
.for X in ${SETS}
INSTALL_SETS += ${DIR}/${X}
.endfor
DISKTYPE = ada
DISKTYPE = md
.if ${DISKTYPE} == "md"
# --- memory disks see md(4) and mdconfig(8)
# size of disk needs to be at least 64M else you encounter this error:
# "cannot create 'pool': one or more devices is less than the minimum size (64M)"
MD_SIZE = 2g
SWAPSIZE = 256m
DISK_1 = /dev/md1
DISK_2 = /dev/md2
DISKS = ${DISK_1} ${DISK_2}
# --- gpt/EFI labels
LABEL_ZFS = mdisk_
LABEL_BOOT = mdboot
.else
# --- real spinning rust disks
DISK_1 = /dev/ada1
DISK_2 = /dev/ada2
DISKS = ${DISK_1} ${DISK_2}
SWAPSIZE = 4G
# --- gpt/EFI labels
LABEL_ZFS = SYSTEM_
LABEL_BOOT = BOOT_
.endif
TMP = /tmp
CACHEFILE = ${TMP}/zpool.cache
TEMPLATE_RC_CONF= ${TMP}/template_rc.conf
MOUNT = /mnt
RC_CONF = ${MOUNT}/etc/rc.conf
LOADER_CONF = ${MOUNT}/boot/loader.conf
FSTAB = ${MOUNT}/etc/fstab
# --- variables end
show:
@echo -------------------------
@echo Current variable settings
@echo -------------------------
@printf "%-20s : [%s]\n" DEBUG "${DEBUG}"
@printf "%-20s : [%s]\n" POOL "${POOL}"
@printf "%-20s : [%s]\n" ROOT_SET "${ROOT_SET}"
@printf "%-20s : [%s]\n" BOOT_SET "${BOOT_SET}"
@printf "%-20s : [%s]\n" SETS "${SETS}"
@printf "%-20s : [%s]\n" DIR "${DIR}"
@printf "%-20s : [%s]\n" INSTALL_SETS "${INSTALL_SETS}"
@printf "%-20s : [%s]\n" DISKTYPE "${DISKTYPE}"
@printf "%-20s : [%s]\n" MD_SIZE "${MD_SIZE}"
@printf "%-20s : [%s]\n" SWAPSIZE "${SWAPSIZE}"
@printf "%-20s : [%s]\n" DISK_1 "${DISK_1}"
@printf "%-20s : [%s]\n" DISK_2 "${DISK_2}"
@printf "%-20s : [%s]\n" DISKS "${DISKS}"
@printf "%-20s : [%s]\n" LABEL_ZFS "${LABEL_ZFS}"
@printf "%-20s : [%s]\n" LABEL_BOOT "${LABEL_BOOT}"
@printf "%-20s : [%s]\n" SWAPSIZE "${SWAPSIZE}"
@printf "%-20s : [%s]\n" TMP "${TMP}"
@printf "%-20s : [%s]\n" CACHEFILE "${CACHEFILE}"
@printf "%-20s : [%s]\n" MOUNT "${MOUNT}"
@printf "%-20s : [%s]\n" TEMPLATE_RC_CONF "${TEMPLATE_RC_CONF}"
@printf "%-20s : [%s]\n" RC_CONF "${RC_CONF}"
@printf "%-20s : [%s]\n" LOADER_CONF "${LOADER_CONF}"
@printf "%-20s : [%s]\n" FSTAB "${FSTAB}"
@echo -------------------------
@echo "Checking for installation sets ..."
.for X in ${INSTALL_SETS}
@printf "Set $X : "
@if [ -f ${X} ] ; then echo found! ; else echo missing! ; fi
.endfor
@echo "Checking for 'rc.conf' template ..."
.if exists(${TEMPLATE_RC_CONF})
@echo ----------- rc.conf template ----------
cat ${TEMPLATE_RC_CONF}
.else
@echo ${TEMPLATE_RC_CONF} not found
exit 100
.endif
# -----------------------------------------------------------------------------
# WARNING: you can 'make' all targets except the 'trailer' target!!
# 'trailer' is a makefile macro and only works as prerequisite of other targets
# Scroll down to the end of the Makefile and read the reason why .....
# -----------------------------------------------------------------------------
diskinfo: trailer
.for X in ${DISKS}
if [ -e ${X} ] ; then diskinfo -v ${X} ; fi
.endfor
# --- Load FreeBSD ZFS kernel modules
zfsload: trailer
kldload -n zfs
kldload -n opensolaris
kldstat
# --------- using make '.for .. in ... .endfor' construct
# make variables you can refer to with a single '$' as in ${X}
# for shell variables you must use a double $$ as $${NR}
partition: trailer
.for X in ${DISKS}
if gpart show ${X} ; then gpart destroy -F ${X} ; fi
gpart create -s gpt ${X}
NR=$$( echo ${X} | tr -c -d '0-9' ) ;\
gpart add -b 40 -s 128k -t freebsd-boot -l ${LABEL_BOOT}$${NR} ${X} ;\
gpart add -t freebsd-zfs -l ${LABEL_ZFS}$${NR} ${X}
gpart bootcode -b /boot/pmbr -p /boot/gptzfsboot -i 1 ${X}
gpart show ${X}
.endfor
ls -l /dev/gpt
# --- Procedure to coach ZFS to use 4K sector aligned read/writes
gnop4k: trailer
@echo Creating gnop devices with 4K sectors .....
for X in ${DISKS} ; do \
NR=$$( echo $${X} | tr -c -d '0-9' ) ;\
gnop create -S 4096 /dev/gpt/${LABEL_ZFS}$${NR} ;\
done
ls -l /dev/gpt
gnop list
gnop status
pool4k: trailer
@echo Creating zpool with 4K gnop devices
if [ -f ${CACHEFILE} ] ; then mv ${CACHEFILE} ${CACHEFILE}.prev ; fi
ls -l /dev/gpt
# ---- creating a ZFS mirror pool without automatically mounting it (-m none) .....
zpool create -f -o cachefile=${CACHEFILE} -m none ${POOL} mirror /dev/gpt/${LABEL_ZFS}*nop
zpool list ${POOL}
zpool status ${POOL}
zfs list
export: trailer
@echo Exporting ${POOL} ......
@echo ---------------------------------
zpool export ${POOL}
@echo ---------------------------------
@echo Showing import status of ${POOL} ......
@echo ---------------------------------
zpool import
gnop_destroy: trailer
@echo Destroy the 4K gnop devices
for X in ${DISKS} ; do \
NR=$$( echo $${X} | tr -c -d '0-9' ) ;\
gnop destroy /dev/gpt/${LABEL_ZFS}$${NR}.nop ;\
done
ls -l /dev/gpt
for X in ${DISKS} ; do gpart show $${X} ; done
import: trailer
@echo Import the pool
zpool import -o cachefile=${CACHEFILE} ${POOL}
zpool list ${POOL}
@echo ---------------------------------
zpool status ${POOL}
@echo ---------------------------------
mount
chk_ashift: trailer
@echo "Verify that ashift value is 12 (2^12 = 4096 ; 2^9 = 512)"
@echo =========================================================
zdb -C -U ${CACHEFILE} ${POOL}
@echo ====================================
zdb -C -U ${CACHEFILE} ${POOL} | grep ashift
# --- Having a 4K aligned pool we now continue with the Vermaden 'ZFS madness' procedure
zfs_options: trailer
zfs set mountpoint=none ${POOL}
zfs set checksum=fletcher4 ${POOL}
zfs set atime=off ${POOL}
zpool list ${POOL}
zpool status ${POOL}
zfs list
zfs_fs: trailer
@echo ---------------------------------
zfs create ${ROOT_SET}
zfs create -o mountpoint=${MOUNT} ${BOOT_SET}
zpool set bootfs=${BOOT_SET} ${POOL}
zfs list
@echo ---------------------------------
mount
# --- alternatively you can configure ZFS swap after reboot
zfs_swap: trailer
zfs create -V ${SWAPSIZE} ${POOL}/swap
zfs set org.freebsd:swap=on ${POOL}/swap
zfs set checksum=off ${POOL}/swap
zfs set sync=disabled ${POOL}/swap
zfs set primarycache=none ${POOL}/swap
zfs set secondarycache=none ${POOL}/swap
zfs list
# ==========================================================
pre_install: trailer partition gnop4k pool4k export gnop_destroy import \
chk_ashift zfs_options zfs_fs zfs_swap
install: trailer
for THIS in ${INSTALL_SETS} ; do \
tar --unlink -xvpJf $${THIS} -C ${MOUNT} ;\
done
ls -l ${MOUNT}
zpool status ${POOL}
zpool iostat ${POOL}
zfs list
post_install: trailer loader.conf fstab rc.conf zfs_boot zfs_umount mountpoint
all: trailer pre_install install post_install
# ==========================================================
loader.conf: trailer
echo 'zfs_load=YES' >> ${LOADER_CONF}
echo 'vfs.root.mountfrom="zfs:${BOOT_SET}"' >> ${LOADER_CONF}
@echo =========== loader.conf =========================
@cat ${LOADER_CONF}
fstab: trailer
# ---- create empty /etc/fstab file
touch ${FSTAB}
rc.conf: trailer
cat ${TEMPLATE_RC_CONF} >> ${RC_CONF}
echo 'zfs_enable="YES"' >> ${RC_CONF}
@echo =========== rc.conf =========================
@cat ${RC_CONF}
zfs_boot: trailer
if [ ! -d ${MOUNT}/boot/zfs ] ; then echo Cannot copy ${CACHEFILE} to ${MOUNT}/boot/zfs ! ; exit 2 ; fi
cp -p ${CACHEFILE} ${MOUNT}/boot/zfs/
ls -l ${MOUNT}/boot/zfs
zfs_umount: trailer
zfs unmount -a
zfs list
@echo ------
mount
mountpoint: trailer
ls -l ${TMP}
zfs set mountpoint=legacy ${BOOT_SET}
zfs list
@echo Setup is finished ............
@echo ----------------- done ! ---------------------------------
@echo you can now reboot into your new ZFS only system now .....
@echo ----------------------------------------------------------
@echo Or you could finish the elementary setup with:
@echo
@echo "1. Remount your ZFS file system : mount -t zfs ${BOOT_SET} ${MOUNT}"
@echo "2. Chroot into ${MOUNT} : chroot ${MOUNT}"
@echo "3. Change the root password : passwd"
@echo "4. Create the /etc/mail/aliases.db : newaliases"
@echo "5. Set up your time zone : tzsetup"
@echo
@echo "6. Exit the chroot : exit"
@echo "7. Unmount : umount ${MOUNT}"
# ====================== U t i l i t i e s ================================
# --- if something went wrong with the pool/zfs creation you clean up with:
pool_destroy: trailer
zfs destroy -r ${POOL}
zfs list
zpool destroy ${POOL}
zpool status
zfs list
# --- destroy gpart partition scheme and zero out first 256 MB of disk(s)
gpart_destroy: trailer
.for X in ${DISKS}
if gpart show ${X} ; then gpart destroy -F ${X} ; fi
dd if=/dev/zero of=${X} bs=1m count=256
.endfor
# --- create FreeBSD memory disks (for testing Makefile modifications)
md_create: trailer
@${DEBUG} echo Creating Memory disk devices ${DISKS}:
@${DEBUG} mdconfig -a -t swap -s ${MD_SIZE} -u 1
@${DEBUG} mdconfig -a -t swap -s ${MD_SIZE} -u 2
@${DEBUG} echo Memory disk devices:
@${DEBUG} mdconfig -lv -u 1
@${DEBUG} mdconfig -lv -u 2
@${DEBUG} ls -l /dev/md*
md_destroy: trailer
@${DEBUG} mdconfig -d -u 1
@${DEBUG} mdconfig -d -u 2
@${DEBUG} echo Memory disk devices:
@${DEBUG} ls -l /dev/md*
# --- don't make the target 'trailer'. I once tried for fun and ran out of swap space!
# From 'dmesg'
# swap_pager: out of swap space
# swap_pager_getswapspace(16): failed
# pid 1812 (make), uid 0, was killed: out of swap space
trailer: .USE
@echo -------------end of ${.TARGET} --------------------
.PHONY: show diskinfo zfsload md_create md_destroy partition gnop4k pool4k export
.PHONY: gnop_destroy import chk_ashift zfs_options zfs_fs zfs_swap install
.PHONY: loader.conf fstab rc.conf zfs_boot zfs_umount mountpoint pool_destroy
.PHONY: gpart_destroy pre_install post_install all trailer
# ---- end of Makefile
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment