script/gentree: clean up custom config applicator to ignore missing cpios
[CDN/Mosi.git] / script / gentree
1 #!/bin/sh
2
3 # Boilerplate
4 _root="$(dirname "${0}")"
5 . "${_root}/lib/env.sh"
6
7 # Load needed modules
8 want root ansi log ask
9
10 targets="prepwork admin overlay packages patch prepboot preptmp prepetc imgboot imgconf imgetc imgall custom"
11
12 pebkac() {
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 '  -d confdir    Conf md_size files and old cpios (${target}/config/conf)'
26   echo '  -l logfile    File to hold stderr spam (${stage}/gentree.log'
27   echo '  -h            Hello! >^-^<'
28   echo ''
29   echo 'Available targets:'
30   for target in ${targets}
31   do
32     echo "  ${target}"
33   done
34   exit 1
35 }
36
37 while getopts "a:c:b:t:r:o:i:p:w:l:h" opt
38 do
39   case "${opt}" in
40     a) arch="${OPTARG}" ;;
41     c) conf="${OPTARG}" ;;
42     b) base="${OPTARG}" ;;
43     t) tree="${OPTARG}" ;;
44     r) root="${OPTARG}" ;;
45     o) ovly="${OPTARG}" ;;
46     i) pkgs="${OPTARG}" ;;
47     p) ptch="${OPTARG}" ;;
48     d) cnfd="${OPTARG}" ;;
49     l) logf="${OPTARG}" ;;
50     h) pebkac ;;
51     [?]) pebkac "Unrecognized option ${opt}" ;;
52   esac
53 done
54 shift $(( $OPTIND - 1 ))
55
56 sequence="${*:-${targets}}"
57
58 base="${base:-$(dirname "${0}")/..}" #"
59 base="$(realpath "${base}")"
60
61 arch="${arch:-$(uname -m)}"
62 conf="${conf:-GENERIC}"
63
64 target="${base}/targets/${arch}/${conf}"
65
66 tree="${tree:-${target}/tree}"
67 root="${root:-${target}/world/root}"
68 ovly="${ovly:-${target}/config/overlay}"
69 pkgs="${pkgs:-${target}/pkg}"
70 ptch="${ptch:-${target}/config/patch}"
71 cnfd="${cnfd:-${target}/config/conf}"
72 logf="${logf:-${tree}/gentree.log}"
73
74 stage="${stage:-${base}/tree}"
75 pkgs="${pkgs:-${base}/pkg}"
76 patch="${patch:-${base}/patch}"
77 overlay="${overlay:-${base}/overlay}"
78 root="${root:-${base}/root}"
79 logfile="${logfile:-${stage}/gentree.log}"
80
81 mkdir -p "${tree}"
82 tree="$(realpath "${tree}")"
83 sroot="${tree}/root"
84 sboot="${tree}/boot"
85 sconf="${tree}/conf"
86
87 exec 2>>"${logf}"
88 _log_to_stderr=yes
89
90 # Helper functions
91 onelink() {
92   # Make sure the provided file(s) have only one link!
93   while [ -n "${1}" ]
94   do
95     if [ -f "${1}" -a "$(stat -f '%l' "${1}")" -gt 1 ]
96     then
97       cp -p "${1}" "${1}.tmp" && mv "${1}.tmp" "${1}" || err "breaklink failed"
98     fi
99     shift
100   done
101 }
102
103 # Build steps
104 do_prepwork() {
105   log Prepare workspace
106   if [ -d "${tree}" -a \( -d "${sroot}" -o -d "${sboot}" -o -d "${sconf}" \) ]
107   then
108     yn n " ${a_yellow}*${a_normal} Workspace already exists. Delete? [y/N]" || err Aborting
109     chk rm -Rf "${sroot}" "${sboot}" "${sconf}"
110   fi
111   chk mkdir -p "${sroot}"
112   # Eliminate schg, because it interferes with hardlinks
113   chk chflags -R noschg "${root}/lib"
114   chk chflags -R noschg "${root}/libexec"
115   chk chflags -R noschg "${root}/sbin"
116   chk chflags -R noschg "${root}/usr"
117   ( cd "${root}" && find . | cpio -p --link "${sroot}" ) || chk
118 }
119
120 do_admin() {
121   log Create an emergency user admin/admin
122   # delink passwd to ensure it doesn't get patched in-place
123   chk onelink "${sroot}/etc/passwd" "${sroot}/etc/master.passwd"
124   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
125 }
126
127 do_overlay() {
128   [ -d "${ovly}" ] || return
129   log Apply overlay from "${ovly##${base}/}"
130   ( cd "${ovly}" && find . | cpio -p "${sroot}" ) || chk
131 }
132
133 do_packages() {
134   [ -d "${pkgs}" ] || return
135   count="$(ls -1 "${pkgs}" | wc -l)"
136   [ "${count}" -gt 0 ] || return
137   log Install ${count} packages from "${pkgs##${base}/}"
138   chk mkdir -p "${sroot}/pkg"
139   ( cd "${pkgs}" && find . | cpio -p --link "${sroot}/pkg" ) || chk
140   chk chroot "${sroot}" /bin/sh -c 'cd /pkg; exec pkg_add -F *'
141   chk rm -Rf "${sroot}/pkg"
142 }
143
144 do_patch() {
145   [ -d "${ptch}" ] || return
146   log Apply patches from "${ptch##${base}/}"
147   for file in "${ptch}"/*
148   do
149     note "... $(basename "${file}")"
150     ( cd "${sroot}" && patch < "${file}" ) || chk
151     # Remove .orig files
152     sed -e '/^+++ /!d; s/^+++ //; s/[   ]*[0-9: .+-]*$//' "${file}" | while read orig
153     do
154       rm -f "${sroot}/${orig}.orig"
155     done || chk
156   done
157 }
158
159 do_prepboot() {
160   log Prepare /boot
161   chk mv "${sroot}/boot/boot" "${sroot}/boot/boot.blk"
162   chk ln -sf . "${sroot}/boot/boot"
163 }
164
165 do_preptmp() {
166   log Prepare /tmp
167   ( cd "${sroot}/tmp" && find . | cpio -p --link ../var/tmp ) || chk
168   chk rm -Rf "${sroot}/tmp"
169   chk ln -sf var/tmp "${sroot}/tmp"
170 }
171
172 do_prepetc() {
173   log Prepare /etc
174   chk mkdir -p "${sroot}/etc/local"
175   chk mkdir -p "${sroot}/usr/local/etc" # Silence warnings
176   ( cd "${sroot}/usr/local/etc" && find . | cpio -p --link ../../../etc/local ) || chk
177   chk rm -Rf "${sroot}/usr/local/etc"
178   chk ln -sf ../../etc/local "${sroot}/usr/local/etc"
179 }
180
181 do_imgboot() {
182   log Create boot imgsrc
183   chk mv "${sroot}/boot" "${tree}"
184   chk mkdir -p "${sroot}/boot"
185   chk rm -f "${sboot}/kernel"/*.symbols
186   # Gzipped kernel is okay
187   chk onelink "${sboot}/kernel/kernel"
188   chk gzip -9 "${sboot}/kernel/kernel"
189   # Compress all files in /boot/kernel
190   # Loader cannot handle gzipped modules. Decompress the required modules
191   # kldload cannot handle gzipped modules either
192   #find "${sboot}/kernel" -type f | xargs gzip -9f || chk
193   #cat "${sboot}/loader.conf" | grep '_load=' | sed -e 's/^\(.*\)_load=.*$/\1/' | while read mod
194   #do
195   #  [ -f "${sboot}/kernel/${mod}.ko.gz" ] && gunzip "${sboot}/kernel/${mod}.ko.gz" || chk
196   #done
197   #
198   # Instead: put all modules in the root image, except those needed to boot the kernel
199   chk mkdir -p "${sroot}/boot/boot"
200   chk mkdir -p "${sroot}/boot/kernel"
201   chk ln -sf "../etc/zfs" "${sroot}/boot/zfs"
202
203   # Link all modules into the root fs
204   ( cd "${sboot}/kernel" && find . -name '*.ko' -o -name 'linker.hints' | cpio -p --link "${sroot}/boot/kernel" ) || chk
205
206   # Remove all modules from the root fs that are preloaded by the loader
207   cat "${sboot}/loader.conf" | grep '_load=' | sed -e 's#^\(.*\)_load=.*$#'"${sroot}/boot/kernel/"'\1.ko#' | xargs rm -f
208
209   # Remove all modules from the boot fs that are present in the root fs
210   ( cd "${sroot}/boot/kernel" && ls -1 ) | sed -e 's#^#'"${sboot}/kernel/"'#' | xargs rm -f
211
212   # Link the preloaded modules from the boot fs to the root fs, to provide a homogenous view
213   ( cd "${sboot}/kernel" && ls -1 ) | while read mod
214   do
215     ln -sf "../boot/kernel/${mod}" "${sroot}/boot/kernel/${mod}"
216   done
217 }
218
219 do_imgconf() {
220   log Create conftree
221   chk mkdir -p "${sroot}/conf"
222   echo "ufs:/dev/ufs/conf" > "${sroot}/conf/diskless_remount" || chk
223   chk mkdir -p "${sconf}/backup"
224   chk mkdir -p "${sconf}/base"
225   chk mkdir -p "${sconf}/default"
226
227   # Create packdirs for each
228   for pack in "${cnfd}"/*.md_size
229   do
230     pack="$(basename "${pack%%.md_size}")"
231     chk mkdir -p "${sconf}/base/${pack}"
232     chk mkdir -p "${sconf}/default/${pack}"
233     ( cat "${cnfd}/${pack}.md_size" > "${sconf}/base/${pack}/md_size" ) || chk
234   done
235   chk chown -R :operator "${sconf}"
236   ( find "${sconf}" -print0 | xargs -0 chmod o-rwx ) || chk
237 }
238
239 do_imgetc() {
240   # etc requires special handling to ensure everything is properly arranged.
241   log Create etc confpack
242   chk touch "${sroot}/etc/diskless"
243   # Touch /COPYRIGHT to provide an immutable time anchor for saveconfig
244   chk touch "${sroot}/COPYRIGHT"
245   chk sleep 1 # Make sure anchor is at least one second older than everything in custom
246   chk mkdir -p "${tree}/pack"
247   chk mv "${sroot}/etc" "${tree}/pack"
248   chk mkdir -p "${sroot}/etc"
249   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"
250
251   # Make sure etc confpack exists; default if necessary
252   chk mkdir -p "${sconf}/base/etc"
253   chk mkdir -p "${sconf}/default/etc"
254   [ -e "${sconf}/base/etc/md_size" ] || echo "10240" > "${sconf}/base/etc/md_size"
255
256   ( cd "${tree}/pack" && find etc | cpio -o ) | gzip -9 > "${sconf}/base/etc.cpio.gz" || chk
257   chk rm -Rf "${tree}/pack/etc"
258 }
259
260 do_imgall() {
261   log Create remaining confpacks
262   chk mkdir -p "${tree}/pack"
263   for pack in "${cnfd}"/*.md_size
264   do
265     pack="$(basename "${pack%%.md_size}")"
266     # Ignore etc, as it was processed in do_imgetc
267     [ "${pack}" = "etc" ] && continue
268     note "... ${pack}"
269     chk mv "${sroot}/${pack}" "${tree}/pack"
270     chk mkdir -p "${sroot}/${pack}"
271     ( cd "${tree}/pack" && find "${pack}" | cpio -o ) | gzip -9 > "${sconf}/base/${pack}.cpio.gz" || chk
272     chk rm -Rf "${tree}/pack/${pack}"
273   done
274 }
275
276 do_custom() {
277   log Patch in custom config
278   ( cd "${cnfd}" && find . -name '*.md_size' -o -print | cpio -p --link "${sconf}/default" ) || chk
279   # Make sure files in default are newer than the tagfile, so they will be caught by saveconfig
280   find "${sconf}/default" -type f -print0 | xargs -0 touch
281   # If there are scavenged cpio.gz confpacks, touch their contents as well.
282   chk mkdir -p "${tree}/pack"
283   if ls -1 "${sconf}/default" | grep -q '.cpio.gz'
284   then
285     chk rm -Rf "${tree}/pack"/*
286     for file in "${sconf}/default"/*.cpio.gz
287     do
288       pack="$(basename "${file%%.cpio.gz}")"
289       zcat "${file}" | ( cd "${tree}/pack"; cpio -id ) || chk
290       find "${tree}/pack" -type f -print0 | xargs -0 touch
291       ( cd "${tree}/pack"; find . | cpio -o ) | gzip -9 > "${file}"
292       chk rm -Rf "${tree}/pack"/*
293     done
294   fi
295 }
296
297 for step in ${sequence}
298 do
299   echo "${targets}" | grep -q "${step}" || err Unrecognized target "${step}"
300   do_${step}
301 done