4 _root="$(dirname "${0}")"
5 . "${_root}/lib/env.sh"
10 targets="prepwork admin overlay packages patch whiteout scripts prepboot preptmp prepetc imgboot imgconf imgetc imgall custom"
13 [ "${*}" ] && printf "${*}\n\n"
14 echo "********************************************************************************"
15 echo "Usage: $(basename "${0}") <-a arch> <-c conf> <-b basedir> <-t stagedir>"
16 echo ' <-i pkgsdir> <-p patchdir> <-r rootdir> <-l logfile> [-h] <targets>'
17 echo " -a arch Arch for image target (default: current arch: $(uname -m))"
18 echo ' -c conf Conf name for image target (default: GENERIC)'
19 echo ' -b basedir Basedir for automagic defaults'
20 echo ' -t stagedir Staging directory name (${target}/tree)'
21 echo ' -r rootdir Directory holding the virgin source tree (${target}/world/root)'
22 echo ' -o overlaydir Directory holding an overlay tree (${target}/config/overlay)'
23 echo ' -i pkgsdir Directory holding packages to install (${target}/pkg)'
24 echo ' -p patchdir Directory holding patches to apply (${target}/config/patch)'
25 echo ' -w whiteout Listfile of paths to remove (${target}/config/whiteout.lst)'
26 echo ' -s scriptdir Directory holding scripts to apply (${target}/config/script)'
27 echo ' -d confdir Conf md_size files and old cpios (${target}/config/conf)'
28 echo ' -l logfile File to hold stderr spam (${stage}/gentree.log'
29 echo ' -h Hello! >^-^<'
31 echo 'Available targets:'
32 for target in ${targets}
39 while getopts "a:c:b:t:r:o:i:p:w:s:l:h" opt
42 a) arch="${OPTARG}" ;;
43 c) conf="${OPTARG}" ;;
44 b) base="${OPTARG}" ;;
45 t) tree="${OPTARG}" ;;
46 r) root="${OPTARG}" ;;
47 o) ovly="${OPTARG}" ;;
48 i) pkgs="${OPTARG}" ;;
49 p) ptch="${OPTARG}" ;;
50 w) rmrf="${OPTARG}" ;;
51 s) scpt="${OPTARG}" ;;
52 d) cnfd="${OPTARG}" ;;
53 l) logf="${OPTARG}" ;;
55 [?]) pebkac "Unrecognized option ${opt}" ;;
58 shift $(( $OPTIND - 1 ))
60 sequence="${*:-${targets}}"
62 base="${base:-$(dirname "${0}")/..}" #"
63 base="$(realpath "${base}")"
65 arch="${arch:-$(uname -m)}"
66 conf="${conf:-GENERIC}"
68 target="${base}/targets/${arch}/${conf}"
70 tree="${tree:-${target}/tree}"
71 root="${root:-${target}/world/root}"
72 ovly="${ovly:-${target}/config/overlay}"
73 pkgs="${pkgs:-${target}/pkg}"
74 ptch="${ptch:-${target}/config/patch}"
75 rmrf="${rmrf:-${target}/config/whiteout.lst}"
76 scpt="${scpt:-${target}/config/script}"
77 cnfd="${cnfd:-${target}/config/conf}"
78 logf="${logf:-${tree}/gentree.log}"
80 stage="${stage:-${base}/tree}"
81 pkgs="${pkgs:-${base}/pkg}"
82 patch="${patch:-${base}/patch}"
83 overlay="${overlay:-${base}/overlay}"
84 root="${root:-${base}/root}"
85 logfile="${logfile:-${stage}/gentree.log}"
88 tree="$(realpath "${tree}")"
95 meh "Logging to ${logf}"
99 # Make sure the provided file(s) have only one link!
102 if [ -f "${1}" -a "$(stat -f '%l' "${1}")" -gt 1 ]
104 cp -p "${1}" "${1}.tmp" && mv "${1}.tmp" "${1}" || err "breaklink failed"
111 chroot "${sroot}" "${@}"
115 for fakeroot in $(which fakeroot) /usr/local/bin/fakeroot
117 [ -x "${fakeroot}" ] && break
121 [ -x "${fakeroot}" ] || warn "security/fakeroot not found! Expect weird ownership from overlay"
127 log Prepare workspace
128 if [ -d "${tree}" -a \( -d "${sroot}" -o -d "${sboot}" -o -d "${sconf}" \) ]
130 yn n " ${a_yellow}*${a_normal} Workspace already exists. Delete? [y/N]" || err Aborting
131 chk rm -Rf "${sroot}" "${sboot}" "${sconf}"
133 chk mkdir -p "${sroot}"
134 # Eliminate schg, because it interferes with hardlinks
135 chk chflags -R noschg "${root}/lib"
136 chk chflags -R noschg "${root}/libexec"
137 chk chflags -R noschg "${root}/sbin"
138 chk chflags -R noschg "${root}/usr"
139 ( cd "${root}" && find . | cpio -pl "${sroot}" ) || chk
143 log Create an emergency user admin/admin
144 # delink passwd to ensure it doesn't get patched in-place
145 chk onelink "${sroot}/etc/passwd" "${sroot}/etc/master.passwd" "${sroot}/etc/group"
146 echo '$1$2rXOWsK/$eiBHA6K7xL96DZbcY24YR0' | chk in_chroot /usr/sbin/pw useradd admin -u 999 -g wheel -G operator -c Administrator -d /usr/home/admin -m -s /bin/csh -H 0
150 [ -d "${ovly}" ] || return
151 log Apply overlay from "${ovly##${base}/}"
152 ( cd "${ovly}" && find . | cpio -pR root:wheel "${sroot}" ) || chk
156 [ -d "${pkgs}" ] || return
157 count="$(ls -1 "${pkgs}" | wc -l)"
158 [ "${count}" -gt 0 ] || return
159 log Install ${count} packages from "${pkgs##${base}/}"
160 chk mkdir -p "${sroot}/pkg"
161 ( cd "${pkgs}" && find . | cpio -pl "${sroot}/pkg" ) || chk
162 chk chroot "${sroot}" /bin/sh -c 'cd /pkg; exec pkg_add -F *'
163 chk rm -Rf "${sroot}/pkg"
167 [ -d "${ptch}" ] || return
168 log Apply patches from "${ptch##${base}/}"
169 for file in "${ptch}"/*
171 note "... $(basename "${file}")"
172 ( cd "${sroot}" && patch < "${file}" ) || chk
174 sed -e '/^+++ /!d; s/^+++ //; s/[ ]*[0-9: .+-]*$//' "${file}" | while read orig
176 rm -f "${sroot}/${orig}.orig"
182 [ -f "${rmrf}" ] || return
183 log Whiteout files from "${rmrf##${base}/}"
184 while read entry < "${rmrf}"
186 # Strip off terminating slash
188 # Obtain directory name, for path resolution
189 entry_path="$(dirname "${entry}")"
190 entry_path="$(realpath "${sroot}/${entry_path}" 2>/dev/null)"
191 entry_file="$(basename "${entry}")"
193 # Ignore paths that do not exist
194 [ "${entry_path}" ] || continue
196 # Warn and ignore paths that fall outside ${sroot} after resolution
197 if [ "$(echo ${entry_path} | sed -e "s/^\(.\{${#sroot}\}\).*$/\1/")" != "${sroot}" ]
199 warn "Whiteout '${entry}' malformed: leads outside '${sroot}'"
203 # Warn and ignore paths that cannot be chdir()'d
204 if [ ! -d "${entry_path}" ]
206 warn "Whiteout '${entry}' malformed: non-directory in path"
213 echo rm -Rf "${entry}"
215 echo rm -f "${entry}"
221 # Run arbitrary user scripts to perform additional functions prior to carving and packaging
222 # Warning! These run as root and no chroot is performed! BE CAREFUL HERE!
224 [ -d "${scpt}" ] && ls -1 "${scpt}" | grep -q '.' || return
225 log Run user scripts from "${scpt##${base}/}"
226 ls -1 "${scpt}" | LANG=C sort | while read file
228 [ -f "${scpt}/${file}" ] || continue
238 chk mv "${sroot}/boot/boot" "${sroot}/boot/boot.blk"
239 chk ln -sf . "${sroot}/boot/boot"
241 # Move /boot/zfs to /etc/zfs and symlink
242 ls -1 "${sroot}/boot/zfs"/* 2>&- && chk mv "${sroot}/boot/zfs"/* "${sroot}/etc/zfs/"
243 chk rmdir "${sroot}/boot/zfs"
244 chk ln -svf ../etc/zfs "${sroot}/boot/zfs"
249 ( cd "${sroot}/tmp" && find . | cpio -p --link ../var/tmp ) || chk
250 chk rm -Rf "${sroot}/tmp"
251 chk ln -sf var/tmp "${sroot}/tmp"
256 chk mkdir -p "${sroot}/etc/local"
257 chk mkdir -p "${sroot}/usr/local/etc" # Silence warnings
258 ( cd "${sroot}/usr/local/etc" && find . | cpio -pl ../../../etc/local ) || chk
259 chk rm -Rf "${sroot}/usr/local/etc"
260 chk ln -sf ../../etc/local "${sroot}/usr/local/etc"
264 log Create boot imgsrc
265 chk mv "${sroot}/boot" "${tree}"
266 chk mkdir -p "${sroot}/boot"
267 chk mkdir -p "${sroot}/modules"
268 chk find "${sboot}/kernel" -type f -name '*.symbols' -delete
270 # Link all modules into /modules, as an alternative search path
271 ( cd "${sboot}/kernel" && find . -name '*.ko' -o -name "${conf}" | cpio -pl "${sroot}/modules" ) || chk
272 ( cd "${sboot}/modules" && find . -name '*.ko' -o -name "${conf}" | cpio -pl "${sroot}/modules" ) || chk
274 # Remove all modules from the root fs that are preloaded by the preloader
275 [ ! -f "${sboot}/loader.conf" ] || cat "${sboot}/loader.conf" | grep '_load=' | \
276 sed -e 's#^\(.*\)_load=.*$#'"${sroot}/modules/"'\1.ko#' | xargs rm -f
278 # Remove all modules from the boot fs that are present in the root fs
279 ( cd "${sroot}/modules" && ls -1 ) | sed -e 's#^#'"${sboot}/kernel/"'#' | xargs rm -f
280 ( cd "${sroot}/modules" && ls -1 ) | sed -e 's#^#'"${sboot}/modules/"'#' | xargs rm -f
282 # Rebuild linker hints in both places, to make sure the kernel can find everything once running
283 kldxref "${sboot}/kernel" "${sboot}/modules" "${sroot}/modules"
285 # Gzip kernel to save boot space
286 chk onelink "${sboot}/kernel/kernel"
287 chk gzip -9 "${sboot}/kernel/kernel"
289 # Compress all the modules as well, since /loader can handle them; kldload cannot,
290 # but I think I can safely assume you will not be kldunloading boot-loaded modules
291 ls -1 "${sboot}/kernel/"*.ko "${sboot}/modules/"*.ko 2>/dev/null | while read kmod
293 chk onelink "${kmod}"
294 chk gzip -9 "${kmod}"
297 # Make sure /boot/zfs -> /etc/zfs symlink exists even without /boot mounted
298 [ -L "${sroot}/boot/zfs" ] || chk ln -sf ../etc/zfs "${sroot}/boot/zfs"
303 chk mkdir -p "${sroot}/conf"
304 echo "ufs:/dev/ufs/conf" > "${sroot}/conf/diskless_remount" || chk
305 chk mkdir -p "${sconf}/backup"
306 chk mkdir -p "${sconf}/base"
307 chk mkdir -p "${sconf}/default"
309 # This will screw up permissions when applied to /conf/{base,default}/*
310 # so do it before their creation
311 chk chown -R :operator "${sconf}"
312 ( find "${sconf}" -print0 | xargs -0 chmod o-rwx ) || chk
314 # Create packdirs for each
315 for pack in "${cnfd}"/*.md_size
317 pack="$(basename "${pack%%.md_size}")"
318 chk mkdir -p "${sconf}/base/${pack}" "${sconf}/default/${pack}"
319 chk chown root:wheel "${sconf}/base/${pack}" "${sconf}/default/${pack}"
320 chk chmod 755 "${sconf}/base/${pack}" "${sconf}/default/${pack}"
321 ( cat "${cnfd}/${pack}.md_size" > "${sconf}/base/${pack}/md_size" ) || chk
326 # etc requires special handling to ensure everything is properly arranged.
327 log Create etc confpack
328 chk touch "${sroot}/etc/diskless"
329 # Touch /COPYRIGHT to provide an immutable time anchor for saveconfig
330 chk touch "${sroot}/COPYRIGHT"
331 chk sleep 1 # Make sure anchor is at least one second older than everything in custom
332 chk mkdir -p "${tree}/pack"
333 chk mv "${sroot}/etc" "${tree}/pack"
334 chk mkdir -p "${sroot}/etc"
335 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"
337 # Make sure etc confpack exists; default if necessary
338 chk mkdir -p "${sconf}/base/etc"
339 chk mkdir -p "${sconf}/default/etc"
340 [ -e "${sconf}/base/etc/md_size" ] || echo "10240" > "${sconf}/base/etc/md_size"
342 ( cd "${tree}/pack" && find etc | cpio -o ) | gzip -9 > "${sconf}/base/etc.cpio.gz" || chk
343 chk rm -Rf "${tree}/pack"
347 log Create remaining confpacks
348 chk mkdir -p "${tree}/pack"
349 for pack in "${cnfd}"/*.md_size
351 pack="$(basename "${pack%%.md_size}")"
352 # Ignore etc, as it was processed in do_imgetc
353 [ "${pack}" = "etc" ] && continue
355 chk mv "${sroot}/${pack}" "${tree}/pack"
356 chk mkdir -p "${sroot}/${pack}"
357 ( cd "${tree}/pack" && find "${pack}" | cpio -o ) | gzip -9 > "${sconf}/base/${pack}.cpio.gz" || chk
358 rm -Rf "${tree}/pack/${pack}" || chflags -R noschg "${tree}/pack/${pack}" && rm -Rf "${tree}/pack/${pack}" || chk
360 chk rm -Rf "${tree}/pack"
364 log Patch in custom config
365 ( cd "${cnfd}" && find . -name '*.md_size' -o -print | cpio -pl "${sconf}/default" ) || chk
366 # Make sure files in default are newer than the tagfile, so they will be caught by saveconfig
367 find "${sconf}/default" -type f -print0 | xargs -0 touch
368 # If there are scavenged cpio.gz confpacks, touch their contents as well.
369 chk mkdir -p "${tree}/pack"
370 if ls -1 "${sconf}/default" | grep -q '.cpio.gz'
372 chk rm -Rf "${tree}/pack"/*
373 for file in "${sconf}/default"/*.cpio.gz
375 pack="$(basename "${file%%.cpio.gz}")"
376 zcat "${file}" | ( cd "${tree}/pack"; cpio -id ) || chk
377 find "${tree}/pack" -type f -print0 | xargs -0 touch
378 ( cd "${tree}/pack"; find . | cpio -o ) | gzip -9 > "${file}"
379 chk rm -Rf "${tree}/pack"/*
382 chk rm -Rf "${tree}/pack"
385 for step in ${sequence}
387 echo "${targets}" | grep -q "${step}" || err Unrecognized target "${step}"