#!/bin/sh # Boilerplate _root="$(dirname "${0}")" . "${_root}/lib/env.sh" # Load needed modules want root ansi log ask targets="prepwork admin overlay packages patch whiteout prepboot preptmp prepetc imgboot imgconf imgetc imgall custom" pebkac() { [ "${*}" ] && printf "${*}\n\n" echo "********************************************************************************" echo "Usage: $(basename "${0}") <-a arch> <-c conf> <-b basedir> <-t stagedir>" echo ' <-i pkgsdir> <-p patchdir> <-r rootdir> <-l logfile> [-h] ' echo " -a arch Arch for image target (default: current arch: $(uname -m))" echo ' -c conf Conf name for image target (default: GENERIC)' echo ' -b basedir Basedir for automagic defaults' echo ' -t stagedir Staging directory name (${target}/tree)' echo ' -r rootdir Directory holding the virgin source tree (${target}/world/root)' echo ' -o overlaydir Directory holding an overlay tree (${target}/config/overlay)' echo ' -i pkgsdir Directory holding packages to install (${target}/pkg)' echo ' -p patchdir Directory holding patches to apply (${target}/config/patch)' echo ' -w whiteout Listfile of paths to remove (${target}/config/whiteout.lst)' echo ' -d confdir Conf md_size files and old cpios (${target}/config/conf)' echo ' -l logfile File to hold stderr spam (${stage}/gentree.log' echo ' -h Hello! >^-^<' echo '' echo 'Available targets:' for target in ${targets} do echo " ${target}" done exit 1 } while getopts "a:c:b:t:r:o:i:p:w:l:h" opt do case "${opt}" in a) arch="${OPTARG}" ;; c) conf="${OPTARG}" ;; b) base="${OPTARG}" ;; t) tree="${OPTARG}" ;; r) root="${OPTARG}" ;; o) ovly="${OPTARG}" ;; i) pkgs="${OPTARG}" ;; p) ptch="${OPTARG}" ;; w) rmrf="${OPTARG}" ;; d) cnfd="${OPTARG}" ;; l) logf="${OPTARG}" ;; h) pebkac ;; [?]) pebkac "Unrecognized option ${opt}" ;; esac done shift $(( $OPTIND - 1 )) sequence="${*:-${targets}}" base="${base:-$(dirname "${0}")/..}" #" base="$(realpath "${base}")" arch="${arch:-$(uname -m)}" conf="${conf:-GENERIC}" target="${base}/targets/${arch}/${conf}" tree="${tree:-${target}/tree}" root="${root:-${target}/world/root}" ovly="${ovly:-${target}/config/overlay}" pkgs="${pkgs:-${target}/pkg}" ptch="${ptch:-${target}/config/patch}" rmrf="${rmrf:-${target}/config/whiteout.lst}" cnfd="${cnfd:-${target}/config/conf}" logf="${logf:-${tree}/gentree.log}" stage="${stage:-${base}/tree}" pkgs="${pkgs:-${base}/pkg}" patch="${patch:-${base}/patch}" overlay="${overlay:-${base}/overlay}" root="${root:-${base}/root}" logfile="${logfile:-${stage}/gentree.log}" mkdir -p "${tree}" tree="$(realpath "${tree}")" sroot="${tree}/root" sboot="${tree}/boot" sconf="${tree}/conf" exec 2>>"${logf}" _log_to_stderr=yes # Helper functions onelink() { # Make sure the provided file(s) have only one link! while [ -n "${1}" ] do if [ -f "${1}" -a "$(stat -f '%l' "${1}")" -gt 1 ] then cp -p "${1}" "${1}.tmp" && mv "${1}.tmp" "${1}" || err "breaklink failed" fi shift done } # Build steps do_prepwork() { log Prepare workspace if [ -d "${tree}" -a \( -d "${sroot}" -o -d "${sboot}" -o -d "${sconf}" \) ] then yn n " ${a_yellow}*${a_normal} Workspace already exists. Delete? [y/N]" || err Aborting chk rm -Rf "${sroot}" "${sboot}" "${sconf}" fi chk mkdir -p "${sroot}" # Eliminate schg, because it interferes with hardlinks chk chflags -R noschg "${root}/lib" chk chflags -R noschg "${root}/libexec" chk chflags -R noschg "${root}/sbin" chk chflags -R noschg "${root}/usr" ( cd "${root}" && find . | cpio -p --link "${sroot}" ) || chk } do_admin() { log Create an emergency user admin/admin # delink passwd to ensure it doesn't get patched in-place chk onelink "${sroot}/etc/passwd" "${sroot}/etc/master.passwd" echo '$1$2rXOWsK/$eiBHA6K7xL96DZbcY24YR0' | chk chroot "${sroot}" /usr/sbin/pw useradd admin -u 999 -g wheel -G operator -c Administrator -d /usr/home/admin -m -s /bin/csh -H 0 } do_overlay() { [ -d "${ovly}" ] || return log Apply overlay from "${ovly##${base}/}" ( cd "${ovly}" && find . | cpio -p "${sroot}" ) || chk } do_packages() { [ -d "${pkgs}" ] || return count="$(ls -1 "${pkgs}" | wc -l)" [ "${count}" -gt 0 ] || return log Install ${count} packages from "${pkgs##${base}/}" chk mkdir -p "${sroot}/pkg" ( cd "${pkgs}" && find . | cpio -p --link "${sroot}/pkg" ) || chk chk chroot "${sroot}" /bin/sh -c 'cd /pkg; exec pkg_add -F *' chk rm -Rf "${sroot}/pkg" } do_patch() { [ -d "${ptch}" ] || return log Apply patches from "${ptch##${base}/}" for file in "${ptch}"/* do note "... $(basename "${file}")" ( cd "${sroot}" && patch < "${file}" ) || chk # Remove .orig files sed -e '/^+++ /!d; s/^+++ //; s/[ ]*[0-9: .+-]*$//' "${file}" | while read orig do rm -f "${sroot}/${orig}.orig" done || chk done } do_whiteout() { [ -f "${rmrf}" ] || return log Whiteout files from "${rmrf##${base}/}" while read entry < "${rmrf}" do # Strip off terminating slash entry="${entry%%/}" # Obtain directory name, for path resolution entry_path="$(dirname "${entry}")" entry_path="$(realpath "${sroot}/${entry_path}" 2>/dev/null)" entry_file="$(basename "${entry}")" # Ignore paths that do not exist [ "${entry_path}" ] || continue # Warn and ignore paths that fall outside ${sroot} after resolution if [ "$(echo ${entry_path} | sed -e "s/^\(.\{${#sroot}\}\).*$/\1/")" != "${sroot}" ] then warn "Whiteout '${entry}' malformed: leads outside '${sroot}'" continue fi # Warn and ignore paths that cannot be chdir()'d if [ ! -d "${entry_path}" ] then warn "Whiteout '${entry}' malformed: non-directory in path" continue fi ( cd "${entry_path}" if [ -d "${entry}" ] then echo rm -Rf "${entry}" else echo rm -f "${entry}" fi ) done } do_prepboot() { log Prepare /boot chk mv "${sroot}/boot/boot" "${sroot}/boot/boot.blk" chk ln -sf . "${sroot}/boot/boot" } do_preptmp() { log Prepare /tmp ( cd "${sroot}/tmp" && find . | cpio -p --link ../var/tmp ) || chk chk rm -Rf "${sroot}/tmp" chk ln -sf var/tmp "${sroot}/tmp" } do_prepetc() { log Prepare /etc chk mkdir -p "${sroot}/etc/local" chk mkdir -p "${sroot}/usr/local/etc" # Silence warnings ( cd "${sroot}/usr/local/etc" && find . | cpio -p --link ../../../etc/local ) || chk chk rm -Rf "${sroot}/usr/local/etc" chk ln -sf ../../etc/local "${sroot}/usr/local/etc" } do_imgboot() { log Create boot imgsrc chk mv "${sroot}/boot" "${tree}" chk mkdir -p "${sroot}/boot" chk rm -f "${sboot}/kernel"/*.symbols # Gzipped kernel is okay chk onelink "${sboot}/kernel/kernel" chk gzip -9 "${sboot}/kernel/kernel" # Compress all files in /boot/kernel # Loader cannot handle gzipped modules. Decompress the required modules # kldload cannot handle gzipped modules either #find "${sboot}/kernel" -type f | xargs gzip -9f || chk #cat "${sboot}/loader.conf" | grep '_load=' | sed -e 's/^\(.*\)_load=.*$/\1/' | while read mod #do # [ -f "${sboot}/kernel/${mod}.ko.gz" ] && gunzip "${sboot}/kernel/${mod}.ko.gz" || chk #done # # Instead: put all modules in the root image, except those needed to boot the kernel chk mkdir -p "${sroot}/boot/boot" chk mkdir -p "${sroot}/boot/kernel" chk ln -sf "../etc/zfs" "${sroot}/boot/zfs" # Link all modules into the root fs ( cd "${sboot}/kernel" && find . -name '*.ko' -o -name 'linker.hints' | cpio -p --link "${sroot}/boot/kernel" ) || chk # Remove all modules from the root fs that are preloaded by the loader cat "${sboot}/loader.conf" | grep '_load=' | sed -e 's#^\(.*\)_load=.*$#'"${sroot}/boot/kernel/"'\1.ko#' | xargs rm -f # Remove all modules from the boot fs that are present in the root fs ( cd "${sroot}/boot/kernel" && ls -1 ) | sed -e 's#^#'"${sboot}/kernel/"'#' | xargs rm -f # Link the preloaded modules from the boot fs to the root fs, to provide a homogenous view ( cd "${sboot}/kernel" && ls -1 ) | while read mod do ln -sf "../boot/kernel/${mod}" "${sroot}/boot/kernel/${mod}" done } do_imgconf() { log Create conftree chk mkdir -p "${sroot}/conf" echo "ufs:/dev/ufs/conf" > "${sroot}/conf/diskless_remount" || chk chk mkdir -p "${sconf}/backup" chk mkdir -p "${sconf}/base" chk mkdir -p "${sconf}/default" # Create packdirs for each for pack in "${cnfd}"/*.md_size do pack="$(basename "${pack%%.md_size}")" chk mkdir -p "${sconf}/base/${pack}" chk mkdir -p "${sconf}/default/${pack}" ( cat "${cnfd}/${pack}.md_size" > "${sconf}/base/${pack}/md_size" ) || chk done chk chown -R :operator "${sconf}" ( find "${sconf}" -print0 | xargs -0 chmod o-rwx ) || chk } do_imgetc() { # etc requires special handling to ensure everything is properly arranged. log Create etc confpack chk touch "${sroot}/etc/diskless" # Touch /COPYRIGHT to provide an immutable time anchor for saveconfig chk touch "${sroot}/COPYRIGHT" chk sleep 1 # Make sure anchor is at least one second older than everything in custom chk mkdir -p "${tree}/pack" chk mv "${sroot}/etc" "${tree}/pack" chk mkdir -p "${sroot}/etc" chk cp -p "${tree}/pack/etc/rc" "${tree}/pack/etc/rc.subr" "${tree}/pack/etc/rc.initdiskless" "${tree}/pack/etc/login.conf.db" "${tree}/pack/etc/diskless" "${sroot}/etc" # Make sure etc confpack exists; default if necessary chk mkdir -p "${sconf}/base/etc" chk mkdir -p "${sconf}/default/etc" [ -e "${sconf}/base/etc/md_size" ] || echo "10240" > "${sconf}/base/etc/md_size" ( cd "${tree}/pack" && find etc | cpio -o ) | gzip -9 > "${sconf}/base/etc.cpio.gz" || chk chk rm -Rf "${tree}/pack" } do_imgall() { log Create remaining confpacks chk mkdir -p "${tree}/pack" for pack in "${cnfd}"/*.md_size do pack="$(basename "${pack%%.md_size}")" # Ignore etc, as it was processed in do_imgetc [ "${pack}" = "etc" ] && continue note "... ${pack}" chk mv "${sroot}/${pack}" "${tree}/pack" chk mkdir -p "${sroot}/${pack}" ( cd "${tree}/pack" && find "${pack}" | cpio -o ) | gzip -9 > "${sconf}/base/${pack}.cpio.gz" || chk chk rm -Rf "${tree}/pack/${pack}" done chk rm -Rf "${tree}/pack" } do_custom() { log Patch in custom config ( cd "${cnfd}" && find . -name '*.md_size' -o -print | cpio -p --link "${sconf}/default" ) || chk # Make sure files in default are newer than the tagfile, so they will be caught by saveconfig find "${sconf}/default" -type f -print0 | xargs -0 touch # If there are scavenged cpio.gz confpacks, touch their contents as well. chk mkdir -p "${tree}/pack" if ls -1 "${sconf}/default" | grep -q '.cpio.gz' then chk rm -Rf "${tree}/pack"/* for file in "${sconf}/default"/*.cpio.gz do pack="$(basename "${file%%.cpio.gz}")" zcat "${file}" | ( cd "${tree}/pack"; cpio -id ) || chk find "${tree}/pack" -type f -print0 | xargs -0 touch ( cd "${tree}/pack"; find . | cpio -o ) | gzip -9 > "${file}" chk rm -Rf "${tree}/pack"/* done fi chk rm -Rf "${tree}/pack" } for step in ${sequence} do echo "${targets}" | grep -q "${step}" || err Unrecognized target "${step}" do_${step} done