Skip to content

Instantly share code, notes, and snippets.

@urosgruber
Forked from rosstimson/Makefile-ZFS-madness-1.18
Created November 16, 2013 23:29
Show Gist options
  • Select an option

  • Save urosgruber/7506783 to your computer and use it in GitHub Desktop.

Select an option

Save urosgruber/7506783 to your computer and use it in GitHub Desktop.

Revisions

  1. @rosstimson rosstimson created this gist Oct 4, 2013.
    383 changes: 383 additions & 0 deletions Makefile-ZFS-madness-1.18
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,383 @@
    # ---- 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