Using save/ to keep downloads and partial progress
[rrq/tiniest.git] / mkit.sh
1 #!/bin/bash
2 #
3 # A script to prepare the tiniest "linux system". It's a kernel and an
4 # initrd within a FAT with a syslinux boot loader. The initrd contains
5 # a fully expanded busybox and uses /bin/sh as its init.
6 #
7 # This script creates and packs that initrd, as well as the whole,
8 # boot image. 
9
10 set -e
11
12 echo '### Step 1. Download original Packages files from sources'
13 mkdir -p save
14 [ -L save/save ] || ln -s . save/save
15 MAPFILE=save/mapfile.txt
16 DEPFILE=save/mapdepends.txt
17
18 REPOS=(
19     deb.devuan.org/merged_daedalus_main
20     deb.devuan.org/merged_ceres_main
21 )
22 ARCH="amd64"
23 MBR=dos # gpt
24
25 : > $MAPFILE
26 : > $DEPFILE
27
28 for REPO in ${REPOS[@]} ; do
29     W=( ${REPO//_/ } )
30     SUITE=${W[1]}
31     SECTION=${W[2]}
32     
33     PKGFILE=save/${W[0]/\//_}_${SUITE}_${SECTION}_binary-${ARCH}_Packages
34     if [ ! -r $PKGFILE ] ; then
35         XZSRC="http://${W[0]}/dists/$SUITE/$SECTION/binary-$ARCH/Packages.xz"
36         echo "$XZSRC"
37         wget -qO - $XZSRC | xzcat - > ${PKGFILE}
38     fi
39
40     echo '# Reduce Packages file into two maps for filename and depends'
41     echo "# ..creating $MAPFILE and $DEPFILE"
42     awk -v MAP=$MAPFILE -v DEP=$DEPFILE '
43     BEGIN { print "###" >> MAP; print "###" >> DEP; }
44 $1=="Package:" {P=$2; next}
45 $1=="Pre-Depends:" {print P,$0 >> DEP;next }
46 $1=="Depends:" {print P,$0 >> DEP;next }
47 $1=="Filename:" {print P,$2 >> MAP;next }
48 ' ${PKGFILE}
49 done
50
51 # Append any additional saved deb files
52 for DEB in save/*.deb ; do
53     P="${DEB%%_*}"
54     P="${P#save/}"
55     echo "$P save/$DEB" >> $MAPFILE
56     dpkg-deb -f "$DEB" Depends Pre-Depends | sed "s/^/$P/" >> $DEPFILE
57 done
58
59 # Function to find the filename for a given package in the given mapfile
60 maplookup() {
61     local FN=${3:-tail}
62     echo "maplookup $1 $2" >&2
63     grep "^$1 " $2 >&2
64     grep "^$1 " $2 | $FN -n 1 | sed 's/[^ ]* //'
65 }
66
67 # Function to clean up a "depends' line
68 depclean() {
69     sed 's/^[^:]*: //;s/([^)]*)//g;s/|[^,]*/ /g;s/,/ /g;s/\s\+/ /g'
70 }
71
72 # Map all dependencies for a list of package
73 #
74 mapdeps() {
75     local D
76     local B=( )
77     local X=" $* "
78     for D in $* ; do
79         local A=(
80             $(grep "^$D Pre-Depends:" $DEPFILE | head -n 1 | depclean)
81             $(grep "^$D Depends:" $DEPFILE | head -n 1 | depclean)
82         )
83         for P in ${A[@]} ; do
84             [ -z "${X%%* $P *}" ] && continue
85             B=( $B $P )
86             X="$X$P "
87         done
88     done
89     if [ -z "$B" ] ; then
90         echo $X
91     else
92         mapdeps $X
93     fi
94 }
95
96 # Function to download a deb file and return its name
97 debfile() {
98     local FN=${2-tail}
99     local F="$(maplookup $1 $MAPFILE $FN)"
100     local P="${F##*/}"
101     if [ ! -e "save/$P" ] ; then
102         for REPO in ${REPOS[@]} ; do
103             echo "download http://${REPO%%_*}/$F" >&2
104             ( cd save && wget -q "http://${REPO%%_*}/$F" ) && break
105             ls -l save/$P >&2
106         done || return 1
107     fi
108     echo "save/$P"
109 }
110
111 # Function to extract from a deb without executing and pre/post scripts
112 # $1 = rootfs $2 = package
113 debextract() {
114     ar p $2 data.tar.xz | tar xJf - -C $1
115 }
116
117 # Deteremine which kernel to use; this is
118 echo "# Determine kernel"
119 KERNEL="$(maplookup linux-image-amd64 $DEPFILE head | \
120                     sed 's/.*\(linux-image[^ ]*\).*/\1/')"
121 echo "# ... $KERNEL"
122
123 echo '### Step 2. Create and populate the initrd, and packit up.'
124 # The initrd contains only a few kernel modules for coping with a
125 # later pivoting onto a "full" filesystem.
126
127 echo "# Create initrd filesystem"
128 rm -fr initrd
129
130 # Helper function to copy a file or link, given full path
131 # $1 = full pathname, $2 = (relative) root path
132 copylinks() {
133     echo "copylinks $*" >&2
134     [ -e "initrd/$1" ] && return 0
135     mkdir -p "initrd${1%/*}"
136     if [ -L "$1" ] ; then
137         local L=$(readlink $2$1)
138         ln -s $L initrd$1
139         [ "${L:0:1}" = "/" ] || L="${1%/*}/$L"
140         copylinks $L $2
141     else
142         cp $2$1 initrd/$1
143     fi
144 }
145
146 # Helper function to copy dynamic binary and its libraries
147 # $1 = full pathname, $2 = (relative) root path
148 bincp() {
149     echo "bincp $*" >&2
150     [ -e "initrd/$1" ] && return 0
151     copylinks $1 $2
152     objdump -x $2$1 | while read A B ; do
153         [ "$A" == "NEEDED" ] || continue
154         local L="$(find ${2:-/} -name $B)"
155         [ -z "$L" ] && echo "MISSING $B" && exit 1
156         copylinks ${L#$2} $2
157     done
158 }
159
160 echo '# Include static vtoydump for Ventoy support'
161 VTOYDUMP=../ventoy/vtoydump/vtoydump
162 if [ -e "$VTOYDUMP" ] ; then
163     mkdir -p initrd/bin
164     cp "$VTOYDUMP" initrd/bin
165 fi
166
167 echo "# Extract static busybox, and fluff it up for more utilities"
168 mkdir -p initrd
169 debextract initrd $(debfile busybox-static head)
170 for L in $(initrd/bin/busybox --listfull) ; do
171     mkdir -p $(dirname initrd/$L)
172     [ -e "initrd/$L" ] && continue
173     case "$L" in
174         bin/busybox) : ;;
175         usr/*) ln -s ../../bin/busybox initrd/$L ;;
176         sbin/*) ln -s ../bin/busybox initrd/$L ;;
177         bin/*)  ln -s busybox initrd/$L ;;
178         linuxrc) ln -s bin/busybox initrd/$L ;;
179     esac
180 done
181
182 echo "# Extract the kernel package ($KERNEL)"
183 echo "# .. and syslinux stuff if needed"
184 if [ ! -d kernel ] ; then
185     mkdir kernel
186     debextract kernel $(debfile $KERNEL head)
187     debextract kernel $(debfile squashfs-tools head)
188     debextract kernel $(debfile syslinux)
189     debextract kernel $(debfile syslinux-common)
190     debextract kernel $(debfile syslinux-efi)
191     debextract kernel $(debfile syslinux-utils)
192     debextract kernel $(debfile isolinux)
193 fi
194
195 echo "# Include some kernel modules in the initrd"
196 MODULES=(
197     # disk
198     scsi_common scsi_mod libata ata_piix ata_generic cdrom sr_mod
199     crc32-pclmul crct10dif_common crc-t10dif crc64 crc64-rocksoft
200     t10-pi sd_mod sg
201     nls_cp437 nls_ascii fat vfat
202     crc32c_generic jbd2 mbcache crc16 ext4
203     usb-storage usbcore usb-common xhci-pci xhci-hcd
204     isofs exfat loop
205     efivarfs
206     # input
207     psmouse evdev
208     # network
209     e1000
210     # overlay
211     overlay squashfs
212 )
213 MOODLES=""
214 B=$(pwd)
215 for m in ${MODULES[@]} ; do
216     km=$(find kernel/lib/modules -name $m.ko)
217     if [ -z "$km" ] ; then
218         echo "Missing module $m"
219         continue
220     fi
221     im=initrd/${km#kernel/}
222     MOODLES+=" $B/$im"
223     mkdir -p $(dirname $im)
224     cp -n $km $im
225 done
226 V=${KERNEL#linux-image-}
227 mkdir -p initrd/boot initrd/lib/modules/$V
228 cp kernel/boot/System.map-$V initrd/
229 cp kernel/lib/modules/$V/modules.order initrd/lib/modules/$V/
230 cp kernel/lib/modules/$V/modules.builtin initrd/lib/modules/$V/
231 depmod -F initrd/System.map-$V -b initrd $V $MOODLES
232
233 echo "# setup a scripted init. The kernel runs this via the #! interpreter"
234 rm -f initrd/sbin/init # just in case
235 cp init/init initrd/init
236 chmod a+x initrd/init
237
238 echo "# Declare password-less root"
239 mkdir -p initrd/etc
240 echo 'root::0:0:root:/root:/bin/bash' > initrd/etc/passwd
241
242 echo "# Now pack up that initrd as initrd.gz"
243 ( cd initrd ; find . | fakeroot cpio -H newc -o | gzip ) >initrd.gz
244
245 echo '### Step 3. create a 32  Mb fat filesystem with bios and UEFI boot'
246 rm -f bootimage.raw
247 dd if=/dev/zero of=bootimage.raw bs=32M count=1
248
249 # Prepare a gpt/dos partition table with a first partition marked as EFI
250 sfdisk bootimage.raw <<EOF
251 label: $MBR
252
253 2048 32767 U *
254 - - L
255 EOF
256
257 # Add a fat filesystem at 2048 61440
258 mkfs.fat -n TINIEST --offset 2048 -F 16 bootimage.raw
259 IMG="-i bootimage.raw@@$((2048*512))"
260
261 # Add an ext2 filesystem at offset 34816*512
262 # Copy initrd.gz and kernel into the fat filesystem root
263 EXT=offset=$((34816*512))
264 mke2fs -t ext4 -E $EXT -F bootimage.raw 15M
265
266 mcopy $IMG initrd.gz ::
267 mcopy $IMG kernel/boot/vm* ::/vmlinuz
268 mcopy $IMG splash.png ::/
269
270 mmd $IMG ::/boot
271 mmd $IMG ::/boot/syslinux
272 mmd $IMG ::/boot/syslinux/bios
273 mcopy $IMG \
274       kernel/usr/lib/syslinux/modules/bios/* ::/boot/syslinux/bios
275 mcopy $IMG syslinux-legacy.cfg ::/syslinux.cfg
276
277 syslinux --install --offset=${IMG#*@@} bootimage.raw
278 mmd $IMG ::/EFI
279 mmd $IMG ::/EFI/BOOT
280 mcopy $IMG kernel/usr/lib/SYSLINUX.EFI/efi64/syslinux.efi \
281       ::/EFI/BOOT/bootx64.efi
282 mcopy $IMG \
283       kernel/usr/lib/syslinux/modules/efi64/* ::/EFI/BOOT
284 mcopy $IMG syslinux-uefi.cfg ::/EFI/BOOT/syslinux.cfg
285 ## Add lua boot script
286 mcopy $IMG muffin.lua ::/EFI/BOOT/muffin.lua
287
288 case "$MBR" in
289     dos) MBRBIN=mbr.bin ;;
290     gpt)
291         MBRBIN=gptmbr.bin
292         sfdisk --relocate gpt-bak-std bootimage.raw
293         sfdisk -f --part-attrs bootimage.raw 1 LegacyBIOSBootable
294         ;;
295     *) exit 1 ;;
296 esac
297 dd conv=notrunc of=bootimage.raw bs=440 count=1 \
298    if=kernel/usr/lib/syslinux/mbr/$MBRBIN
299
300 echo "# populate the extra partition"
301 if [ -d extra ] ; then
302     mkdir -p X
303     fuse2fs -o fakeroot -o$EXT bootimage.raw X
304     rsync -r extra/. X/.
305     umount X
306 fi
307
308 exit