mkimage.sh 15 KB

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