]> CyberLeo.Net >> Repos - CDN/Mosi.git/blob - script/gentree
script/gentree: use fakeroot for overlay to avoid weird ownership creeping into firmware
[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 whiteout scripts 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 '  -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! >^-^<'
30   echo ''
31   echo 'Available targets:'
32   for target in ${targets}
33   do
34     echo "  ${target}"
35   done
36   exit 1
37 }
38
39 while getopts "a:c:b:t:r:o:i:p:w:s:l:h" opt
40 do
41   case "${opt}" in
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}" ;;
54     h) pebkac ;;
55     [?]) pebkac "Unrecognized option ${opt}" ;;
56   esac
57 done
58 shift $(( $OPTIND - 1 ))
59
60 sequence="${*:-${targets}}"
61
62 base="${base:-$(dirname "${0}")/..}" #"
63 base="$(realpath "${base}")"
64
65 arch="${arch:-$(uname -m)}"
66 conf="${conf:-GENERIC}"
67
68 target="${base}/targets/${arch}/${conf}"
69
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}"
79
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}"
86
87 mkdir -p "${tree}"
88 tree="$(realpath "${tree}")"
89 sroot="${tree}/root"
90 sboot="${tree}/boot"
91 sconf="${tree}/conf"
92
93 exec 2>>"${logf}"
94 _log_to_stderr=yes
95 meh "Logging to ${logf}"
96
97 # Helper functions
98 onelink() {
99   # Make sure the provided file(s) have only one link!
100   while [ -n "${1}" ]
101   do
102     if [ -f "${1}" -a "$(stat -f '%l' "${1}")" -gt 1 ]
103     then
104       cp -p "${1}" "${1}.tmp" && mv "${1}.tmp" "${1}" || err "breaklink failed"
105     fi
106     shift
107   done
108 }
109
110 fakeroot() {
111   for fakeroot in $(which fakeroot) /usr/local/bin/fakeroot
112   do
113     [ -x "${fakeroot}" ] && break
114     unset fakeroot
115   done
116
117   [ -x "${fakeroot}" ] || warn "security/fakeroot not found! Expect weird ownership from overlay"
118   "${fakeroot}" "${@}"
119 }
120
121 # Build steps
122 do_prepwork() {
123   log Prepare workspace
124   if [ -d "${tree}" -a \( -d "${sroot}" -o -d "${sboot}" -o -d "${sconf}" \) ]
125   then
126     yn n " ${a_yellow}*${a_normal} Workspace already exists. Delete? [y/N]" || err Aborting
127     chk rm -Rf "${sroot}" "${sboot}" "${sconf}"
128   fi
129   chk mkdir -p "${sroot}"
130   # Eliminate schg, because it interferes with hardlinks
131   chk chflags -R noschg "${root}/lib"
132   chk chflags -R noschg "${root}/libexec"
133   chk chflags -R noschg "${root}/sbin"
134   chk chflags -R noschg "${root}/usr"
135   ( cd "${root}" && find . | cpio -p --link "${sroot}" ) || chk
136 }
137
138 do_admin() {
139   log Create an emergency user admin/admin
140   # delink passwd to ensure it doesn't get patched in-place
141   chk onelink "${sroot}/etc/passwd" "${sroot}/etc/master.passwd"
142   echo '$1$2rXOWsK/$eiBHA6K7xL96DZbcY24YR0' | chk /usr/sbin/pw -V "${sroot}/etc" useradd admin -u 999 -g wheel -G operator -c Administrator -d /usr/home/admin -m -k "${sroot}/usr/share/skel" -s /bin/csh -H 0
143 }
144
145 do_overlay() {
146   [ -d "${ovly}" ] || return
147   log Apply overlay from "${ovly##${base}/}"
148   ( cd "${ovly}" && find . | fakeroot cpio -p "${sroot}" ) || chk
149 }
150
151 do_packages() {
152   [ -d "${pkgs}" ] || return
153   count="$(ls -1 "${pkgs}" | wc -l)"
154   [ "${count}" -gt 0 ] || return
155   log Install ${count} packages from "${pkgs##${base}/}"
156   chk mkdir -p "${sroot}/pkg"
157   ( cd "${pkgs}" && find . | cpio -p --link "${sroot}/pkg" ) || chk
158   chk chroot "${sroot}" /bin/sh -c 'cd /pkg; exec pkg_add -F *'
159   chk rm -Rf "${sroot}/pkg"
160 }
161
162 do_patch() {
163   [ -d "${ptch}" ] || return
164   log Apply patches from "${ptch##${base}/}"
165   for file in "${ptch}"/*
166   do
167     note "... $(basename "${file}")"
168     ( cd "${sroot}" && patch < "${file}" ) || chk
169     # Remove .orig files
170     sed -e '/^+++ /!d; s/^+++ //; s/[   ]*[0-9: .+-]*$//' "${file}" | while read orig
171     do
172       rm -f "${sroot}/${orig}.orig"
173     done || chk
174   done
175 }
176
177 do_whiteout() {
178   [ -f "${rmrf}" ] || return
179   log Whiteout files from "${rmrf##${base}/}"
180   while read entry < "${rmrf}"
181   do
182     # Strip off terminating slash
183     entry="${entry%%/}"
184     # Obtain directory name, for path resolution
185     entry_path="$(dirname "${entry}")"
186     entry_path="$(realpath "${sroot}/${entry_path}" 2>/dev/null)"
187     entry_file="$(basename "${entry}")"
188
189     # Ignore paths that do not exist
190     [ "${entry_path}" ] || continue
191
192     # Warn and ignore paths that fall outside ${sroot} after resolution
193     if [ "$(echo ${entry_path} | sed -e "s/^\(.\{${#sroot}\}\).*$/\1/")" != "${sroot}" ]
194     then
195       warn "Whiteout '${entry}' malformed: leads outside '${sroot}'"
196       continue
197     fi
198
199     # Warn and ignore paths that cannot be chdir()'d
200     if [ ! -d "${entry_path}" ]
201     then
202       warn "Whiteout '${entry}' malformed: non-directory in path"
203       continue
204     fi
205
206     ( cd "${entry_path}"
207       if [ -d "${entry}" ]
208       then
209         echo rm -Rf "${entry}"
210       else
211         echo rm -f "${entry}"
212       fi
213     )
214   done
215 }
216
217 # Run arbitrary user scripts to perform additional functions prior to carving and packaging
218 # Warning! These run as root and no chroot is performed! BE CAREFUL HERE!
219 do_scripts() {
220   [ -d "${scpt}" ] && ls -1 "${scpt}" | grep -q '.' || return
221   log Run user scripts from "${scpt##${base}/}"
222   ls -1 "${scpt}" | LANG=C sort | while read file
223   do
224     [ -f "${scpt}/${file}" ] || continue
225     log "... ${file}"
226     ( cd "${sroot}"
227       . "${scpt}/${file}"
228     ) || chk
229   done
230 }
231
232 do_prepboot() {
233   log Prepare /boot
234   chk mv "${sroot}/boot/boot" "${sroot}/boot/boot.blk"
235   chk ln -sf . "${sroot}/boot/boot"
236
237   # Move /boot/zfs to /etc/zfs and symlink
238   ls -1 "${sroot}/boot/zfs"/* 2>&- && chk mv "${sroot}/boot/zfs"/* "${sroot}/etc/zfs/"
239   chk rmdir "${sroot}/boot/zfs"
240   chk ln -svf ../etc/zfs "${sroot}/boot/zfs"
241 }
242
243 do_preptmp() {
244   log Prepare /tmp
245   ( cd "${sroot}/tmp" && find . | cpio -p --link ../var/tmp ) || chk
246   chk rm -Rf "${sroot}/tmp"
247   chk ln -sf var/tmp "${sroot}/tmp"
248 }
249
250 do_prepetc() {
251   log Prepare /etc
252   chk mkdir -p "${sroot}/etc/local"
253   chk mkdir -p "${sroot}/usr/local/etc" # Silence warnings
254   ( cd "${sroot}/usr/local/etc" && find . | cpio -p --link ../../../etc/local ) || chk
255   chk rm -Rf "${sroot}/usr/local/etc"
256   chk ln -sf ../../etc/local "${sroot}/usr/local/etc"
257 }
258
259 do_imgboot() {
260   log Create boot imgsrc
261   chk mv "${sroot}/boot" "${tree}"
262   chk mkdir -p "${sroot}/boot"
263   chk mkdir -p "${sroot}/modules"
264   chk find "${sboot}/kernel" -type f -name '*.symbols' -delete
265
266   # Link all modules into /modules, as an alternative search path
267   ( cd "${sboot}/kernel" && find . -name '*.ko' -o -name "${conf}" | cpio -p --link "${sroot}/modules" ) || chk
268   ( cd "${sboot}/modules" && find . -name '*.ko' -o -name "${conf}" | cpio -p --link "${sroot}/modules" ) || chk
269
270   # Remove all modules from the root fs that are preloaded by the preloader
271   [ ! -f "${sboot}/loader.conf" ] || cat "${sboot}/loader.conf" | grep '_load=' | \
272     sed -e 's#^\(.*\)_load=.*$#'"${sroot}/modules/"'\1.ko#' | xargs rm -f
273
274   # Remove all modules from the boot fs that are present in the root fs
275   ( cd "${sroot}/modules" && ls -1 ) | sed -e 's#^#'"${sboot}/kernel/"'#' | xargs rm -f
276   ( cd "${sroot}/modules" && ls -1 ) | sed -e 's#^#'"${sboot}/modules/"'#' | xargs rm -f
277
278   # Rebuild linker hints in both places, to make sure the kernel can find everything once running
279   kldxref "${sboot}/kernel" "${sboot}/modules" "${sroot}/modules"
280
281   # Gzip kernel to save boot space
282   chk onelink "${sboot}/kernel/kernel"
283   chk gzip -9 "${sboot}/kernel/kernel"
284
285   # Compress all the modules as well, since /loader can handle them; kldload cannot,
286   # but I think I can safely assume you will not be kldunloading boot-loaded modules
287   ls -1 "${sboot}/kernel/"*.ko "${sboot}/modules/"*.ko 2>/dev/null | while read kmod
288   do
289     chk onelink "${kmod}"
290     chk gzip -9 "${kmod}"
291   done
292
293   # Make sure /boot/zfs -> /etc/zfs symlink exists even without /boot mounted
294   [ -L "${sroot}/boot/zfs" ] || chk ln -sf ../etc/zfs "${sroot}/boot/zfs"
295 }
296
297 do_imgconf() {
298   log Create conftree
299   chk mkdir -p "${sroot}/conf"
300   echo "ufs:/dev/ufs/conf" > "${sroot}/conf/diskless_remount" || chk
301   chk mkdir -p "${sconf}/backup"
302   chk mkdir -p "${sconf}/base"
303   chk mkdir -p "${sconf}/default"
304
305   # Create packdirs for each
306   for pack in "${cnfd}"/*.md_size
307   do
308     pack="$(basename "${pack%%.md_size}")"
309     chk mkdir -p "${sconf}/base/${pack}"
310     chk mkdir -p "${sconf}/default/${pack}"
311     ( cat "${cnfd}/${pack}.md_size" > "${sconf}/base/${pack}/md_size" ) || chk
312   done
313   chk chown -R :operator "${sconf}"
314   ( find "${sconf}" -print0 | xargs -0 chmod o-rwx ) || chk
315 }
316
317 do_imgetc() {
318   # etc requires special handling to ensure everything is properly arranged.
319   log Create etc confpack
320   chk touch "${sroot}/etc/diskless"
321   # Touch /COPYRIGHT to provide an immutable time anchor for saveconfig
322   chk touch "${sroot}/COPYRIGHT"
323   chk sleep 1 # Make sure anchor is at least one second older than everything in custom
324   chk mkdir -p "${tree}/pack"
325   chk mv "${sroot}/etc" "${tree}/pack"
326   chk mkdir -p "${sroot}/etc"
327   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"
328
329   # Make sure etc confpack exists; default if necessary
330   chk mkdir -p "${sconf}/base/etc"
331   chk mkdir -p "${sconf}/default/etc"
332   [ -e "${sconf}/base/etc/md_size" ] || echo "10240" > "${sconf}/base/etc/md_size"
333
334   ( cd "${tree}/pack" && find etc | cpio -o ) | gzip -9 > "${sconf}/base/etc.cpio.gz" || chk
335   chk rm -Rf "${tree}/pack"
336 }
337
338 do_imgall() {
339   log Create remaining confpacks
340   chk mkdir -p "${tree}/pack"
341   for pack in "${cnfd}"/*.md_size
342   do
343     pack="$(basename "${pack%%.md_size}")"
344     # Ignore etc, as it was processed in do_imgetc
345     [ "${pack}" = "etc" ] && continue
346     note "... ${pack}"
347     chk mv "${sroot}/${pack}" "${tree}/pack"
348     chk mkdir -p "${sroot}/${pack}"
349     ( cd "${tree}/pack" && find "${pack}" | cpio -o ) | gzip -9 > "${sconf}/base/${pack}.cpio.gz" || chk
350     rm -Rf "${tree}/pack/${pack}" || chflags -R noschg "${tree}/pack/${pack}" && rm -Rf "${tree}/pack/${pack}" || chk
351   done
352   chk rm -Rf "${tree}/pack"
353 }
354
355 do_custom() {
356   log Patch in custom config
357   ( cd "${cnfd}" && find . -name '*.md_size' -o -print | cpio -p --link "${sconf}/default" ) || chk
358   # Make sure files in default are newer than the tagfile, so they will be caught by saveconfig
359   find "${sconf}/default" -type f -print0 | xargs -0 touch
360   # If there are scavenged cpio.gz confpacks, touch their contents as well.
361   chk mkdir -p "${tree}/pack"
362   if ls -1 "${sconf}/default" | grep -q '.cpio.gz'
363   then
364     chk rm -Rf "${tree}/pack"/*
365     for file in "${sconf}/default"/*.cpio.gz
366     do
367       pack="$(basename "${file%%.cpio.gz}")"
368       zcat "${file}" | ( cd "${tree}/pack"; cpio -id ) || chk
369       find "${tree}/pack" -type f -print0 | xargs -0 touch
370       ( cd "${tree}/pack"; find . | cpio -o ) | gzip -9 > "${file}"
371       chk rm -Rf "${tree}/pack"/*
372     done
373   fi
374   chk rm -Rf "${tree}/pack"
375 }
376
377 for step in ${sequence}
378 do
379   echo "${targets}" | grep -q "${step}" || err Unrecognized target "${step}"
380   do_${step}
381 done