mkimage.sh 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411
  1. #!/bin/sh
  2. #-
  3. # Copyright (c) 2013-2016 Juan Romero Pardines.
  4. # Copyright (c) 2017 Google
  5. # All rights reserved.
  6. #
  7. # Redistribution and use in source and binary forms, with or without
  8. # modification, are permitted provided that the following conditions
  9. # are met:
  10. # 1. Redistributions of source code must retain the above copyright
  11. # notice, this list of conditions and the following disclaimer.
  12. # 2. Redistributions in binary form must reproduce the above copyright
  13. # notice, this list of conditions and the following disclaimer in the
  14. # documentation and/or other materials provided with the distribution.
  15. #
  16. # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
  17. # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  18. # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
  19. # IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
  20. # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  21. # NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  22. # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  23. # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  24. # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
  25. # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  26. #-
  27. . ./version.sh
  28. readonly PROGNAME=$(basename "$0")
  29. readonly ARCH=$(uname -m)
  30. trap 'printf "\nInterrupted! exiting...\n"; cleanup; exit 0' INT TERM HUP
  31. # This source pulls in all the functions from lib.sh. This set of
  32. # functions makes it much easier to work with chroots and abstracts
  33. # away all the problems with running binaries with QEMU.
  34. # shellcheck source=./lib.sh
  35. . ./lib.sh
  36. # This script has a special cleanup() function since it needs to
  37. # unmount the rootfs as mounted on a loop device. This function is
  38. # defined after sourcing the library functions to ensure it is the
  39. # last one defined.
  40. cleanup() {
  41. umount_pseudofs
  42. umount -f "${ROOTFS}/boot" 2>/dev/null
  43. umount -f "${ROOTFS}" 2>/dev/null
  44. if [ -e "$LOOPDEV" ]; then
  45. partx -d "$LOOPDEV" 2>/dev/null
  46. losetup -d "$LOOPDEV" 2>/dev/null
  47. fi
  48. [ -d "$ROOTFS" ] && rmdir "$ROOTFS"
  49. }
  50. # This script is designed to take in a complete platformfs and spit
  51. # out an image that is suitable for writing with dd. The image is
  52. # configurable in terms of the filesystem layout, but not in terms of
  53. # the installed system itself. Customization to the installed system
  54. # should be made during the mkplatformfs step.
  55. usage() {
  56. cat <<_EOF
  57. Usage: $PROGNAME [options] <rootfs-tarball>
  58. The <rootfs-tarball> argument expects a tarball generated by void-mkrootfs.
  59. The platform is guessed automatically by its name.
  60. Accepted sizes suffixes: KiB, MiB, GiB, TiB, EiB.
  61. OPTIONS
  62. -b <fstype> Set /boot filesystem type (defaults to FAT)
  63. -B <bsize> Set /boot filesystem size (defaults to 64MiB)
  64. -r <fstype> Set / filesystem type (defaults to EXT4)
  65. -s <totalsize> Set total image size (defaults to 2GB)
  66. -o <output> Set image filename (guessed automatically)
  67. -x <num> Use <num> threads to compress the image (dynamic if unset)
  68. -h Show this help
  69. -V Show version
  70. Resulting image will have 2 partitions, /boot and /.
  71. _EOF
  72. exit 0
  73. }
  74. # ########################################
  75. # SCRIPT EXECUTION STARTS HERE
  76. # ########################################
  77. while getopts "b:B:o:r:s:x:h:V" opt; do
  78. case $opt in
  79. b) BOOT_FSTYPE="$OPTARG";;
  80. B) BOOT_FSSIZE="$OPTARG";;
  81. o) FILENAME="$OPTARG";;
  82. r) ROOT_FSTYPE="$OPTARG";;
  83. s) IMGSIZE="$OPTARG";;
  84. x) COMPRESSOR_THREADS="$OPTARG" ;;
  85. V) echo "$PROGNAME $MKLIVE_VERSION"; exit 0;;
  86. h) usage;;
  87. esac
  88. done
  89. shift $((OPTIND - 1))
  90. ROOTFS_TARBALL="$1"
  91. if [ -z "$ROOTFS_TARBALL" ]; then
  92. usage
  93. elif [ ! -r "$ROOTFS_TARBALL" ]; then
  94. # In rare cases the tarball can wind up owned by the wrong user.
  95. # This leads to confusing failures if execution is allowed to
  96. # proceed.
  97. die "Cannot read rootfs tarball: $ROOTFS_TARBALL"
  98. fi
  99. # Setup the platform variable. Here we want just the name and
  100. # optionally -musl if this is the musl variant.
  101. PLATFORM="${ROOTFS_TARBALL#void-}"
  102. PLATFORM="${PLATFORM%-PLATFORMFS*}"
  103. # Be absolutely certain the platform is supported before continuing
  104. case "$PLATFORM" in
  105. bananapi|beaglebone|cubieboard2|cubietruck|odroid-c2|odroid-u2|rpi-armv6l|rpi-armv7l|rpi-aarch64|GCP|pinebookpro|pinephone|rock64|*-musl);;
  106. *) die "The $PLATFORM is not supported, exiting..."
  107. esac
  108. # Default for bigger boot partion on rk33xx devices since it needs to
  109. # fit at least 2 Kernels + initramfs
  110. case "$PLATFORM" in
  111. pinebookpro*|rock64*)
  112. : "${BOOT_FSSIZE:=256MiB}"
  113. ;;
  114. esac
  115. # By default we build all platform images with a 64MiB boot partition
  116. # formated FAT16, and an approximately 1.9GiB root partition formated
  117. # ext4. More exotic combinations are of course possible, but this
  118. # combination works on all known platforms.
  119. : "${IMGSIZE:=2G}"
  120. : "${BOOT_FSTYPE:=vfat}"
  121. : "${BOOT_FSSIZE:=64MiB}"
  122. : "${ROOT_FSTYPE:=ext4}"
  123. # Verify that the required tooling is available
  124. readonly REQTOOLS="sfdisk partx losetup mount truncate mkfs.${BOOT_FSTYPE} mkfs.${ROOT_FSTYPE}"
  125. check_tools
  126. # This is an awful hack since the script isn't using privesc
  127. # mechanisms selectively. This is a TODO item.
  128. if [ "$(id -u)" -ne 0 ]; then
  129. die "need root perms to continue, exiting."
  130. fi
  131. # Set the default filename if none was provided above. The default
  132. # will include the platform the image is being built for and the date
  133. # on which it was built.
  134. if [ -z "$FILENAME" ]; then
  135. FILENAME="void-${PLATFORM}-$(date -u +%Y%m%d).img"
  136. fi
  137. # Create the base image. This was previously accomplished with dd,
  138. # but truncate is markedly faster.
  139. info_msg "Creating disk image ($IMGSIZE) ..."
  140. truncate -s "${IMGSIZE}" "$FILENAME" >/dev/null 2>&1
  141. # Grab a tmpdir for the rootfs. If this fails we need to halt now
  142. # because otherwise things will go very badly for the host system.
  143. ROOTFS=$(mktemp -d) || die "Could not create tmpdir for ROOTFS"
  144. info_msg "Creating disk image partitions/filesystems ..."
  145. if [ "$BOOT_FSTYPE" = "vfat" ]; then
  146. # The mkfs.vfat program tries to make some "intelligent" choices
  147. # about the type of filesystem it creates. Instead we set options
  148. # if the type is vfat to ensure that the same options will be used
  149. # every time.
  150. _args="-I -F16"
  151. fi
  152. case "$PLATFORM" in
  153. cubieboard2|cubietruck|ci20*|odroid-c2*)
  154. # These platforms use a single partition for the entire filesystem.
  155. sfdisk "${FILENAME}" <<_EOF
  156. label: dos
  157. 2048,,L
  158. _EOF
  159. LOOPDEV=$(losetup --show --find --partscan "$FILENAME")
  160. mkfs.${ROOT_FSTYPE} -O '^64bit,^extra_isize,^has_journal' "${LOOPDEV}p1" >/dev/null 2>&1
  161. mount "${LOOPDEV}p1" "$ROOTFS"
  162. ROOT_UUID=$(blkid -o value -s UUID "${LOOPDEV}p1")
  163. ;;
  164. *)
  165. # These platforms use a partition layout with a small boot
  166. # partition (64M by default) and the rest of the space as the
  167. # root filesystem. This is the generally preferred disk
  168. # layout for new platforms.
  169. case "$PLATFORM" in
  170. pinebookpro*|rock64*)
  171. # rk33xx devices use GPT and need more space reserved
  172. sfdisk "$FILENAME" <<_EOF
  173. label: gpt
  174. unit: sectors
  175. first-lba: 32768
  176. name=BootFS, size=${BOOT_FSSIZE}, type=L, bootable, attrs="LegacyBIOSBootable"
  177. name=RootFS, type=L
  178. _EOF
  179. ;;
  180. *)
  181. # The rest use MBR and need less space reserved
  182. sfdisk "${FILENAME}" <<_EOF
  183. label: dos
  184. 2048,${BOOT_FSSIZE},b,*
  185. ,+,L
  186. _EOF
  187. ;;
  188. esac
  189. LOOPDEV=$(losetup --show --find --partscan "$FILENAME")
  190. # Normally we need to quote to prevent argument splitting, but
  191. # we explicitly want argument splitting here.
  192. # shellcheck disable=SC2086
  193. mkfs.${BOOT_FSTYPE} $_args "${LOOPDEV}p1" >/dev/null
  194. case "$ROOT_FSTYPE" in
  195. # Because the images produced by this script are generally
  196. # either on single board computers using flash memory or
  197. # in cloud environments that already provide disk
  198. # durability, we shut off the journal for ext filesystems.
  199. # For flash memory this greatly extends the life of the
  200. # memory and for cloud images this lowers the overhead by
  201. # a small amount.
  202. ext[34]) disable_journal="-O ^has_journal";;
  203. esac
  204. mkfs.${ROOT_FSTYPE} ${disable_journal:+"$disable_journal"} "${LOOPDEV}p2" >/dev/null 2>&1
  205. mount "${LOOPDEV}p2" "$ROOTFS"
  206. mkdir -p "${ROOTFS}/boot"
  207. mount "${LOOPDEV}p1" "${ROOTFS}/boot"
  208. BOOT_UUID=$(blkid -o value -s UUID "${LOOPDEV}p1")
  209. ROOT_UUID=$(blkid -o value -s UUID "${LOOPDEV}p2")
  210. ROOT_PARTUUID=$(blkid -o value -s PARTUUID "${LOOPDEV}p2")
  211. ;;
  212. esac
  213. # This step unpacks the platformfs tarball made by mkplatformfs.sh.
  214. info_msg "Unpacking rootfs tarball ..."
  215. if [ "$PLATFORM" = "beaglebone" ]; then
  216. # The beaglebone requires some special extra handling. The MLO
  217. # program is a special first stage boot loader that brings up
  218. # enough of the processor to then load u-boot which loads the rest
  219. # of the system. The noauto option also prevents /boot from being
  220. # mounted during system startup.
  221. fstab_args=",noauto"
  222. tar xfp "$ROOTFS_TARBALL" -C "$ROOTFS" ./boot/MLO
  223. tar xfp "$ROOTFS_TARBALL" -C "$ROOTFS" ./boot/u-boot.img
  224. touch "$ROOTFS/boot/uEnv.txt"
  225. umount "$ROOTFS/boot"
  226. fi
  227. # In the general case, its enough to just unpack the ROOTFS_TARBALL
  228. # onto the ROOTFS. This will get a system that is ready to boot, save
  229. # for the bootloader which is handled later.
  230. tar xfp "$ROOTFS_TARBALL" --xattrs --xattrs-include='*' -C "$ROOTFS"
  231. # For f2fs the system should not attempt an fsck at boot. This
  232. # filesystem is in theory self healing and does not use the standard
  233. # mechanisms. All other filesystems should use fsck at boot.
  234. fspassno="1"
  235. if [ "$ROOT_FSTYPE" = "f2fs" ]; then
  236. fspassno="0"
  237. fi
  238. # Void images prefer uuids to nodes in /dev since these are not
  239. # dependent on the hardware layout. On a single board computer this
  240. # may not matter much but it makes the cloud images easier to manage.
  241. echo "UUID=$ROOT_UUID / $ROOT_FSTYPE defaults 0 ${fspassno}" >> "${ROOTFS}/etc/fstab"
  242. if [ -n "$BOOT_UUID" ]; then
  243. echo "UUID=$BOOT_UUID /boot $BOOT_FSTYPE defaults${fstab_args} 0 2" >> "${ROOTFS}/etc/fstab"
  244. fi
  245. # Images are shipped with root as the only user by default, so we need to
  246. # ensure ssh login is possible for headless setups.
  247. sed -i "${ROOTFS}/etc/ssh/sshd_config" -e 's|^#\(PermitRootLogin\) .*|\1 yes|g'
  248. # This section does final configuration on the images. In the case of
  249. # SBCs this writes the bootloader to the image or sets up other
  250. # required binaries to boot. In the case of images destined for a
  251. # Cloud, this sets up the services that the cloud will expect to be
  252. # running and a suitable bootloader. When adding a new platform,
  253. # please add a comment explaining what the steps you are adding do,
  254. # and where information about your specific platform's boot process
  255. # can be found.
  256. info_msg "Configuring image for platform $PLATFORM"
  257. case "$PLATFORM" in
  258. bananapi*|cubieboard2*|cubietruck*)
  259. dd if="${ROOTFS}/boot/u-boot-sunxi-with-spl.bin" of="${LOOPDEV}" bs=1024 seek=8 >/dev/null 2>&1
  260. ;;
  261. odroid-c2*)
  262. dd if="${ROOTFS}/boot/bl1.bin.hardkernel" of="${LOOPDEV}" bs=1 count=442 >/dev/null 2>&1
  263. dd if="${ROOTFS}/boot/bl1.bin.hardkernel" of="${LOOPDEV}" bs=512 skip=1 seek=1 >/dev/null 2>&1
  264. dd if="${ROOTFS}/boot/u-boot.bin" of="${LOOPDEV}" bs=512 seek=97 >/dev/null 2>&1
  265. ;;
  266. odroid-u2*)
  267. dd if="${ROOTFS}/boot/E4412_S.bl1.HardKernel.bin" of="${LOOPDEV}" seek=1 >/dev/null 2>&1
  268. dd if="${ROOTFS}/boot/bl2.signed.bin" of="${LOOPDEV}" seek=31 >/dev/null 2>&1
  269. dd if="${ROOTFS}/boot/u-boot.bin" of="${LOOPDEV}" seek=63 >/dev/null 2>&1
  270. dd if="${ROOTFS}/boot/E4412_S.tzsw.signed.bin" of="${LOOPDEV}" seek=2111 >/dev/null 2>&1
  271. ;;
  272. ci20*)
  273. dd if="${ROOTFS}/boot/u-boot-spl.bin" of="${LOOPDEV}" obs=512 seek=1 >/dev/null 2>&1
  274. dd if="${ROOTFS}/boot/u-boot.img" of="${LOOPDEV}" obs=1K seek=14 >/dev/null 2>&1
  275. ;;
  276. rock64*)
  277. rk33xx_flash_uboot "${ROOTFS}/usr/lib/rock64-uboot" "$LOOPDEV"
  278. # populate the extlinux.conf file
  279. cat >"${ROOTFS}/etc/default/extlinux" <<_EOF
  280. TIMEOUT=10
  281. # Defaults to current kernel cmdline if left empty
  282. CMDLINE="panic=10 coherent_pool=1M console=ttyS2,1500000 root=UUID=${ROOT_UUID} rw"
  283. # set this to use a DEVICETREEDIR line in place of an FDT line
  284. USE_DEVICETREEDIR="yes"
  285. # relative dtb path supplied to FDT line, as long as above is unset
  286. DTBPATH=""
  287. _EOF
  288. mkdir -p "${ROOTFS}/boot/extlinux"
  289. run_cmd_chroot "${ROOTFS}" "/etc/kernel.d/post-install/60-extlinux"
  290. cleanup_chroot
  291. ;;
  292. pinebookpro*)
  293. rk33xx_flash_uboot "${ROOTFS}/usr/lib/pinebookpro-uboot" "$LOOPDEV"
  294. run_cmd_chroot "${ROOTFS}" "xbps-reconfigure -f pinebookpro-kernel"
  295. cleanup_chroot
  296. ;;
  297. pinephone*)
  298. sed -i "s/CMDLINE=\"\(.*\)\"\$/CMDLINE=\"\1 root=PARTUUID=${ROOT_PARTUUID}\"/" "${ROOTFS}/etc/default/pinephone-uboot-config"
  299. dd if="${ROOTFS}/boot/u-boot-sunxi-with-spl.bin" of="${LOOPDEV}" bs=1024 seek=8 conv=notrunc,fsync >/dev/null 2>&1
  300. run_cmd_chroot "${ROOTFS}" "xbps-reconfigure -f pinephone-kernel"
  301. cleanup_chroot
  302. ;;
  303. GCP*)
  304. # Google Cloud Platform image configuration for Google Cloud
  305. # Engine. The steps below are built in reference to the
  306. # documentation on building custom images available here:
  307. # https://cloud.google.com/compute/docs/images/import-existing-image
  308. # The images produced by this script are ready to upload and boot.
  309. # Setup GRUB
  310. mount_pseudofs
  311. run_cmd_chroot "${ROOTFS}" "grub-install ${LOOPDEV}"
  312. sed -i "s:page_poison=1:page_poison=1 console=ttyS0,38400n8d:" "${ROOTFS}/etc/default/grub"
  313. run_cmd_chroot "${ROOTFS}" update-grub
  314. # Setup the GCP Guest services
  315. for _service in dhcpcd sshd agetty-console nanoklogd socklog-unix GCP-Guest-Initialization GCP-accounts GCP-clock-skew GCP-ip-forwarding ; do
  316. run_cmd_chroot "${ROOTFS}" "ln -sv /etc/sv/$_service /etc/runit/runsvdir/default/$_service"
  317. done
  318. # Turn off the agetty's since we can't use them anyway
  319. rm -v "${ROOTFS}/etc/runit/runsvdir/default/agetty-tty"*
  320. # Disable root login over ssh and lock account
  321. sed -i "s:PermitRootLogin yes:PermitRootLogin no:" "${ROOTFS}/etc/ssh/sshd_config"
  322. run_cmd_chroot "${ROOTFS}" "passwd -l root"
  323. # Set the Timezone
  324. run_cmd_chroot "${ROOTFS}" "ln -svf /usr/share/zoneinfo/UTC /etc/localtime"
  325. # Generate glibc-locales if necessary (this is a noop on musl)
  326. if [ "$PLATFORM" = GCP ] ; then
  327. run_cmd_chroot "${ROOTFS}" "xbps-reconfigure -f glibc-locales"
  328. fi
  329. # Remove SSH host keys (these will get rebuilt on first boot)
  330. rm -f "${ROOTFS}/etc/ssh/*key*"
  331. rm -f "${ROOTFS}/etc/ssh/moduli"
  332. # Force the hostname since this isn't read from DHCP
  333. echo void-GCE > "${ROOTFS}/etc/hostname"
  334. # Cleanup the chroot from anything that was setup for the
  335. # run_cmd_chroot commands
  336. cleanup_chroot
  337. ;;
  338. esac
  339. # Release all the mounts, deconfigure the loop device, and remove the
  340. # rootfs mountpoint. Since this was just a mountpoint it should be
  341. # empty. If it contains stuff we bail out here since something went
  342. # very wrong.
  343. umount -R "$ROOTFS"
  344. losetup -d "$LOOPDEV"
  345. rmdir "$ROOTFS" || die "$ROOTFS not empty!"
  346. # We've been working with this as root for a while now, so this makes
  347. # sure the permissions are sane.
  348. chmod 644 "$FILENAME"
  349. # The standard images are ready to go, but the cloud images require
  350. # some minimal additional post processing.
  351. case "$PLATFORM" in
  352. GCP*)
  353. # This filename is mandated by the Google Cloud Engine import
  354. # process, the archive name is not.
  355. mv "$FILENAME" disk.raw
  356. info_msg "Compressing disk.raw"
  357. tar Sczf "${FILENAME%.img}.tar.gz" disk.raw
  358. # Since this process just produces something that can be
  359. # uploaded, we remove the original disk image.
  360. rm disk.raw
  361. info_msg "Sucessfully created ${FILENAME%.img}.tar.gz image."
  362. ;;
  363. *)
  364. info_msg "Compressing $FILENAME with xz (level 9 compression)"
  365. xz "-T${COMPRESSOR_THREADS:-0}" -9 "$FILENAME"
  366. info_msg "Successfully created $FILENAME image."
  367. ;;
  368. esac