record bug numbers for some outstanding issues
[shellsnippets/shellsnippets.git] / posix / mkrpi3b+img.sh
1 #!/bin/sh
2 #-
3 # Copyright © 2019
4 #       mirabilos <t.glaser@tarent.de>
5 #
6 # Provided that these terms and disclaimer and all copyright notices
7 # are retained or reproduced in an accompanying document, permission
8 # is granted to deal in this work without restriction, including un‐
9 # limited rights to use, publicly perform, distribute, sell, modify,
10 # merge, give away, or sublicence.
11 #
12 # This work is provided “AS IS” and WITHOUT WARRANTY of any kind, to
13 # the utmost extent permitted by applicable law, neither express nor
14 # implied; without malicious intent or gross negligence. In no event
15 # may a licensor, author or contributor be held liable for indirect,
16 # direct, other damage, loss, or other issues arising in any way out
17 # of dealing in the work, even if advised of the possibility of such
18 # damage or existence of a defect, except proven that it results out
19 # of said person’s immediate fault when using the work as intended.
20 #-
21 # Installs a Raspberry Pi 3B+ image from scratch (tested on a Debian
22 # bullseye/sid host system, others should work as well given suitab‐
23 # ly up-to-date tools), using qemu-user/binfmt_misc emulation to run
24 # the foreign architecture steps in a chroot.
25
26 #########
27 # SETUP #
28 #########
29
30 ht='    '
31 nl='
32 '
33 IFS=" $ht$nl"
34 POSIXLY_CORRECT=1
35 export POSIXLY_CORRECT
36 LANGUAGE=C
37 LC_ALL=C.UTF-8
38 export LC_ALL
39 unset LANGUAGE
40 safe_PATH=/bin:/sbin:/usr/bin:/usr/sbin
41 PATH=$PATH:$safe_PATH  # just to make sure
42 export safe_PATH PATH
43
44 needprintf=
45 if test -n "$KSH_VERSION"; then
46         p() {
47                 typeset i
48                 for i in "$@"; do
49                         print -ru2 -- "$i"
50                 done
51         }
52 elif test x"$(printf '%s\n' 'a b' c 2>/dev/null)" = x"a b${nl}c"; then
53         p() {
54                 printf '%s\n' "$@" >&2
55         }
56 else
57         needprintf=y
58         p() {
59                 for p_arg in "$@"; do
60                         echo "$p_arg" >&2
61                 done
62         }
63 fi
64
65 ################################
66 # ERROR HANDLING AND UNWINDING #
67 ################################
68
69 T=
70 loopdev=
71 kpx=
72 mpt=
73 dieteardown() {
74         set -x
75         if test -n "$mpt"; then
76                 umount "$mpt/tmp"
77                 umount "$mpt/proc"
78                 umount "$mpt/dev/shm"
79                 umount "$mpt/dev/pts"
80                 umount "$mpt/boot/firmware"
81                 umount "$mpt"
82         fi
83         mpt=
84         if test -n "$kpx"; then
85                 kpartx -d -f -v -p p -t dos -s "$dvname"
86         fi
87         kpx=
88         if test -n "$loopdev"; then
89                 losetup -d "$loopdev"
90         fi
91         loopdev=
92 }
93 diecleanup() {
94         dieteardown
95         if test -n "$T"; then
96                 cd /
97                 rm -rf --one-file-system "$T"
98         fi
99         T=
100 }
101 die() {
102         pfx='E: '
103         for arg in "$@"; do
104                 p "$pfx$arg"
105                 pfx='N: '
106         done
107         diecleanup
108         exit 1
109 }
110 dieif() {
111         dieif_reason=$1; shift
112         "$@" || die "$dieif_reason"
113 }
114 trap 'p "I: exiting, cleaning up…"; diecleanup; exit 0' EXIT
115 trap 'p "E: caught SIGHUP, cleaning up…"; diecleanup; exit 129' HUP
116 trap 'p "E: caught SIGINT, cleaning up…"; diecleanup; exit 130' INT
117 trap 'p "E: caught SIGQUIT, cleaning up…"; diecleanup; exit 131' QUIT
118 trap 'p "E: caught SIGPIPE, cleaning up…"; diecleanup; exit 141' PIPE
119 trap 'p "E: caught SIGTERM, cleaning up…"; diecleanup; exit 143' TERM
120
121 #########################
122 # PREREQUISITE CHECKING #
123 #########################
124
125 case $TERM in
126 (Eterm|Eterm-color|ansi|cons25|cons25-debian|cygwin|dumb|hurd|linux|mach|mach-bold|mach-color|mach-gnu|mach-gnu-color|pcansi|rxvt|rxvt-basic|rxvt-m|rxvt-unicode|rxvt-unicode-256color|screen|screen-256color|screen-256color-bce|screen-bce|screen-s|screen-w|screen.xterm-256color|sun|vt100|vt102|vt220|vt52|wsvt25|wsvt25m|xterm|xterm-256color|xterm-color|xterm-debian|xterm-mono|xterm-r5|xterm-r6|xterm-vt220|xterm-xfree86)
127         # list from ncurses-base (6.1+20181013-2+deb10u1)
128         ;;
129 (screen.*|screen-*)
130         # aliases possibly from ncurses-term
131         TERM=screen ;;
132 (rxvt.*|rxvt-*)
133         # let’s hope…
134         TERM=rxvt ;;
135 (xterm.*|xterm-*)
136         # …this works…
137         TERM=xterm ;;
138 (linux.*)
139         # …probably
140         TERM=linux ;;
141 (*)
142         die "Your terminal type '$TERM' is not supported by ncurses-base." \
143             'Maybe run this script in GNU screen?' ;;
144 esac
145
146 rv=0
147 chkhosttool() {
148         chkhosttool_prog=$1; shift
149         chkhosttool_missing=0
150         while test $# -gt 0; do
151                 test -x "$1" || {
152                         test $chkhosttool_missing = 1 || \
153                             p "E: please install $chkhosttool_prog to continue!"
154                         chkhosttool_missing=1
155                         p "N: missing: $1"
156                 }
157                 shift
158         done
159         test $chkhosttool_missing = 0 || rv=1
160 }
161 chkhosttool bc /usr/bin/bc
162 chkhosttool binfmt-support /usr/sbin/update-binfmts
163 chkhosttool coreutils /bin/cat /bin/chmod /bin/chown /bin/cp /bin/dd \
164     /bin/ln /bin/mkdir /bin/mktemp /bin/rm /bin/stty /usr/bin/env \
165     /usr/bin/id /usr/bin/printf /usr/bin/truncate /usr/sbin/chroot
166 chkhosttool debootstrap /usr/sbin/debootstrap
167 chkhosttool dosfstools /sbin/mkfs.msdos
168 chkhosttool dpkg /usr/bin/dpkg-deb
169 chkhosttool e2fsprogs /sbin/mkfs.ext4
170 chkhosttool eatmydata /usr/bin/eatmydata
171 chkhosttool fdisk /sbin/fdisk
172 chkhosttool kpartx /sbin/kpartx
173 chkhosttool mount /bin/mount /bin/umount /sbin/losetup
174 chkhosttool qemu-user-static /usr/bin/qemu-aarch64-static
175 chkhosttool util-linux /bin/lsblk /sbin/fstrim /usr/bin/unshare
176 chkhosttool whiptail /usr/bin/whiptail
177 unset chkhosttool_prog
178 unset chkhosttool_missing
179 test x"$rv" = x"0" || exit "$rv"
180 if test -n "$needprintf"; then
181         p() {
182                 printf '%s\n' "$@" >&2
183         }
184         unset p_arg
185 fi
186 unset needprintf
187
188 case $(id -u) in
189 (0) ;;
190 (*) die 'Please run this as root.' ;;
191 esac
192
193 T=$(mktemp -d /tmp/mkrpi3b+img.XXXXXXXXXX) || \
194     die 'cannot create temporary directory'
195 case $T in
196 (/*) ;;
197 (*) die "non-absolute temporary directory: $T" ;;
198 esac
199 chmod 700 "$T" || die 'chmod failed'
200 cd "$T" || die 'cannot cd into temporary directory'
201
202 #########################
203 # DIALOGUE PREPARATIONS #
204 #########################
205
206 # assign tgtvar fallback glob
207 assign() {
208         assign_tgt=$1; shift
209         assign_nil=$1; shift
210         eval "$assign_tgt=\$assign_nil"
211         test -n "$1" && test -e "$1" || return 0
212         eval "$assign_tgt=\$*"
213 }
214
215 w() {
216         whiptail --backtitle 'mkrpi3b+img.sh' --output-fd 4 4>res "$@"
217         rv=$?
218         res=$(cat res) || die cannot read whiptail result file
219         return $rv
220 }
221 dw() {
222         if w "$@"; then
223                 s=$(($s+1))
224         elif test x"$s" = x"0"; then
225                 p '' 'I: aborted by user'
226                 diecleanup
227                 exit $rv
228         else
229                 s=$(($s-1))
230         fi
231         return $rv
232 }
233
234 ################################
235 # ENSURE MINIMUM TERMINAL SIZE #
236 ################################
237
238 s=0
239 while test x"$s" != x"9"; do
240         set -- $(stty size) || die 'stty failed'
241         test $# -eq 2 || die 'stty weird output' "$@"
242         case "$*" in
243         (*[!\ 0-9]*) die 'stty invalid output' "$@" ;;
244         esac
245         case $s in
246         (0)
247                 #### INITIAL TERMINAL SIZE CHECK
248                 s=4
249                 test $1 -ge 24 || s=1
250                 test $2 -ge 80 || s=1
251                 ;;
252         (1)
253                 #### TTY TOO SMALL REQUEST CHANGE
254                 p 'E: tty size too small' \
255                   "N: ${2}x$1 actual" "N: 80x24 minimum"
256                 sleep 5
257                 s=2
258                 ;;
259         (2)
260                 #### SEE WHETHER THAT HELPED
261                 s=4
262                 test $1 -ge 24 || s=3
263                 test $2 -ge 80 || s=3
264                 ;;
265         (3)
266                 #### STILL REQUEST CHANGE
267                 w --title 'Terminal size' --msgbox \
268                     "Your terminal is too small (only ${2}x$1 glyphs).
269
270 Please resize your terminal to at least 80x24 now, then press Enter to continue. Afterwards, DO *NOT* change the terminal size again! APT and debconf really do not like that, and your display will be garbled if you do…
271
272 Change size now, press Enter only afterwards to continue." 14 72
273                 s=5
274                 ;;
275         (4)
276                 #### INITIAL TTY SIZE OK
277                 w --title 'Terminal size' --msgbox \
278                     "Your terminal size is okay: ${2}x$1 glyphs, minimum 80x24 required.
279
280 Please DO *NOT* change the terminal size for the entire runtime of this script, starting now! APT and debconf really do not like that, and your display will be garbled if you do…
281
282 Press Enter to continue." 14 72
283                 s=5
284                 ;;
285         (5)
286                 #### SEE THAT WE END UP AT CORRECT SIZE
287                 s=9
288                 test $1 -ge 24 || s=6
289                 test $2 -ge 80 || s=6
290                 ;;
291         (6)
292                 #### NOT GOOD ENOUGH
293                 if w --title 'Terminal size' --yes-button OK --no-button Exit \
294                     --yesno "Your terminal still is too small (${2}x$1).
295
296 We requested you to change it to at least 80x24 (and keep the size constant afterwards). Please do so now, or press Escape or use the Exit button if you really can’t.
297
298 Remember to *NOT* change the size afterwards, since APT and debconf cache it and do not like sudden changes below them; if you do, your display will be garbled…
299
300 Change size now, press Enter only afterwards to continue." 17 72; then
301                         s=5
302                 else
303                         p '' 'I: aborted by user'
304                         exit 0
305                 fi
306                 ;;
307         esac
308 done
309
310 #################
311 # DIALOGUE LOOP #
312 #################
313
314 assign devices '' /dev/sd[a-z]
315 tgtdev=MANUAL
316 tgtimg=/dev/sdX
317 myfqdn=rpi3bplus.lan.tarent.invalid
318 userid=pi
319 setcma=
320 dropsd=--defaultno
321 pkgadd=-
322 s=0
323 while test x"$s" != x"9"; do
324         case $s in
325         (0)
326                 #### WHICH TARGET DEVICE? (CHOICE)
327                 set --
328                 for x in $devices; do
329                         set -- "$@" "$x" "$x"
330                 done
331                 if dw --title 'Select target device' \
332                     --notags --default-item "$tgtdev" \
333                     --menu 'Select which device to write to. IT WILL BE OVERWRITTEN!' \
334                     20 72 10 MANUAL 'Enter path manually (/dev/XXX or image file)' "$@"; then
335                         tgtdev=$res
336                         if test x"$tgtdev" = x"MANUAL"; then
337                                 tgtimg=/dev/sdX
338                         else
339                                 tgtimg=$tgtdev
340                                 s=$(($s+1))
341                         fi
342                 fi
343                 ;;
344         (1)
345                 #### WHICH TARGET DEVICE OR IMAGE? (FREETEXT)
346                 if dw --title 'Choose target device' \
347                     --inputbox 'Enter path to target raw device (e.g. /dev/sdX) or to pre-existing, already correctly-sized, image file to use:' \
348                     20 72 "$tgtimg"; then
349                         tgtimg=$res
350                 fi
351                 ;;
352         (2)
353                 #### VALIDATE IMAGE/DEVICE PATH/SIZE/ETC. / CREATE SPARSE FILE
354                 # step to go back to if things fail
355                 if test x"$tgtdev" = x"MANUAL"; then
356                         s=1
357                 else
358                         s=0
359                 fi
360                 # check image/device: path, existence, not a symlink (for stability)
361                 case $tgtimg in
362                 (/[!/]*) ;;
363                 (*)
364                         w --msgbox 'The chosen device/image path is not an absolute pathname!' 8 72
365                         continue ;;
366                 esac
367                 test -e "$tgtimg" || if w --title 'Nonexistent path chosen' \
368                     --ok-button 'Create' --cancel-button 'Go back' \
369                     --inputbox 'The chosen device/image path does not exist!
370
371 If you wish to create it, enter a size in the format accepted by GNU coreutils’ truncate(1) utility (that is, a number followed by M for MiB or G for GiB, in its most basic form) and select the “Create” button (or just press Enter). This will create a sparse image file (which will only take up the space actually used by data). We have pre-filled the input field with the minimum allowed image size.
372
373 The image will be created *immediately* and never deleted!
374
375 If you do not with to create it, press Escape to go back instead.' \
376                     20 72 1536M; then
377                         truncate -s "$res" "$tgtimg" || \
378                             die 'failed to create sparse file'
379                         test -e "$tgtimg" || die 'sparse file not created'
380                 else
381                         continue  # go back
382                 fi
383                 if test -h "$tgtimg"; then
384                         tgtimg=$(readlink -f "$tgtimg") || die 'error in readlink -f'
385                         s=2 # redo from start
386                         continue
387                 fi
388                 # whether block device, regular file or grounds for refusal
389                 if test -b "$tgtimg"; then
390                         dvname=$tgtimg
391                 elif test -f "$tgtimg"; then
392                         dvname=$(losetup -f) || die 'losetup failed in get'
393                         case $dvname in
394                         (/dev/loop*) ;;
395                         (*) die 'losetup shows weird result' "$dvname" ;;
396                         esac
397                         loopdev=$dvname
398                         losetup "$loopdev" "$tgtimg" || die 'losetup failed in set'
399                 else
400                         w --msgbox 'The chosen device/image path is neither a block special device nor a regular file!' 8 72
401                         continue
402                 fi
403                 # we now have a block (or loopback device) we can check
404                 sz=$(lsblk --nodeps --noheadings --output SIZE --bytes \
405                     --raw "$dvname") || die 'lsblk failed'
406                 case $sz in
407                 (*[!0-9]*) die 'lsblk shows weird result' "$sz" ;;
408                 ([0-9]*) ;;
409                 (*) die 'lsblk returned empty result' ;;
410                 esac
411                 case $(echo "a=0; if($sz<(1536*1048576)) a=1; a" | bc) in
412                 (0) ;;
413                 (1)
414                         w --msgbox 'The chosen device/image path is smaller than 1536 MiB!' 8 72
415                         dieteardown
416                         continue ;;
417                 (*) die 'bc returned weird result' ;;
418                 esac
419                 # and it’s big enough for the Debian installation; accept
420                 s=2
421                 sz=$(echo "scale=2; $sz/1048576" | bc) || die 'bc cannot divide'
422                 dw --title 'Accept target device' \
423                     --yesno "Your chosen target device: $tgtimg
424
425 Block device $dvname of size $sz MiB
426
427 Do you REALLY want to use this as target device
428 and OVERWRITE ALL DATA with no chance of recovery?" 14 72
429                 ;;
430         (3)
431                 #### HOSTNAME FOR THE SYSTEM
432                 if dw --title 'Enter target hostname' \
433                     --inputbox 'Enter fully-qualified hostname the target device should have:' \
434                     20 72 "$myfqdn"; then
435                         # one trailing full stop is allowed (like DNS)
436                         myfqdn=${res%.}
437                         # check length [1; 255]
438                         if test "${#myfqdn}" -lt 1; then
439                                 w --msgbox 'The given hostname is empty!' 8 72
440                                 s=3; continue  # retry
441                         fi
442                         if test "${#myfqdn}" -gt 255; then
443                                 w --msgbox 'The given hostname is too long!' 8 72
444                                 s=3; continue  # retry
445                         fi
446                         # check characters used
447                         case $myfqdn in
448                         (.*|*.)
449                                 w --msgbox 'The given hostname begins or ends with a full stop!' 8 72
450                                 s=3; continue ;;
451                         (*[!.0-9A-Za-z-]*)
452                                 w --msgbox 'The given hostname contains invalid characters!' 8 72
453                                 s=3; continue ;;
454                         esac
455                         # similar for the component labels
456                         IFS=.; set -o noglob
457                         set -- $myfqdn
458                         IFS=" $ht$nl"; set +o noglob
459                         for x in "$@"; do
460                                 if test "${#x}" -lt 1 || test "${#x}" -gt 63; then
461                                         w --msgbox 'The given hostname contains parts that are empty or too long!' 8 72
462                                         s=3; break
463                                 fi
464                                 # invalid label composition
465                                 case $x in
466                                 (-*)
467                                         w --msgbox 'The given hostname contains parts that begin with a hyphen-minus!' 8 72
468                                         s=3; break ;;
469                                 (*-)
470                                         w --msgbox 'The given hostname contains parts that end with a hyphen-minus!' 8 72
471                                         s=3; break ;;
472                                 (*[!0-9A-Za-z-]*)
473                                         w --msgbox 'The given hostname contains invalid characters!' 8 72
474                                         s=3; break ;;
475                                 esac
476                         done
477                         # s is now 3=redo (msgbox shown) or 4=go on
478                 fi
479                 ;;
480         (4)
481                 #### USERNAME TO CREATE FOR INITIAL SSH AND SUDO
482                 if dw --title 'Enter initial username' \
483                     --inputbox 'Enter UNIX username of the initially created user (which has full sudo access):' \
484                     20 72 "$userid"; then
485                         userid=$res
486                         # Unix limitations
487                         if test -z "$userid"; then
488                                 w --msgbox 'The given username is empty!' 8 72
489                                 s=4; continue  # retry
490                         fi
491                         if test "${#userid}" -gt 32; then
492                                 w --msgbox 'The given username is too long! (32 bytes max.)' 8 72
493                                 s=4; continue  # retry
494                         fi
495                         # default /etc/adduser.conf NAME_REGEX
496                         case $userid in
497                         (*[!a-z0-9_-]*)
498                                 w --msgbox 'The given username contains invalid characters!' 8 72
499                                 s=4; continue ;;
500                         ([!a-z]*)
501                                 w --msgbox 'The given username does not start with a letter!' 8 72
502                                 s=4; continue ;;
503                         esac
504                 fi
505                 ;;
506         (5)
507                 #### ADJUST DEFAULT CMA SIZE?
508                 if w --title 'Default CMA size' \
509                     $setcma --yesno "Raise default CMA from 64 to 128 MiB?
510
511 This is especially useful when you’ll be using graphics." 10 72; then
512                         setcma=
513                         s=6
514                 elif test x"$rv" = x"1"; then
515                         setcma=--defaultno
516                         s=6
517                 else
518                         s=4
519                 fi
520                 ;;
521         (6)
522                 #### SELECT INIT SYSTEM
523                 if w --title 'Choose the init system' \
524                     $dropsd --yesno "Change init system from systemd to sysvinit?
525
526 The default init system in Debian 10 “buster” is systemd with usrmerge.
527 This option allows you to change to traditional SysV init with classic
528 filesystem layout.
529
530 Most users will say “No” here." 10 72; then
531                         dropsd=
532                         s=7
533                 elif test x"$rv" = x"1"; then
534                         dropsd=--defaultno
535                         s=7
536                 else
537                         s=5
538                 fi
539                 ;;
540         (7)
541                 #### EXTRA PACKAGES TO INSTALL
542                 if test x"$pkgadd" = x"-"; then
543                         pkgadd='anacron bind9-host bridge-utils postfix bsd-mailx curl etckeeper ethtool ntp openssh-server patch pv rdate reportbug unscd wget _WLAN_'
544                         blurb=' We have provided you with a selection of default useful system utilities and services, which you can change if you wish, of course.'
545                 else
546                         blurb=
547                 fi
548                 if dw --title 'Extra packages to install' \
549                     --inputbox "Enter extra packages to install, separated by space.$blurb Some other extra packages, like less and sudo, are always installed.
550
551 You can use _WLAN_ to select packages needed to support WiFi. Press ^U (Ctrl-U) to delete the entire line; enter just - to restore the default and edit anew." \
552                     20 72 "$pkgadd"; then
553                         pkgadd=$res
554                         test x"$pkgadd" = x"-" && s=7
555                 fi
556                 ;;
557         (8)
558                 #### SUMMARY BEFORE DOING ANYTHING (except sparse file creation)
559                 if test -n "$setcma"; then cma=64; else cma=128; fi
560                 if test -n "$dropsd"; then
561                         init='systemd with usrmerge'
562                 else
563                         init='sysvinit with standard filesystem'
564                 fi
565                 dw --title 'Proceed with installation?' --defaultno \
566                     --yesno "Do you wish to proceed and DELETE ALL DATA from the target device? (Choosing “No” or pressing Escape allows you to go back to each individual step for changing the information.) Summary of settings:
567
568 Target  : $tgtimg
569 Hostname: $myfqdn
570 Username: $userid
571 CMA size: $cma MiB
572 init/FHS: $init
573
574 Packages: $pkgadd" 20 72
575                 ;;
576         esac
577 done
578
579 ##########################
580 # PREPARE DISC STRUCTURE #
581 ##########################
582
583 p 'I: ok, proceeding; this may take some time…' \
584   'N: be prepared to interactively answer more questions though'
585 sleep 3
586 # store some random seed for later, 1ˢᵗ half
587 dd if=/dev/urandom bs=256 count=1 of=rnd 2>/dev/null || die 'dd rnd1 failed'
588 # create MBR with empty BIOS partition table
589 s='This SD card boots on a Raspberry Pi 3B+ only!'
590 printf '\xE8\x'$(echo "obase=16; ${#s}+5" | bc)'\0\r\n' >data
591 printf '%s' 'This SD card boots on a Raspberry Pi 3B+ only!' | tee txt >>data
592 printf '\r\n\0\x5E\16\x1F\xAC\10\xC0\x74\xFE\xB4\16\xBB\7\0\xCD\20\xEB\xF2' \
593     >>data
594 dd if=/dev/zero bs=16 count=4 of=pt 2>/dev/null || die 'dd mbr1 failed'
595 printf '\x55\xAA' >>pt
596 # cobble together wiping partition first MiB “en passant”
597 dd if=/dev/urandom bs=256 count=4096 of=mbr 2>/dev/null || die 'dd mbr2 failed'
598 dd if=mbr bs=1048576 of="$dvname" seek=1 2>/dev/null || die 'dd clr1 failed'
599 dd if=mbr bs=1048576 of="$dvname" seek=128 2>/dev/null || die 'dd clr2 failed'
600 dd if=data of=mbr conv=notrunc 2>/dev/null || die 'dd mbr3 failed'
601 dd if=pt of=mbr bs=1 seek=446 conv=notrunc 2>/dev/null || die 'dd mbr4 failed'
602 dd if=mbr bs=1048576 of="$dvname" 2>/dev/null || die 'dd mbr5 failed'
603 rm mbr
604 # create partitions
605 dieif 'fdisk failed' \
606     fdisk -c=nondos -t MBR -w always -W always "$tgtimg" <<'EOF'
607 n
608 p
609 1
610 2048
611 262143
612 n
613 p
614 2
615 262144
616
617 t
618 1
619 c
620 w
621 EOF
622 kpx=/dev/mapper/${dvname##*/}
623 kpartx -a -f -v -p p -t dos -s "$dvname" || die 'kpartx failed'
624 # create filesystems
625 eatmydata mkfs.msdos -f 1 -F 32 -m txt -n RPi3BpFirmw -v "${kpx}p1" || \
626     die 'mkfs.msdos failed'
627 eatmydata mkfs.ext4 -e remount-ro -E discard -L RasPi3B+root \
628     -O sparse_super2 -U random "${kpx}p2" || die 'mkfs.ext4 failed'
629 # mount filesystems
630 mpt=$T/mnt
631 mkdir "$mpt" || die 'mkdir mpt failed'
632 mount -t ext4 -o noatime,discard "${kpx}p2" "$mpt" || die 'mount (ext4) failed'
633 mkdir "$mpt/boot" || die 'mkdir mpt/boot failed'
634 mkdir "$mpt/boot/firmware" || die 'mkdir mpt/boot/firmware failed'
635 mount -t vfat -o noatime,discard "${kpx}p1" "$mpt/boot/firmware" || \
636     die 'mount (vfat) failed'
637
638 #################################################
639 # INSTALL DEBIAN, FIRST STAGE (CROSS-BOOTSTRAP) #
640 #################################################
641
642 p 'I: created filesystems, now debootstrapping…'
643 if test -n "$dropsd"; then
644         init=
645 else
646         init=--no-merged-usr
647 fi
648 eatmydata debootstrap --arch=arm64 --include=eatmydata,makedev,mksh $init \
649     --force-check-gpg --verbose --foreign buster "$mpt" \
650     http://deb.debian.org/debian sid || die 'debootstrap (first stage) failed'
651     # script specified here as it’s normally what buster symlinks to,
652     # to achieve compatibility with more host distros
653 # we need this early; Debian #700633
654 (
655         set -e
656         cd "$mpt"
657         for archive in var/cache/apt/archives/*eatmydata*.deb; do
658                 dpkg-deb --fsys-tarfile "$archive" >a
659                 tar -xkf a
660         done
661         rm -f a
662 ) || die 'failure extracting eatmydata early'
663 # the user can delete this later, from the booted system (or apt will)
664 cp /usr/bin/qemu-aarch64-static "$mpt/usr/bin/" || die 'cp failed'
665
666 ##################################################
667 # INSTALL DEBIAN, SECOND STAGE (UNDER EMULATION) #
668 ##################################################
669
670 p 'I: second stage bootstrap (under emulation), slooow…'
671 mount -t tmpfs swap "$mpt/dev/shm" || die 'mount /dev/shm failed'
672 mount -t proc  proc "$mpt/proc" || die 'mount /proc failed'
673 mount -t tmpfs swap "$mpt/tmp" || die 'mount /tmp failed'
674 chroot "$mpt" /usr/bin/env -i LC_ALL=C.UTF-8 HOME=/root PATH="$safe_PATH" \
675     TERM="$TERM" /usr/bin/eatmydata /debootstrap/debootstrap --second-stage || \
676     die 'debootstrap (second stage) failed'
677 # debootstrap umounts some; just umount then remount everything
678 umount "$mpt/tmp" 2>/dev/null
679 umount "$mpt/proc" 2>/dev/null
680 umount "$mpt/dev/shm" 2>/dev/null
681
682 ####################################################################
683 # CREATE POST-BOOTSTRAP ENVIRONMENT AND ADJUST CONFIGURATION FILES #
684 ####################################################################
685
686 p 'I: pre-configuring…'
687 mount -t tmpfs swap "$mpt/dev/shm" || die 'remount /dev/shm failed'
688 mount -t proc  proc "$mpt/proc" || die 'remount /proc failed'
689 mount -t tmpfs swap "$mpt/tmp" || die 'remount /tmp failed'
690 # extra as needed below
691 mount --bind /dev/pts "$mpt/dev/pts" || die 'bind-mount /dev/pts failed'
692
693 # standard configuration files (generic)
694 if test -n "$dropsd"; then
695         rnd=/var/lib/systemd/random-seed
696 else
697         rnd=/var/lib/urandom/random-seed
698 fi
699 (
700         set -ex
701         printf '%s\n' '0.0 0 0.0' 0 UTC >"$mpt/etc/adjtime"
702         cat >"$mpt/etc/apt/sources.list" <<-'EOF'
703 deb http://deb.debian.org/debian buster main non-free contrib
704 deb http://deb.debian.org/debian-security buster/updates main non-free contrib
705 deb http://deb.debian.org/debian buster-updates main non-free contrib
706 deb http://deb.debian.org/debian buster-backports main non-free contrib
707         EOF
708         # from console-setup (1.193) config/keyboard
709         cat >"$mpt/etc/default/keyboard" <<-'EOF'
710                 # KEYBOARD CONFIGURATION FILE
711
712                 # Consult the keyboard(5) manual page.
713
714                 XKBMODEL=pc105
715                 XKBLAYOUT=us
716                 XKBVARIANT=
717                 XKBOPTIONS=
718
719                 BACKSPACE=guess
720         EOF
721         : >"$mpt/etc/default/locale"
722         cat >"$mpt/etc/fstab" <<-'EOF'
723 LABEL=RasPi3B+root  /               ext4   defaults,relatime,discard       0  2
724 LABEL=RPi3BpFirmw   /boot/firmware  vfat   defaults,noatime,discard        0  1
725 swap                /tmp            tmpfs  defaults,relatime,nosuid,nodev  0  0
726         EOF
727         case $myfqdn in
728         (*.*)   myhost="$myfqdn ${myfqdn%%.*}" ;;
729         (*)     myhost=$myfqdn ;;
730         esac
731         printf '%s\n' "$myfqdn" >"$mpt/etc/hostname"
732         cat >"$mpt/etc/hosts" <<-EOF
733                 127.0.0.1       $myhost localhost localhost.localdomain
734
735                 ::1     ip6-localhost ip6-loopback localhost6 localhost6.localdomain6
736                 fe00::0 ip6-localnet
737                 ff00::0 ip6-mcastprefix
738                 ff02::1 ip6-allnodes
739                 ff02::2 ip6-allrouters
740                 ff02::3 ip6-allhosts
741         EOF
742         rm -f "$mpt/etc/mtab"
743         ln -sfT /proc/self/mounts "$mpt/etc/mtab"
744         cat >>"$mpt/etc/network/interfaces" <<-'EOF'
745
746                 # The loopback network interface
747                 auto lo
748                 iface lo inet loopback
749
750                 # First Ethernet interface
751                 auto eth0
752                 iface eth0 inet dhcp
753         EOF
754         # for bootstrapping in chroot
755         cat /etc/resolv.conf >"$mpt/etc/resolv.conf"
756         # base directory, init system-dependent
757         mkdir -p "$mpt${rnd%/*}"
758         test -d "$mpt${rnd%/*}"/.
759         chown 0:0 "$mpt${rnd%/*}"
760         chmod 755 "$mpt${rnd%/*}"
761 ) || die 'pre-configuring failed'
762
763 ###################################
764 # CREATE POST-INSTALLATION SCRIPT #
765 ###################################
766
767 (
768         set -e
769         # beginning
770         cat <<-'EOF'
771                 #!/bin/mksh
772                 set -e
773                 set -o pipefail
774                 unset LANGUAGE
775                 export DEBIAN_FRONTEND=teletype HOME=/root LC_ALL=C.UTF-8 \
776                     PATH=/usr/sbin:/usr/bin:/sbin:/bin POSIXLY_CORRECT=1
777                 print -ru2 -- 'I: the MAKEDEV step is extremely slow…'
778                 set -x
779                 (cd /dev && exec MAKEDEV std sd console ttyS0)
780                 hostname "$(</etc/hostname)"
781                 apt-get clean
782                 apt-get update
783                 # for debconf (required)
784                 apt-get --purge -y install --no-install-recommends \
785                     libterm-readline-gnu-perl
786                 export DEBIAN_FRONTEND=readline
787                 # just in case
788                 apt-get --purge -y dist-upgrade
789         EOF
790         # switch to sysvinit?
791         test -n "$dropsd" || cat <<-'EOF'
792                 apt-get --purge -y install --no-install-recommends \
793                     sysvinit-core systemd-
794                 printf '%s\n' \
795                     'Package: systemd' 'Pin: version *' 'Pin-Priority: -1' '' \
796                     >/etc/apt/preferences.d/systemd
797                 (: >/etc/init.d/.legacy-bootordering)
798                 grep FANCYTTY /etc/lsb-base-logging.sh >/dev/null 2>&1 || \
799                     echo FANCYTTY=0 >>/etc/lsb-base-logging.sh
800         EOF
801         # install base packages
802         cat <<-'EOF'
803                 rm -f /var/cache/apt/archives/*.deb
804                 apt-get --purge -y install --no-install-recommends \
805                     busybox firmware-brcm80211 firmware-linux-free \
806                     linux-image-arm64
807                 rm -f /var/cache/apt/archives/*.deb
808                 apt-get --purge -y install --no-install-recommends \
809                     adduser ed linuxlogo raspi3-firmware whiptail
810                 rm -f /var/cache/apt/archives/*.deb
811                 export DEBIAN_FRONTEND=dialog
812                 # basic configuration
813                 print -r -- '(. /etc/os-release 2>/dev/null; linux_logo' \
814                     '-uy ${PRETTY_NAME+-t "OS version: $PRETTY_NAME"} || :)' \
815                     >/etc/profile.d/linux_logo.sh
816                 (whiptail --backtitle 'mkrpi3b+img.sh' --msgbox \
817                     'We will now reconfigure some packages, so you can set up some basic things about your system: timezone (default UTC), keyboard layout, console font, and the system locale (and possibly whether additional locales are to be installed).
818
819 Press Enter to continue.' 12 72 || :)
820                 dpkg-reconfigure -plow tzdata
821                 rm -f /etc/default/locale  # force generation
822                 DEBIAN_PRIORITY=low \
823                     apt-get --purge -y install --no-install-recommends \
824                     console-{common,data,setup} locales
825         EOF
826         # adjust CMA size?
827         test -n "$setcma" || cat <<-'EOF'
828                 ed -s /etc/default/raspi3-firmware <<-'EODB'
829                         ,g/^#CMA=64M/s//CMA=128M/
830                         w
831                         q
832                 EODB
833                 /etc/initramfs/post-update.d/z50-raspi3-firmware
834         EOF
835         # remaining packages and configuration
836         cat <<-'EOF'
837                 : remaining user configuration may error out
838                 set +e
839                 # make man-db faster at cost of no apropos(1) lookup database
840                 debconf-set-selections <<-'EODB'
841                         man-db man-db/build-database boolean false
842                         man-db man-db/auto-update boolean false
843                 EODB
844                 : install basic packages
845                 apt-get --purge -y install --no-install-recommends \
846                     bc ca-certificates ifupdown iproute2 jupp joe-jupp less \
847                     lsb-release lynx man-db mc mlocate molly-guard net-tools \
848                     netcat-openbsd openssh-client popularity-contest procps \
849                     rsync screen sharutils sudo
850                 rm -f /var/cache/apt/archives/*.deb
851         EOF
852         set -o noglob
853         set -- $pkgadd
854         set +o noglob
855         pkgs= s=
856         for pkg in "$@"; do
857                 case $pkg in
858                 (_WLAN_) pkg='crda wireless-tools wpasupplicant' ;;
859                 esac
860                 pkgs="$pkgs$s$pkg" s=' '
861         done
862         # list of groups from user-setup (1.81)
863         # debian/user-setup.templates: passwd/user-default-groups
864         set -- audio bluetooth cdrom debian-tor dip floppy lpadmin \
865             netdev plugdev scanner video
866         # we add adm and sudo
867         groups="$* adm sudo"
868         cat <<-EOF
869                 : install extra packages
870                 apt-get --purge install --no-install-recommends $pkgs
871                 rm -f /var/cache/apt/archives/*.deb
872                 : create initial user account, do remember password set
873                 adduser '$userid'
874                 : ignore errors for nonexisting groups, please
875                 for group in $groups; do
876                         adduser '$userid' \$group
877                 done
878                 : end of pre-scripted post-bootstrap steps
879                 set +x
880                 userid='$userid'
881         EOF
882
883         ##############################################################
884         # PERMIT MANUAL STEPS BY SWITCHING (UNDER EMULATION) TO USER #
885         ##############################################################
886
887         cat <<-'EOF'
888                 whiptail --backtitle 'mkrpi3b+img.sh' \
889                     --msgbox "We will now (chrooted into the target system, under emulation, so it will be really slooooow…) run a login shell as the user account we just created ($userid), so you can do any manual post-installation steps desired.
890
891 Please use “sudo -S command” to run things as root, if necessary.
892
893 Press Enter to continue; exit the emulation with the “exit” command." 14 72
894                 unset DEBIAN_FRONTEND POSIXLY_CORRECT
895                 export HOME=/  # later overridden by su
896                 >>/var/log/syslog print -r -- "$(date +"%b %d %T")" \
897                     "${HOSTNAME%%.*} mkrpi3b+img.sh[$$]:" \
898                     soliciting manual post-installation steps
899                 chown 0:adm /var/log/syslog
900                 chmod 640 /var/log/syslog
901                 find /usr/lib -name libeatmydata.so\* -a -type f -print0 | \
902                     xargs -0r chmod u+s --
903                 su - "$userid"
904                 find /usr/lib -name libeatmydata.so\* -a -type f -print0 | \
905                     xargs -0r chmod u-s --
906                 print -ru2 -- 'I: running apt-get autoremove,' \
907                     'acknowledge as desired'
908                 apt-get --purge autoremove
909                 print -ru2 -- 'I: finally, cleaning up'
910                 apt-get clean
911                 pwck -s
912                 grpck -s
913                 rm -f /etc/{passwd,group,{,g}shadow,sub{u,g}id}-
914                 if whence -p etckeeper >/dev/null; then
915                         etckeeper commit 'Finish installation'
916                         etckeeper vcs gc
917                 fi
918                 rm -f /var/log/bootstrap.log
919                 # from /lib/init/bootclean.sh
920                 cd /run
921                 find . ! -xtype d ! -name utmp ! -name innd.pid -delete
922                 # fine𝄐
923                 >>/var/log/syslog print -r -- "$(date +"%b %d %T")" \
924                     "${HOSTNAME%%.*} mkrpi3b+img.sh[$$]:" \
925                     finishing up installation\; once booted natively on the \
926                     device, you can nuke /usr/bin/qemu-aarch64-static manually
927         EOF
928 ) >"$mpt/root/munge-it.sh" || die 'post-installation script creation failure'
929
930 # now place initial random seed
931 mv rnd "$mpt$rnd" || die 'mv rnd failed'
932 chown 0:0 "$mpt$rnd" || die 'chown rnd failed'
933 chmod 600 "$mpt$rnd" || die 'chmod rnd failed'
934 dd if=/dev/urandom bs=256 count=1 >>"$mpt$rnd" || die 'dd rnd2 failed'
935
936 ########################################################
937 # POST-BOOTSTRAP SCRIPT RUN IN CHROOT, UNDER EMULATION #
938 ########################################################
939
940 unshare --uts chroot "$mpt" /usr/bin/env -i TERM="$TERM" /usr/bin/eatmydata \
941     /bin/mksh /root/munge-it.sh || die 'post-bootstrap failed'
942 rm -f "$mpt/root/munge-it.sh"
943
944 #######################
945 # FINISH AND CLEAN UP #
946 #######################
947
948 w --infobox 'OK. We will now clean up the target system.' 7 72
949
950 fstrim -v "$mpt/boot/firmware"
951 fstrim -v "$mpt"
952 dd if=/dev/urandom bs=64 count=1 conv=notrunc of="$mpt$rnd" || \
953     p 'W: dd rnd3 failed'
954 p "I: done installing on $dvname ($tgtimg)"
955 # Debian #801614
956 test -n "$dropsd" || p 'W: when installing X11, you’ll need these extra steps:' \
957     'N: 1. install the package xserver-xorg-legacy' \
958     'N: 2. edit /etc/X11/Xwrapper.config to add the two lines:' \
959     'N:     allowed_users=anybody  # or as you desire' \
960     'N:     needs_root_rights=yes'
961 # remaining cleanup and unwinding done in the EXIT trap
962 exit 0