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