mkparabolaiso 91 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161
  1. #!/usr/bin/env bash
  2. #
  3. # SPDX-License-Identifier: GPL-3.0-or-later
  4. set -e -u
  5. shopt -s extglob
  6. # Control the environment
  7. umask 0022
  8. export LC_ALL="C.UTF-8"
  9. if [[ -v LANGUAGE ]]; then
  10. # LC_ALL=C.UTF-8, unlike LC_ALL=C, does not override LANGUAGE.
  11. # See https://sourceware.org/bugzilla/show_bug.cgi?id=16621 and https://savannah.gnu.org/bugs/?62815
  12. unset LANGUAGE
  13. fi
  14. [[ -v SOURCE_DATE_EPOCH ]] || printf -v SOURCE_DATE_EPOCH '%(%s)T' -1
  15. export SOURCE_DATE_EPOCH
  16. # Set application name from the script's file name
  17. app_name="${0##*/}"
  18. # Define global variables. All of them will be overwritten later
  19. pkg_list=()
  20. bootstrap_pkg_list=()
  21. quiet=""
  22. work_dir=""
  23. out_dir=""
  24. gpg_key=""
  25. gpg_sender=""
  26. iso_name=""
  27. iso_label=""
  28. iso_uuid=""
  29. iso_publisher=""
  30. iso_application=""
  31. iso_version=""
  32. install_dir=""
  33. arch=""
  34. pacman_conf=""
  35. packages=""
  36. packages_dual=""
  37. bootstrap_packages=""
  38. bootstrap_packages_dual=""
  39. pacstrap_dir=""
  40. declare -i rm_work_dir=0
  41. buildmodes=()
  42. bootmodes=()
  43. airootfs_image_type=""
  44. airootfs_image_tool_options=()
  45. cert_list=()
  46. declare -A file_permissions=()
  47. efibootimg=""
  48. efiboot_files=()
  49. # Show an INFO message
  50. # $1: message string
  51. _msg_info() {
  52. local _msg="${1}"
  53. [[ "${quiet}" == "y" ]] || printf '[%s] INFO: %s\n' "${app_name}" "${_msg}"
  54. }
  55. # Show a WARNING message
  56. # $1: message string
  57. _msg_warning() {
  58. local _msg="${1}"
  59. printf '[%s] WARNING: %s\n' "${app_name}" "${_msg}" >&2
  60. }
  61. # Show an ERROR message then exit with status
  62. # $1: message string
  63. # $2: exit code number (with 0 does not exit)
  64. _msg_error() {
  65. local _msg="${1}"
  66. local _error=${2}
  67. printf '[%s] ERROR: %s\n' "${app_name}" "${_msg}" >&2
  68. if (( _error > 0 )); then
  69. exit "${_error}"
  70. fi
  71. }
  72. # Show help usage, with an exit status.
  73. # $1: exit status number.
  74. _usage() {
  75. IFS='' read -r -d '' usagetext <<ENDUSAGETEXT || true
  76. usage: ${app_name} [options] <profile_dir>
  77. options:
  78. -A <application> Set an application name for the ISO
  79. Default: '${iso_application}'
  80. -C <file> pacman configuration file.
  81. Default: '${pacman_conf}'
  82. -D <install_dir> Set an install_dir. All files will be located here.
  83. Default: '${install_dir}'
  84. NOTE: Max 8 characters, use only [a-z0-9]
  85. -L <label> Set the ISO volume label
  86. Default: '${iso_label}'
  87. -P <publisher> Set the ISO publisher
  88. Default: '${iso_publisher}'
  89. -c [cert ..] Provide certificates for codesigning of netboot artifacts as
  90. well as the rootfs artifact.
  91. Multiple files are provided as quoted, space delimited list.
  92. The first file is considered as the signing certificate,
  93. the second as the key.
  94. -g <gpg_key> Set the PGP key ID to be used for signing the rootfs image.
  95. Passed to gpg as the value for --default-key
  96. -G <mbox> Set the PGP signer (must include an email address)
  97. Passed to gpg as the value for --sender
  98. -h This message
  99. -m [mode ..] Build mode(s) to use (valid modes are: 'bootstrap', 'iso' and 'netboot').
  100. Multiple build modes are provided as quoted, space delimited list.
  101. -o <out_dir> Set the output directory
  102. Default: '${out_dir}'
  103. -p [package ..] Package(s) to install.
  104. Multiple packages are provided as quoted, space delimited list.
  105. -r Delete the working directory at the end.
  106. -v Enable verbose output
  107. -w <work_dir> Set the working directory
  108. Default: '${work_dir}'
  109. profile_dir: Directory of the parabolaiso profile to build
  110. ENDUSAGETEXT
  111. printf '%s' "${usagetext}"
  112. exit "${1}"
  113. }
  114. # Shows configuration options.
  115. _show_config() {
  116. local build_date
  117. printf -v build_date '%(%FT%R%z)T' "${SOURCE_DATE_EPOCH}"
  118. _msg_info "${app_name} configuration settings"
  119. _msg_info " Architecture: ${arch}"
  120. _msg_info " Working directory: ${work_dir}"
  121. _msg_info " Installation directory: ${install_dir}"
  122. _msg_info " Build date: ${build_date}"
  123. _msg_info " Output directory: ${out_dir}"
  124. _msg_info " Current build mode: ${buildmode}"
  125. _msg_info " Build modes: ${buildmodes[*]}"
  126. _msg_info " GPG key: ${gpg_key:-None}"
  127. _msg_info " GPG signer: ${gpg_sender:-None}"
  128. _msg_info "Code signing certificates: ${cert_list[*]:-None}"
  129. _msg_info " Profile: ${profile}"
  130. _msg_info "Pacman configuration file: ${pacman_conf}"
  131. _msg_info " Image file name: ${image_name:-None}"
  132. _msg_info " ISO volume label: ${iso_label}"
  133. _msg_info " ISO publisher: ${iso_publisher}"
  134. _msg_info " ISO application: ${iso_application}"
  135. _msg_info " Boot modes: ${bootmodes[*]:-None}"
  136. _msg_info " Packages File: ${buildmode_packages}"
  137. _msg_info " Packages: ${buildmode_pkg_list[*]}"
  138. if [[ "${arch}" == "dual" ]]; then
  139. _msg_info " Packages (i686): ${buildmode_pkg_list_i686[*]:-None}"
  140. _msg_info " Packages (x86_64): ${buildmode_pkg_list_x86_64[*]:-None}"
  141. fi
  142. }
  143. # Cleanup airootfs
  144. _cleanup_pacstrap_dir() {
  145. _msg_info "Cleaning up in ${arch} pacstrap location..."
  146. # Delete all files in /boot
  147. [[ -d "${pacstrap_dir}/boot" ]] && find "${pacstrap_dir}/boot" -mindepth 1 -delete
  148. # Delete pacman database sync cache files (*.tar.gz)
  149. [[ -d "${pacstrap_dir}/var/lib/pacman" ]] && find "${pacstrap_dir}/var/lib/pacman" -maxdepth 1 -type f -delete
  150. # Delete pacman database sync cache
  151. [[ -d "${pacstrap_dir}/var/lib/pacman/sync" ]] && find "${pacstrap_dir}/var/lib/pacman/sync" -delete
  152. # Delete pacman package cache
  153. [[ -d "${pacstrap_dir}/var/cache/pacman/pkg" ]] && find "${pacstrap_dir}/var/cache/pacman/pkg" -type f -delete
  154. # Delete all log files, keeps empty dirs.
  155. [[ -d "${pacstrap_dir}/var/log" ]] && find "${pacstrap_dir}/var/log" -type f -delete
  156. # Delete all temporary files and dirs
  157. [[ -d "${pacstrap_dir}/var/tmp" ]] && find "${pacstrap_dir}/var/tmp" -mindepth 1 -delete
  158. # Delete package pacman related files.
  159. find "${work_dir}" \( -name '*.pacnew' -o -name '*.pacsave' -o -name '*.pacorig' \) -delete
  160. # Create /etc/machine-id with special value 'uninitialized': the final id is
  161. # generated on first boot, systemd's first-boot mechanism applies (see machine-id(5))
  162. rm -f -- "${pacstrap_dir}/etc/machine-id"
  163. printf 'uninitialized\n' >"${pacstrap_dir}/etc/machine-id"
  164. _msg_info "Done!"
  165. }
  166. # Create a squashfs image and place it in the ISO 9660 file system.
  167. # $@: options to pass to mksquashfs
  168. _run_mksquashfs() {
  169. local mksquashfs_options=() image_path="${isofs_dir}/${install_dir}/${arch}/airootfs.sfs"
  170. rm -f -- "${image_path}"
  171. [[ ! "${quiet}" == "y" ]] || mksquashfs_options+=('-no-progress' '-quiet')
  172. mksquashfs "$@" "${image_path}" -noappend "${airootfs_image_tool_options[@]}" "${mksquashfs_options[@]}"
  173. }
  174. # Create an ext4 image containing the root file system and pack it inside a squashfs image.
  175. # Save the squashfs image on the ISO 9660 file system.
  176. _mkairootfs_ext4+squashfs() {
  177. local ext4_hash_seed mkfs_ext4_options=()
  178. [[ -e "${pacstrap_dir}" ]] || _msg_error "The path '${pacstrap_dir}' does not exist" 1
  179. _msg_info "Creating ext4 image of 32 GiB and copying '${pacstrap_dir}/' to it..."
  180. ext4_hash_seed="$(uuidgen --sha1 --namespace 93a870ff-8565-4cf3-a67b-f47299271a96 \
  181. --name "${SOURCE_DATE_EPOCH} ext4 hash seed")"
  182. mkfs_ext4_options=(
  183. '-d' "${pacstrap_dir}"
  184. '-O' '^has_journal,^resize_inode'
  185. '-E' "lazy_itable_init=0,root_owner=0:0,hash_seed=${ext4_hash_seed}"
  186. '-m' '0'
  187. '-F'
  188. '-U' 'clear'
  189. )
  190. [[ ! "${quiet}" == "y" ]] || mkfs_ext4_options+=('-q')
  191. rm -f -- "${pacstrap_dir}.img"
  192. E2FSPROGS_FAKE_TIME="${SOURCE_DATE_EPOCH}" mkfs.ext4 "${mkfs_ext4_options[@]}" -- "${pacstrap_dir}.img" 32G
  193. tune2fs -c 0 -i 0 -- "${pacstrap_dir}.img" >/dev/null
  194. _msg_info "Done!"
  195. install -d -m 0755 -- "${isofs_dir}/${install_dir}/${arch}"
  196. _msg_info "Creating SquashFS image, this may take some time..."
  197. _run_mksquashfs "${pacstrap_dir}.img"
  198. _msg_info "Done!"
  199. rm -- "${pacstrap_dir}.img"
  200. }
  201. # Create a squashfs image containing the root file system and saves it on the ISO 9660 file system.
  202. _mkairootfs_squashfs() {
  203. [[ -e "${pacstrap_dir}" ]] || _msg_error "The path '${pacstrap_dir}' does not exist" 1
  204. install -d -m 0755 -- "${isofs_dir}/${install_dir}/${arch}"
  205. _msg_info "Creating ${arch} SquashFS image, this may take some time..."
  206. _run_mksquashfs "${pacstrap_dir}"
  207. }
  208. # Create an EROFS image containing the root file system and saves it on the ISO 9660 file system.
  209. _mkairootfs_erofs() {
  210. local fsuuid mkfs_erofs_options=()
  211. [[ -e "${pacstrap_dir}" ]] || _msg_error "The path '${pacstrap_dir}' does not exist" 1
  212. install -d -m 0755 -- "${isofs_dir}/${install_dir}/${arch}"
  213. local image_path="${isofs_dir}/${install_dir}/${arch}/airootfs.erofs"
  214. rm -f -- "${image_path}"
  215. [[ ! "${quiet}" == "y" ]] || mkfs_erofs_options+=('--quiet')
  216. # Generate reproducible file system UUID from SOURCE_DATE_EPOCH
  217. fsuuid="$(uuidgen --sha1 --namespace 93a870ff-8565-4cf3-a67b-f47299271a96 --name "${SOURCE_DATE_EPOCH}")"
  218. mkfs_erofs_options+=('-U' "${fsuuid}" "${airootfs_image_tool_options[@]}")
  219. _msg_info "Creating EROFS image, this may take some time..."
  220. mkfs.erofs "${mkfs_erofs_options[@]}" -- "${image_path}" "${pacstrap_dir}"
  221. _msg_info "Done!"
  222. }
  223. # Create checksum file for the rootfs image.
  224. _mkchecksum() {
  225. _msg_info "Creating checksum file for self-test..."
  226. cd -- "${isofs_dir}/${install_dir}/${arch}"
  227. if [[ -e "${isofs_dir}/${install_dir}/${arch}/airootfs.sfs" ]]; then
  228. sha512sum airootfs.sfs >airootfs.sha512
  229. elif [[ -e "${isofs_dir}/${install_dir}/${arch}/airootfs.erofs" ]]; then
  230. sha512sum airootfs.erofs >airootfs.sha512
  231. fi
  232. cd -- "${OLDPWD}"
  233. _msg_info "Done!"
  234. }
  235. # GPG sign the root file system image.
  236. _mk_pgp_signature() {
  237. local gpg_options=()
  238. local airootfs_image_filename="${1}"
  239. _msg_info "Signing rootfs image using GPG..."
  240. rm -f -- "${airootfs_image_filename}.sig"
  241. # Add gpg sender option if the value is provided
  242. [[ -z "${gpg_sender}" ]] || gpg_options+=('--sender' "${gpg_sender}")
  243. # always use the .sig file extension, as that is what mkinitcpio-parabolaiso's hooks expect
  244. gpg --batch --no-armor --no-include-key-block --output "${airootfs_image_filename}.sig" --detach-sign \
  245. --default-key "${gpg_key}" "${gpg_options[@]}" "${airootfs_image_filename}"
  246. _msg_info "Done!"
  247. }
  248. # Helper function to run functions only one time.
  249. # $1: function name
  250. _run_once() {
  251. if [[ ! -e "${work_dir}/${run_once_mode}.${1}.${arch}" ]]; then
  252. "$1"
  253. touch "${work_dir}/${run_once_mode}.${1}.${arch}"
  254. fi
  255. }
  256. # Helper function to run commands for the i686 and x86_64 architectures.
  257. # $@: commands to run in both architectures
  258. _run_dual() {
  259. local architectures="${arch}"
  260. local arch
  261. local cmd
  262. if [[ "${architectures}" == "dual" ]]; then
  263. architectures="i686 x86_64"
  264. fi
  265. for arch in ${architectures}; do
  266. pacstrap_dir="${work_dir}/${arch}/airootfs"
  267. if [[ "${buildmode}" == "bootstrap" ]]; then
  268. pacstrap_dir="${work_dir}/${arch}/bootstrap/root.${arch}"
  269. fi
  270. for cmd in "$@"; do
  271. ${cmd}
  272. done
  273. done
  274. }
  275. # Set up custom pacman.conf with custom cache and pacman hook directories.
  276. _make_pacman_conf() {
  277. local _cache_dirs _system_cache_dirs _profile_cache_dirs
  278. _system_cache_dirs="$(pacman-conf CacheDir | tr '\n' ' ')"
  279. _profile_cache_dirs="$(pacman-conf --config "${pacman_conf}" CacheDir | tr '\n' ' ')"
  280. # Only use the profile's CacheDir, if it is not the default and not the same as the system cache dir.
  281. if [[ "${_profile_cache_dirs}" != "/var/cache/pacman/pkg" ]] \
  282. && [[ "${_system_cache_dirs}" != "${_profile_cache_dirs}" ]]; then
  283. _cache_dirs="${_profile_cache_dirs}"
  284. else
  285. _cache_dirs="${_system_cache_dirs}"
  286. fi
  287. _msg_info "Copying custom pacman.conf to ${arch} work directory..."
  288. _msg_info "Using pacman CacheDir: ${_cache_dirs}"
  289. # take the profile pacman.conf and strip all settings that would break in chroot when using pacman -r
  290. # append CacheDir and HookDir to [options] section
  291. # HookDir is *always* set to the airootfs' override directory
  292. # see `man 8 pacman` for further info
  293. sed "/Architecture/d;/\[options\]/a Architecture = ${arch}" "${pacman_conf}" | \
  294. pacman-conf --config /dev/stdin \
  295. | sed "/CacheDir/d;/DBPath/d;/HookDir/d;/LogFile/d;/RootDir/d;/\[options\]/a CacheDir = ${_cache_dirs}
  296. /\[options\]/a HookDir = ${pacstrap_dir}/etc/pacman.d/hooks/" >"${work_dir}/${buildmode}.pacman.conf.${arch}"
  297. }
  298. # Prepare working directory and copy custom root file system files.
  299. _make_custom_airootfs() {
  300. local passwd=()
  301. local filename permissions
  302. install -d -m 0755 -o 0 -g 0 -- "${pacstrap_dir}"
  303. if [[ -d "${profile}/airootfs" ]]; then
  304. _msg_info "Copying custom ${arch} airootfs files..."
  305. cp -af --no-preserve=ownership,mode -- "${profile}/airootfs/." "${pacstrap_dir}"
  306. # Set ownership and mode for files and directories
  307. for filename in "${!file_permissions[@]}"; do
  308. IFS=':' read -ra permissions <<<"${file_permissions["${filename}"]}"
  309. # Prevent file path traversal outside of $pacstrap_dir
  310. if [[ "$(realpath -q -- "${pacstrap_dir}${filename}")" != "${pacstrap_dir}"* ]]; then
  311. _msg_error "Failed to set permissions on '${pacstrap_dir}${filename}'. Outside of valid path." 1
  312. # Warn if the file does not exist
  313. elif [[ ! -e "${pacstrap_dir}${filename}" ]]; then
  314. _msg_warning "Cannot change permissions of '${pacstrap_dir}${filename}'. The file or directory does not exist."
  315. else
  316. if [[ "${filename: -1}" == "/" ]]; then
  317. chown -fhR -- "${permissions[0]}:${permissions[1]}" "${pacstrap_dir}${filename}"
  318. chmod -fR -- "${permissions[2]}" "${pacstrap_dir}${filename}"
  319. else
  320. chown -fh -- "${permissions[0]}:${permissions[1]}" "${pacstrap_dir}${filename}"
  321. chmod -f -- "${permissions[2]}" "${pacstrap_dir}${filename}"
  322. fi
  323. fi
  324. done
  325. _msg_info "Done!"
  326. fi
  327. }
  328. # Install desired packages to the root file system
  329. _make_packages() {
  330. _msg_info "Installing packages to '${pacstrap_dir}/'..."
  331. local buildmode_pkg_list_arch
  332. eval "buildmode_pkg_list_arch=(\${buildmode_pkg_list_${arch}[@]})"
  333. if [[ -v gpg_publickey ]]; then
  334. exec {PARABOLAISO_GNUPG_FD}<"$gpg_publickey"
  335. export PARABOLAISO_GNUPG_FD
  336. fi
  337. if [[ -v cert_list[0] ]]; then
  338. exec {PARABOLAISO_TLS_FD}<"${cert_list[0]}"
  339. export PARABOLAISO_TLS_FD
  340. fi
  341. if [[ -v cert_list[2] ]]; then
  342. exec {PARABOLAISO_TLSCA_FD}<"${cert_list[2]}"
  343. export PARABOLAISO_TLSCA_FD
  344. fi
  345. # Install the qemu-arm-static binary
  346. if [[ "${arch}" == "armv7h" ]] && ! setarch armv7l /bin/true 2>/dev/null; then
  347. # Make sure that qemu-static is set up with binfmt_misc
  348. if [[ -z $(grep -l -xF \
  349. -e "interpreter /usr/bin/qemu-arm-static" \
  350. -r -- /proc/sys/fs/binfmt_misc 2>/dev/null \
  351. | xargs -r grep -xF 'enabled') ]]; then
  352. # Register the qemu-arm-static as an ARM interpreter in the kernel (using binfmt_misc kernel module)
  353. printf ':arm:M::\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x28\x00:\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff:/usr/bin/qemu-arm-static:' >/proc/sys/fs/binfmt_misc/register
  354. fi
  355. install -d -m 0755 -- "${pacstrap_dir}/usr/bin"
  356. install -m 0755 -- /usr/bin/qemu-arm-static "${pacstrap_dir}/usr/bin"
  357. fi
  358. # Unset TMPDIR to work around https://bugs.archlinux.org/task/70580
  359. if [[ "${quiet}" = "y" ]]; then
  360. env -u TMPDIR pacstrap -C "${work_dir}/${buildmode}.pacman.conf.${arch}" -c -G -M -- "${pacstrap_dir}" "${buildmode_pkg_list[@]}" "${buildmode_pkg_list_arch[@]}" &>/dev/null
  361. else
  362. env -u TMPDIR pacstrap -C "${work_dir}/${buildmode}.pacman.conf.${arch}" -c -G -M -- "${pacstrap_dir}" "${buildmode_pkg_list[@]}" "${buildmode_pkg_list_arch[@]}"
  363. fi
  364. # Delete the qemu-arm-static binary
  365. if [[ "${arch}" == "armv7h" ]] && ! setarch armv7l /bin/true 2>/dev/null; then
  366. rm -f -- "${pacstrap_dir}/usr/bin/qemu-arm-static"
  367. fi
  368. if [[ -v cert_list[0] ]]; then
  369. exec {PARABOLAISO_TLS_FD}<&-
  370. unset PARABOLAISO_TLS_FD
  371. fi
  372. if [[ -v cert_list[2] ]]; then
  373. exec {PARABOLAISO_TLSCA_FD}<&-
  374. unset PARABOLAISO_TLSCA_FD
  375. fi
  376. if [[ -v gpg_publickey ]]; then
  377. exec {PARABOLAISO_GNUPG_FD}<&-
  378. unset PARABOLAISO_GNUPG_FD
  379. fi
  380. _msg_info "Done! Packages installed successfully."
  381. }
  382. # Customize installation.
  383. _make_customize_airootfs() {
  384. local passwd=()
  385. if [[ -e "${profile}/airootfs/etc/passwd" ]]; then
  386. _msg_info "Copying /etc/skel/* to ${arch} user homes..."
  387. while IFS=':' read -a passwd -r; do
  388. # Only operate on UIDs in range 1000–59999
  389. (( passwd[2] >= 1000 && passwd[2] < 60000 )) || continue
  390. # Skip invalid home directories
  391. [[ "${passwd[5]}" == '/' ]] && continue
  392. [[ -z "${passwd[5]}" ]] && continue
  393. # Prevent path traversal outside of $pacstrap_dir
  394. if [[ "$(realpath -q -- "${pacstrap_dir}${passwd[5]}")" == "${pacstrap_dir}"* ]]; then
  395. if [[ ! -d "${pacstrap_dir}${passwd[5]}" ]]; then
  396. install -d -m 0750 -o "${passwd[2]}" -g "${passwd[3]}" -- "${pacstrap_dir}${passwd[5]}"
  397. fi
  398. cp -dRT --update=none --preserve=mode,timestamps,links -- "${pacstrap_dir}/etc/skel/." "${pacstrap_dir}${passwd[5]}"
  399. chmod -f 0750 -- "${pacstrap_dir}${passwd[5]}"
  400. chown -hR -- "${passwd[2]}:${passwd[3]}" "${pacstrap_dir}${passwd[5]}"
  401. else
  402. _msg_error "Failed to set permissions on '${pacstrap_dir}${passwd[5]}'. Outside of valid path." 1
  403. fi
  404. done <"${profile}/airootfs/etc/passwd"
  405. _msg_info "Done!"
  406. fi
  407. if [[ -e "${pacstrap_dir}/root/customize_airootfs.sh" ]]; then
  408. _msg_info "Running customize_airootfs.sh in '${pacstrap_dir}' chroot..."
  409. _msg_warning "customize_airootfs.sh is deprecated! Support for it will be removed in a future parabolaiso version."
  410. chmod -f -- +x "${pacstrap_dir}/root/customize_airootfs.sh"
  411. # Unset TMPDIR to work around https://bugs.archlinux.org/task/70580
  412. eval -- env -u TMPDIR arch-chroot "${pacstrap_dir}" "/root/customize_airootfs.sh"
  413. rm -- "${pacstrap_dir}/root/customize_airootfs.sh"
  414. _msg_info "Done! customize_airootfs.sh run successfully."
  415. fi
  416. }
  417. # Set up boot loaders
  418. _make_bootmodes() {
  419. local bootmode
  420. for bootmode in "${bootmodes[@]}"; do
  421. _run_once "_make_bootmode_${bootmode}"
  422. done
  423. if [[ "${bootmodes[*]}" != *grub* ]]; then
  424. _run_once _make_common_grubenv_and_loopbackcfg
  425. fi
  426. }
  427. # Copy kernel and initramfs to ISO 9660
  428. _make_boot_on_iso9660() {
  429. _msg_info "Preparing ${arch} kernel and initramfs for the ISO 9660 file system..."
  430. install -d -m 0755 -- "${isofs_dir}/${install_dir}/boot/${arch}"
  431. install -m 0644 -- "${pacstrap_dir}/boot/initramfs-"*".img" "${isofs_dir}/${install_dir}/boot/${arch}/"
  432. install -m 0644 -- "${pacstrap_dir}/boot/vmlinuz-"* "${isofs_dir}/${install_dir}/boot/${arch}/"
  433. _msg_info "Done!"
  434. }
  435. # Prepare syslinux for booting from MBR (isohybrid)
  436. _make_bootmode_bios.syslinux.mbr() {
  437. _msg_info "Setting up SYSLINUX for BIOS booting from a disk..."
  438. install -d -m 0755 -- "${isofs_dir}/boot/syslinux"
  439. for _cfg in "${profile}/syslinux/"*.cfg; do
  440. sed "s|%PARABOLAISO_LABEL%|${iso_label}|g;
  441. s|%PARABOLAISO_UUID%|${iso_uuid}|g;
  442. s|%INSTALL_DIR%|${install_dir}|g;
  443. s|%ARCH%|${arch}|g" \
  444. "${_cfg}" >"${isofs_dir}/boot/syslinux/${_cfg##*/}"
  445. done
  446. if [[ -e "${profile}/syslinux/splash.png" ]]; then
  447. install -m 0644 -- "${profile}/syslinux/splash.png" "${isofs_dir}/boot/syslinux/"
  448. fi
  449. install -m 0644 -- "${pacstrap_dir}/usr/lib/syslinux/bios/"*.c32 "${isofs_dir}/boot/syslinux/"
  450. install -m 0644 -- "${pacstrap_dir}/usr/lib/syslinux/bios/lpxelinux.0" "${isofs_dir}/boot/syslinux/"
  451. install -m 0644 -- "${pacstrap_dir}/usr/lib/syslinux/bios/memdisk" "${isofs_dir}/boot/syslinux/"
  452. _run_dual '_run_once _make_boot_on_iso9660'
  453. if [[ -e "${isofs_dir}/boot/syslinux/hdt.c32" ]]; then
  454. install -d -m 0755 -- "${isofs_dir}/boot/syslinux/hdt"
  455. if [[ -e "${pacstrap_dir}/usr/share/hwdata/pci.ids" ]]; then
  456. gzip -cn9 "${pacstrap_dir}/usr/share/hwdata/pci.ids" > \
  457. "${isofs_dir}/boot/syslinux/hdt/pciids.gz"
  458. fi
  459. find "${pacstrap_dir}/usr/lib/modules" -name 'modules.alias' -print -exec gzip -cn9 '{}' ';' -quit > \
  460. "${isofs_dir}/boot/syslinux/hdt/modalias.gz"
  461. fi
  462. # Add other aditional/extra files to ${install_dir}/boot/
  463. if [[ -e "${pacstrap_dir}/boot/memtest86+/memtest.bin" ]]; then
  464. install -d -m 0755 -- "${isofs_dir}/boot/memtest86+/"
  465. # rename for PXE: https://wiki.parabola.nu/index.php/Syslinux#Using_memtest
  466. install -m 0644 -- "${pacstrap_dir}/boot/memtest86+/memtest.bin" "${isofs_dir}/boot/memtest86+/memtest"
  467. install -m 0644 -- "${pacstrap_dir}/usr/share/licenses/common/GPL2/license.txt" "${isofs_dir}/boot/memtest86+/"
  468. fi
  469. _msg_info "Done! SYSLINUX set up for BIOS booting from a disk successfully."
  470. }
  471. # Prepare syslinux for El-Torito booting
  472. _make_bootmode_bios.syslinux.eltorito() {
  473. _msg_info "Setting up SYSLINUX for BIOS booting from an optical disc..."
  474. install -d -m 0755 -- "${isofs_dir}/boot/syslinux"
  475. install -m 0644 -- "${pacstrap_dir}/usr/lib/syslinux/bios/isolinux.bin" "${isofs_dir}/boot/syslinux/"
  476. install -m 0644 -- "${pacstrap_dir}/usr/lib/syslinux/bios/isohdpfx.bin" "${isofs_dir}/boot/syslinux/"
  477. # ISOLINUX and SYSLINUX installation is shared
  478. _run_once _make_bootmode_bios.syslinux.mbr
  479. _msg_info "Done! SYSLINUX set up for BIOS booting from an optical disc successfully."
  480. }
  481. # Copy kernel and initramfs to FAT image
  482. _make_boot_on_fat() {
  483. _msg_info "Preparing kernel and initramfs for the FAT file system..."
  484. mmd -i "${efibootimg}" \
  485. "::/${install_dir}" "::/${install_dir}/boot" "::/${install_dir}/boot/x86_64"
  486. mcopy -i "${efibootimg}" "${pacstrap_dir}/boot/vmlinuz-"* \
  487. "${pacstrap_dir}/boot/initramfs-"*".img" "::/${install_dir}/boot/x86_64/"
  488. _msg_info "Done!"
  489. }
  490. # Create a FAT image (efiboot.img) which will serve as the EFI system partition
  491. # $1: image size in bytes
  492. _make_efibootimg() {
  493. local imgsize_kib="0"
  494. local imgsize_bytes=${1}
  495. if (( imgsize_bytes < 2*1024*1024 )); then
  496. _msg_info "Validating '${bootmode}': efiboot.img size is ${imgsize_bytes} bytes is less than 2 MiB! Bumping up to 2 MiB"
  497. imgsize_bytes=$((2*1024*1024))
  498. fi
  499. # Convert from bytes to KiB and round up to the next full MiB with an additional MiB for reserved sectors.
  500. imgsize_kib="$(
  501. awk 'function ceil(x){return int(x)+(x>int(x))}
  502. function byte_to_kib(x){return x/1024}
  503. function mib_to_kib(x){return x*1024}
  504. END {print mib_to_kib(ceil((byte_to_kib($1)+1024)/1024))}' <<<"${imgsize_bytes}"
  505. )"
  506. # The FAT image must be created with mkfs.fat not mformat, as some systems have issues with mformat made images:
  507. # https://lists.gnu.org/archive/html/grub-devel/2019-04/msg00099.html
  508. rm -f -- "${efibootimg}"
  509. _msg_info "Creating FAT image of size: ${imgsize_kib} KiB..."
  510. if [[ "${quiet}" == "y" ]]; then
  511. # mkfs.fat does not have a -q/--quiet option, so redirect stdout to /dev/null instead
  512. # https://github.com/dosfstools/dosfstools/issues/103
  513. mkfs.fat -C -n PARAISO_EFI "${efibootimg}" "${imgsize_kib}" >/dev/null
  514. else
  515. mkfs.fat -C -n PARAISO_EFI "${efibootimg}" "${imgsize_kib}"
  516. fi
  517. # Create the default/fallback boot path in which a boot loaders will be placed later.
  518. mmd -i "${efibootimg}" ::/EFI ::/EFI/BOOT
  519. }
  520. # Copy GRUB files to ISO 9660 which is used by both IA32 UEFI and x64 UEFI
  521. _make_common_bootmode_grub_copy_to_isofs() {
  522. local files_to_copy=()
  523. files_to_copy+=("${work_dir}/grub/"*)
  524. if compgen -G "${profile}/grub/!(*.cfg)" &>/dev/null; then
  525. files_to_copy+=("${profile}/grub/"!(*.cfg))
  526. fi
  527. install -d -m 0755 -- "${isofs_dir}/boot/grub"
  528. cp -r --remove-destination -- "${files_to_copy[@]}" "${isofs_dir}/boot/grub/"
  529. }
  530. # Prepare GRUB configuration files
  531. _make_common_bootmode_grub_cfg() {
  532. local _cfg search_filename
  533. install -d -- "${work_dir}/grub"
  534. # Create a /boot/grub/YYYY-mm-dd-HH-MM-SS-00.uuid file on ISO 9660. GRUB will search for it to find the ISO
  535. # volume. This is similar to what grub-mkrescue does, except it places the file in /.disk/, but we opt to use a
  536. # directory that does not start with a dot to avoid it being accidentally missed when copying the ISO's contents.
  537. : >"${work_dir}/grub/${iso_uuid}.uuid"
  538. search_filename="/boot/grub/${iso_uuid}.uuid"
  539. # Fill GRUB configuration files
  540. for _cfg in "${profile}/grub/"*'.cfg'; do
  541. sed "s|%PARABOLAISO_LABEL%|${iso_label}|g;
  542. s|%PARABOLAISO_UUID%|${iso_uuid}|g;
  543. s|%INSTALL_DIR%|${install_dir}|g;
  544. s|%ARCH%|${arch}|g;
  545. s|%PARABOLAISO_SEARCH_FILENAME%|${search_filename}|g" \
  546. "${_cfg}" >"${work_dir}/grub/${_cfg##*/}"
  547. done
  548. # Prepare grub.cfg that will be embedded inside the GRUB binaries
  549. IFS='' read -r -d '' grubembedcfg <<'EOF' || true
  550. if ! [ -d "$cmdpath" ]; then
  551. # On some firmware, GRUB has a wrong cmdpath when booted from an optical disc. During El Torito boot, GRUB is
  552. # launched from a case-insensitive FAT-formatted EFI system partition, but it seemingly cannot access that partition
  553. # and sets cmdpath to the whole cd# device which has case-sensitive ISO 9660 + Rock Ridge + Joliet file systems.
  554. # See https://gitlab.archlinux.org/archlinux/archiso/-/issues/183 and https://savannah.gnu.org/bugs/?62886
  555. if regexp --set=1:parabolaiso_bootdevice '^\(([^)]+)\)\/?[Ee][Ff][Ii]\/[Bb][Oo][Oo][Tt]\/?$' "${cmdpath}"; then
  556. set cmdpath="(${parabolaiso_bootdevice})/EFI/BOOT"
  557. set PARABOLAISO_HINT="${parabolaiso_bootdevice}"
  558. fi
  559. fi
  560. # Prepare a hint for the search command using the device in cmdpath
  561. if [ -z "${PARABOLAISO_HINT}" ]; then
  562. regexp --set=1:PARABOLAISO_HINT '^\(([^)]+)\)' "${cmdpath}"
  563. fi
  564. # Search for the ISO volume
  565. if search --no-floppy --set=parabolaiso_device --file '%PARABOLAISO_SEARCH_FILENAME%' --hint "${PARABOLAISO_HINT}"; then
  566. set PARABOLAISO_HINT="${parabolaiso_device}"
  567. if probe --set PARABOLAISO_UUID --fs-uuid "${PARABOLAISO_HINT}"; then
  568. export PARABOLAISO_UUID
  569. fi
  570. else
  571. echo "Could not find a volume with a '%PARABOLAISO_SEARCH_FILENAME%' file on it!"
  572. fi
  573. # Load grub.cfg
  574. if [ "${PARABOLAISO_HINT}" == 'memdisk' -o -z "${PARABOLAISO_HINT}" ]; then
  575. echo 'Could not find the ISO volume!'
  576. elif [ -e "(${PARABOLAISO_HINT})/boot/grub/grub.cfg" ]; then
  577. export PARABOLAISO_HINT
  578. set root="${PARABOLAISO_HINT}"
  579. configfile "(${PARABOLAISO_HINT})/boot/grub/grub.cfg"
  580. else
  581. echo "File '(${PARABOLAISO_HINT})/boot/grub/grub.cfg' not found!"
  582. fi
  583. EOF
  584. grubembedcfg="${grubembedcfg//'%PARABOLAISO_SEARCH_FILENAME%'/"${search_filename}"}"
  585. printf '%s\n' "$grubembedcfg" >"${work_dir}/grub-embed.cfg"
  586. # Write grubenv
  587. printf '%.1024s' \
  588. "$(printf '# GRUB Environment Block\nNAME=%s\nVERSION=%s\nPARABOLAISO_LABEL=%s\nINSTALL_DIR=%s\nARCH=%s\nPARABOLAISO_SEARCH_FILENAME=%s\n%s' \
  589. "${iso_name}" \
  590. "${iso_version}" \
  591. "${iso_label}" \
  592. "${install_dir}" \
  593. "${arch}" \
  594. "${search_filename}" \
  595. "$(printf '%0.1s' "#"{1..1024})")" \
  596. >"${work_dir}/grub/grubenv"
  597. }
  598. # Create GRUB specific configuration files when GRUB is not used as a boot loader
  599. _make_common_grubenv_and_loopbackcfg() {
  600. local search_filename
  601. install -d -m 0755 -- "${isofs_dir}/boot/grub"
  602. # Create a /boot/grub/YYYY-mm-dd-HH-MM-SS-00.uuid file on ISO 9660. GRUB will search for it to find the ISO
  603. # volume. This is similar to what grub-mkrescue does, except it places the file in /.disk/, but we opt to use a
  604. # directory that does not start with a dot to avoid it being accidentally missed when copying the ISO's contents.
  605. search_filename="/boot/grub/${iso_uuid}.uuid"
  606. : >"${isofs_dir}/${search_filename}"
  607. # Write grubenv
  608. printf '%.1024s' \
  609. "$(printf '# GRUB Environment Block\nNAME=%s\nVERSION=%s\nPARABOLAISO_LABEL=%s\nINSTALL_DIR=%s\nARCH=%s\nPARABOLAISO_SEARCH_FILENAME=%s\n%s' \
  610. "${iso_name}" \
  611. "${iso_version}" \
  612. "${iso_label}" \
  613. "${install_dir}" \
  614. "${arch}" \
  615. "${search_filename}" \
  616. "$(printf '%0.1s' "#"{1..1024})")" \
  617. >"${isofs_dir}/boot/grub/grubenv"
  618. # Copy loopback.cfg to /boot/grub/ on ISO 9660
  619. if [[ -e "${profile}/grub/loopback.cfg" ]]; then
  620. sed "s|%PARABOLAISO_LABEL%|${iso_label}|g;
  621. s|%PARABOLAISO_UUID%|${iso_uuid}|g;
  622. s|%INSTALL_DIR%|${install_dir}|g;
  623. s|%ARCH%|${arch}|g;
  624. s|%PARABOLAISO_SEARCH_FILENAME%|${search_filename}|g" \
  625. "${profile}/grub/loopback.cfg" >"${isofs_dir}/boot/grub/loopback.cfg"
  626. fi
  627. }
  628. _make_bootmode_uefi-ia32.grub.esp() {
  629. local grubmodules=()
  630. # Prepare configuration files
  631. _run_once _make_common_bootmode_grub_cfg
  632. # Create EFI binary
  633. # Module list from https://bugs.archlinux.org/task/71382#comment202911
  634. grubmodules=(all_video at_keyboard boot btrfs cat chain configfile echo efifwsetup efinet exfat ext2 f2fs fat font \
  635. gfxmenu gfxterm gzio halt hfsplus iso9660 jpeg keylayouts linux loadenv loopback lsefi lsefimmap \
  636. minicmd normal ntfs ntfscomp part_apple part_gpt part_msdos png read reboot regexp search \
  637. search_fs_file search_fs_uuid search_label serial sleep tpm udf usb usbserial_common usbserial_ftdi \
  638. usbserial_pl2303 usbserial_usbdebug video xfs zstd)
  639. grub-mkstandalone -O i386-efi \
  640. --modules="${grubmodules[*]}" \
  641. --locales="en@quot" \
  642. --themes="" \
  643. --disable-shim-lock \
  644. -o "${work_dir}/BOOTIA32.EFI" "boot/grub/grub.cfg=${work_dir}/grub-embed.cfg"
  645. # Add GRUB to the list of files used to calculate the required FAT image size.
  646. efiboot_files+=("${work_dir}/BOOTIA32.EFI"
  647. "${pacstrap_dir}/usr/share/edk2-shell/ia32/Shell_Full.efi")
  648. if [[ " ${bootmodes[*]} " =~ uefi-x64.systemd-boot.esp ]]; then
  649. # TODO: Remove this branch.
  650. _run_once _make_bootmode_uefi-x64.systemd-boot.esp
  651. elif [[ " ${bootmodes[*]} " =~ uefi-x64.grub.esp ]]; then
  652. _run_once _make_bootmode_uefi-x64.grub.esp
  653. else
  654. efiboot_imgsize="$(du -bcs -- "${efiboot_files[@]}" 2>/dev/null | awk 'END { print $1 }')"
  655. # Create a FAT image for the EFI system partition
  656. _make_efibootimg "$efiboot_imgsize"
  657. fi
  658. # Copy GRUB EFI binary to the default/fallback boot path
  659. mcopy -i "${efibootimg}" "${work_dir}/BOOTIA32.EFI" ::/EFI/BOOT/BOOTIA32.EFI
  660. # Copy GRUB files
  661. _run_once _make_common_bootmode_grub_copy_to_isofs
  662. if [[ -e "${pacstrap_dir}/usr/share/edk2-shell/ia32/Shell_Full.efi" ]]; then
  663. mcopy -i "${efibootimg}" "${pacstrap_dir}/usr/share/edk2-shell/ia32/Shell_Full.efi" ::/shellia32.efi
  664. fi
  665. _msg_info "Done! GRUB set up for UEFI booting successfully."
  666. }
  667. # Prepare GRUB for El Torito booting
  668. _make_bootmode_uefi-ia32.grub.eltorito() {
  669. # El Torito UEFI boot requires an image containing the EFI system partition.
  670. # uefi-ia32.grub.eltorito has the same requirements as uefi-ia32.grub.esp
  671. _run_once _make_bootmode_uefi-ia32.grub.esp
  672. # Prepare configuration files
  673. _run_once _make_common_bootmode_grub_cfg
  674. # Additionally set up systemd-boot in ISO 9660. This allows creating a medium for the live environment by using
  675. # manual partitioning and simply copying the ISO 9660 file system contents.
  676. # This is not related to El Torito booting and no firmware uses these files.
  677. _msg_info "Preparing an /EFI directory for the ISO 9660 file system..."
  678. install -d -m 0755 -- "${isofs_dir}/EFI/BOOT"
  679. # Copy GRUB EFI binary to the default/fallback boot path
  680. install -m 0644 -- "${work_dir}/BOOTIA32.EFI" "${isofs_dir}/EFI/BOOT/BOOTIA32.EFI"
  681. # Copy GRUB configuration files
  682. _run_once _make_common_bootmode_grub_copy_to_isofs
  683. # edk2-shell based UEFI shell
  684. if [[ -e "${pacstrap_dir}/usr/share/edk2-shell/ia32/Shell_Full.efi" ]]; then
  685. install -m 0644 -- "${pacstrap_dir}/usr/share/edk2-shell/ia32/Shell_Full.efi" "${isofs_dir}/shellia32.efi"
  686. fi
  687. _msg_info "Done!"
  688. }
  689. _make_bootmode_uefi-x64.grub.esp() {
  690. local grubmodules=()
  691. # Prepare configuration files
  692. _run_once _make_common_bootmode_grub_cfg
  693. # Create EFI binary
  694. # Module list from https://bugs.archlinux.org/task/71382#comment202911
  695. grubmodules=(all_video at_keyboard boot btrfs cat chain configfile echo efifwsetup efinet ext2 f2fs fat font \
  696. gfxmenu gfxterm gzio halt hfsplus iso9660 jpeg keylayouts linux loadenv loopback lsefi lsefimmap \
  697. minicmd normal part_apple part_gpt part_msdos png read reboot regexp search search_fs_file \
  698. search_fs_uuid search_label serial sleep tpm usb usbserial_common usbserial_ftdi usbserial_pl2303 \
  699. usbserial_usbdebug video xfs zstd)
  700. grub-mkstandalone -O x86_64-efi \
  701. --modules="${grubmodules[*]}" \
  702. --locales="en@quot" \
  703. --themes="" \
  704. --disable-shim-lock \
  705. -o "${work_dir}/BOOTx64.EFI" "boot/grub/grub.cfg=${work_dir}/grub-embed.cfg"
  706. # Add GRUB to the list of files used to calculate the required FAT image size.
  707. efiboot_files+=("${work_dir}/BOOTx64.EFI"
  708. "${pacstrap_dir}/usr/share/edk2-shell/x64/Shell_Full.efi")
  709. efiboot_imgsize="$(du -bcs -- "${efiboot_files[@]}" 2>/dev/null | awk 'END { print $1 }')"
  710. # Create a FAT image for the EFI system partition
  711. _make_efibootimg "$efiboot_imgsize"
  712. # Copy GRUB EFI binary to the default/fallback boot path
  713. mcopy -i "${efibootimg}" "${work_dir}/BOOTx64.EFI" ::/EFI/BOOT/BOOTx64.EFI
  714. # Copy GRUB files
  715. _run_once _make_common_bootmode_grub_copy_to_efibootimg
  716. if [[ -e "${pacstrap_dir}/usr/share/edk2-shell/x64/Shell_Full.efi" ]]; then
  717. mcopy -i "${efibootimg}" "${pacstrap_dir}/usr/share/edk2-shell/x64/Shell_Full.efi" ::/shellx64.efi
  718. fi
  719. # Add other aditional/extra files to ${install_dir}/boot/
  720. if [[ -e "${pacstrap_dir}/boot/memtest86+/memtest.efi" ]]; then
  721. install -d -m 0755 -- "${isofs_dir}/boot/memtest86+/"
  722. install -m 0644 -- "${pacstrap_dir}/boot/memtest86+/memtest.efi" "${isofs_dir}/boot/memtest86+/memtest.efi"
  723. install -m 0644 -- "${pacstrap_dir}/usr/share/licenses/common/GPL2/license.txt" "${isofs_dir}/boot/memtest86+/"
  724. fi
  725. _msg_info "Done! GRUB set up for UEFI booting successfully."
  726. }
  727. # Prepare GRUB for El Torito booting
  728. _make_bootmode_uefi-x64.grub.eltorito() {
  729. # El Torito UEFI boot requires an image containing the EFI system partition.
  730. # uefi-x64.grub.eltorito has the same requirements as uefi-x64.grub.esp
  731. _run_once _make_bootmode_uefi-x64.grub.esp
  732. # Prepare configuration files
  733. _run_once _make_common_bootmode_grub_cfg
  734. # Additionally set up systemd-boot in ISO 9660. This allows creating a medium for the live environment by using
  735. # manual partitioning and simply copying the ISO 9660 file system contents.
  736. # This is not related to El Torito booting and no firmware uses these files.
  737. _msg_info "Preparing an /EFI directory for the ISO 9660 file system..."
  738. install -d -m 0755 -- "${isofs_dir}/EFI/BOOT"
  739. # Copy GRUB EFI binary to the default/fallback boot path
  740. install -m 0644 -- "${work_dir}/BOOTx64.EFI" "${isofs_dir}/EFI/BOOT/BOOTx64.EFI"
  741. # Copy GRUB files
  742. _run_once _make_common_bootmode_grub_copy_to_isofs
  743. # edk2-shell based UEFI shell
  744. if [[ -e "${pacstrap_dir}/usr/share/edk2-shell/x64/Shell_Full.efi" ]]; then
  745. install -m 0644 -- "${pacstrap_dir}/usr/share/edk2-shell/x64/Shell_Full.efi" "${isofs_dir}/shellx64.efi"
  746. fi
  747. _msg_info "Done!"
  748. }
  749. _make_common_bootmode_systemd-boot() {
  750. local _file efiboot_imgsize
  751. # Calculate the required FAT image size in bytes
  752. # shellcheck disable=SC2076
  753. if [[ " ${bootmodes[*]} " =~ ' uefi-x64.systemd-boot.esp ' || " ${bootmodes[*]} " =~ ' uefi-x64.systemd-boot.eltorito ' ]]; then
  754. efiboot_files+=("${pacstrap_dir}/usr/lib/systemd/boot/efi/systemd-bootx64.efi"
  755. "${pacstrap_dir}/usr/share/edk2-shell/x64/Shell_Full.efi")
  756. fi
  757. # shellcheck disable=SC2076
  758. if [[ " ${bootmodes[*]} " =~ ' uefi-ia32.systemd-boot.esp ' || " ${bootmodes[*]} " =~ ' uefi-ia32.systemd-boot.eltorito ' ]]; then
  759. efiboot_files+=("${pacstrap_dir}/usr/lib/systemd/boot/efi/systemd-bootia32.efi"
  760. "${pacstrap_dir}/usr/share/edk2-shell/ia32/Shell_Full.efi")
  761. fi
  762. efiboot_files+=("${work_dir}/loader/"
  763. "${pacstrap_dir}/boot/vmlinuz-"*
  764. "${pacstrap_dir}/boot/initramfs-"*".img")
  765. efiboot_imgsize="$(du -bcs -- "${efiboot_files[@]}" 2>/dev/null | awk 'END { print $1 }')"
  766. # Create a FAT image for the EFI system partition
  767. _make_efibootimg "$efiboot_imgsize"
  768. }
  769. _make_common_bootmode_systemd-boot_conf() {
  770. local _conf
  771. install -d -m 0755 -- "${work_dir}/loader" "${work_dir}/loader/entries"
  772. install -m 0644 -- "${profile}/efiboot/loader/loader.conf" "${work_dir}/loader"
  773. for _conf in "${profile}/efiboot/loader/entries/"*".conf"; do
  774. sed "s|%PARABOLAISO_LABEL%|${iso_label}|g;
  775. s|%PARABOLAISO_UUID%|${iso_uuid}|g;
  776. s|%INSTALL_DIR%|${install_dir}|g;
  777. s|%ARCH%|${arch}|g" \
  778. "${_conf}" >"${work_dir}/loader/entries/${_conf##*/}"
  779. done
  780. }
  781. # Copy systemd-boot configuration files to ISO 9660
  782. _make_common_bootmode_systemd-boot_conf.isofs() {
  783. cp -r --remove-destination -- "${work_dir}/loader" "${isofs_dir}/"
  784. }
  785. # Copy systemd-boot configuration files to FAT image
  786. _make_common_bootmode_systemd-boot_conf.esp() {
  787. mcopy -i "${efibootimg}" -s "${work_dir}/loader" ::/
  788. }
  789. # Prepare systemd-boot for booting when written to a disk (isohybrid)
  790. _make_bootmode_uefi-x64.systemd-boot.esp() {
  791. _msg_info "Setting up systemd-boot for x64 UEFI booting..."
  792. # Prepare configuration files
  793. _run_once _make_common_bootmode_systemd-boot_conf
  794. # Prepare a FAT image for the EFI system partition
  795. _run_once _make_common_bootmode_systemd-boot
  796. # Copy systemd-boot EFI binary to the default/fallback boot path
  797. mcopy -i "${efibootimg}" \
  798. "${pacstrap_dir}/usr/lib/systemd/boot/efi/systemd-bootx64.efi" ::/EFI/BOOT/BOOTx64.EFI
  799. # Copy systemd-boot configuration files
  800. _run_once _make_common_bootmode_systemd-boot_conf.esp
  801. # shellx64.efi is picked up automatically when on /
  802. if [[ -e "${pacstrap_dir}/usr/share/edk2-shell/x64/Shell_Full.efi" ]]; then
  803. mcopy -i "${efibootimg}" \
  804. "${pacstrap_dir}/usr/share/edk2-shell/x64/Shell_Full.efi" ::/shellx64.efi
  805. fi
  806. # Copy kernel and initramfs to FAT image.
  807. # systemd-boot can only access files from the EFI system partition it was launched from.
  808. _run_once _make_boot_on_fat
  809. _msg_info "Done! systemd-boot set up for x64 UEFI booting successfully."
  810. }
  811. # Prepare systemd-boot for El Torito booting
  812. _make_bootmode_uefi-x64.systemd-boot.eltorito() {
  813. # Prepare configuration files
  814. _run_once _make_common_bootmode_systemd-boot_conf
  815. # El Torito UEFI boot requires an image containing the EFI system partition.
  816. # uefi-x64.systemd-boot.eltorito has the same requirements as uefi-x64.systemd-boot.esp
  817. _run_once _make_bootmode_uefi-x64.systemd-boot.esp
  818. # Additionally set up systemd-boot in ISO 9660. This allows creating a medium for the live environment by using
  819. # manual partitioning and simply copying the ISO 9660 file system contents.
  820. # This is not related to El Torito booting and no firmware uses these files.
  821. _msg_info "Preparing an /EFI directory for the ISO 9660 file system..."
  822. install -d -m 0755 -- "${isofs_dir}/EFI/BOOT"
  823. # Copy systemd-boot EFI binary to the default/fallback boot path
  824. install -m 0644 -- "${pacstrap_dir}/usr/lib/systemd/boot/efi/systemd-bootx64.efi" \
  825. "${isofs_dir}/EFI/BOOT/BOOTx64.EFI"
  826. # Copy systemd-boot configuration files
  827. _run_once _make_common_bootmode_systemd-boot_conf.isofs
  828. # edk2-shell based UEFI shell
  829. # shellx64.efi is picked up automatically when on /
  830. if [[ -e "${pacstrap_dir}/usr/share/edk2-shell/x64/Shell_Full.efi" ]]; then
  831. install -m 0644 -- "${pacstrap_dir}/usr/share/edk2-shell/x64/Shell_Full.efi" "${isofs_dir}/shellx64.efi"
  832. fi
  833. _msg_info "Done!"
  834. }
  835. _make_bootmode_uefi-ia32.systemd-boot.esp() {
  836. _msg_info "Setting up systemd-boot for IA32 UEFI booting..."
  837. # Prepare configuration files
  838. _run_once _make_common_bootmode_systemd-boot_conf
  839. # Prepare a FAT image for the EFI system partition
  840. _run_once _make_common_bootmode_systemd-boot
  841. # Copy systemd-boot EFI binary to the default/fallback boot path
  842. mcopy -i "${efibootimg}" \
  843. "${pacstrap_dir}/usr/lib/systemd/boot/efi/systemd-bootia32.efi" ::/EFI/BOOT/BOOTIA32.EFI
  844. # Copy systemd-boot configuration files
  845. _run_once _make_common_bootmode_systemd-boot_conf.esp
  846. # shellia32.efi is picked up automatically when on /
  847. if [[ -e "${pacstrap_dir}/usr/share/edk2-shell/ia32/Shell_Full.efi" ]]; then
  848. mcopy -i "${efibootimg}" \
  849. "${pacstrap_dir}/usr/share/edk2-shell/ia32/Shell_Full.efi" ::/shellia32.efi
  850. fi
  851. # Copy kernel and initramfs to FAT image.
  852. # systemd-boot can only access files from the EFI system partition it was launched from.
  853. _run_once _make_boot_on_fat
  854. _msg_info "Done! systemd-boot set up for IA32 UEFI booting successfully."
  855. }
  856. _make_bootmode_uefi-ia32.systemd-boot.eltorito() {
  857. # Prepare configuration files
  858. _run_once _make_common_bootmode_systemd-boot_conf
  859. # El Torito UEFI boot requires an image containing the EFI system partition.
  860. # uefi-ia32.systemd-boot.eltorito has the same requirements as uefi-ia32.systemd-boot.esp
  861. _run_once _make_bootmode_uefi-ia32.systemd-boot.esp
  862. # Additionally set up systemd-boot in ISO 9660. This allows creating a medium for the live environment by using
  863. # manual partitioning and simply copying the ISO 9660 file system contents.
  864. # This is not related to El Torito booting and no firmware uses these files.
  865. _msg_info "Preparing an /EFI directory for the ISO 9660 file system..."
  866. install -d -m 0755 -- "${isofs_dir}/EFI/BOOT"
  867. # Copy systemd-boot EFI binary to the default/fallback boot path
  868. install -m 0644 -- "${pacstrap_dir}/usr/lib/systemd/boot/efi/systemd-bootia32.efi" \
  869. "${isofs_dir}/EFI/BOOT/BOOTIA32.EFI"
  870. # Copy systemd-boot configuration files
  871. _run_once _make_common_bootmode_systemd-boot_conf.isofs
  872. # edk2-shell based UEFI shell
  873. # shellia32.efi is picked up automatically when on /
  874. if [[ -e "${pacstrap_dir}/usr/share/edk2-shell/ia32/Shell_Full.efi" ]]; then
  875. install -m 0644 -- "${pacstrap_dir}/usr/share/edk2-shell/ia32/Shell_Full.efi" "${isofs_dir}/shellia32.efi"
  876. fi
  877. _msg_info "Done!"
  878. }
  879. _validate_requirements_bootmode_bios.syslinux.mbr() {
  880. # bios.syslinux.mbr requires bios.syslinux.eltorito
  881. # shellcheck disable=SC2076
  882. if [[ ! " ${bootmodes[*]} " =~ ' bios.syslinux.eltorito ' ]]; then
  883. (( validation_error=validation_error+1 ))
  884. _msg_error "Using 'bios.syslinux.mbr' boot mode without 'bios.syslinux.eltorito' is not supported." 0
  885. fi
  886. # Check if the syslinux package is in the package list
  887. # shellcheck disable=SC2076
  888. if [[ ! " ${pkg_list[*]} " =~ ' syslinux ' ]]; then
  889. (( validation_error=validation_error+1 ))
  890. _msg_error "Validating '${bootmode}': The 'syslinux' package is missing from the package list!" 0
  891. fi
  892. # Check if syslinux configuration files exist
  893. if [[ ! -d "${profile}/syslinux" ]]; then
  894. (( validation_error=validation_error+1 ))
  895. _msg_error "Validating '${bootmode}': The '${profile}/syslinux' directory is missing!" 0
  896. else
  897. local cfgfile
  898. for cfgfile in "${profile}/syslinux/"*'.cfg'; do
  899. if [[ -e "${cfgfile}" ]]; then
  900. break
  901. else
  902. (( validation_error=validation_error+1 ))
  903. _msg_error "Validating '${bootmode}': No configuration file found in '${profile}/syslinux/'!" 0
  904. fi
  905. done
  906. fi
  907. # Check for optional packages
  908. # shellcheck disable=SC2076
  909. if [[ ! " ${pkg_list[*]} " =~ ' memtest86+ ' ]]; then
  910. _msg_info "Validating '${bootmode}': 'memtest86+' is not in the package list. Memmory testing will not be available from syslinux."
  911. fi
  912. }
  913. _validate_requirements_bootmode_bios.syslinux.eltorito() {
  914. # bios.syslinux.eltorito has the exact same requirements as bios.syslinux.mbr
  915. _validate_requirements_bootmode_bios.syslinux.mbr
  916. }
  917. _validate_requirements_common_systemd-boot() {
  918. # Check if mkfs.fat is available
  919. if ! command -v mkfs.fat &>/dev/null; then
  920. (( validation_error=validation_error+1 ))
  921. _msg_error "Validating '${bootmode}': mkfs.fat is not available on this host. Install 'dosfstools'!" 0
  922. fi
  923. # Check if mmd and mcopy are available
  924. if ! { command -v mmd &>/dev/null && command -v mcopy &>/dev/null; }; then
  925. (( validation_error=validation_error+1 ))
  926. _msg_error "Validating '${bootmode}': mmd and/or mcopy are not available on this host. Install 'mtools'!" 0
  927. fi
  928. # Check if systemd-boot configuration files exist
  929. if [[ ! -d "${profile}/efiboot/loader/entries" ]]; then
  930. (( validation_error=validation_error+1 ))
  931. _msg_error "Validating '${bootmode}': The '${profile}/efiboot/loader/entries' directory is missing!" 0
  932. else
  933. if [[ ! -e "${profile}/efiboot/loader/loader.conf" ]]; then
  934. (( validation_error=validation_error+1 ))
  935. _msg_error "Validating '${bootmode}': File '${profile}/efiboot/loader/loader.conf' not found!" 0
  936. fi
  937. local conffile
  938. for conffile in "${profile}/efiboot/loader/entries/"*'.conf'; do
  939. if [[ -e "${conffile}" ]]; then
  940. break
  941. else
  942. (( validation_error=validation_error+1 ))
  943. _msg_error "Validating '${bootmode}': No configuration file found in '${profile}/efiboot/loader/entries/'!" 0
  944. fi
  945. done
  946. fi
  947. # Check for optional packages
  948. # shellcheck disable=SC2076
  949. if [[ ! " ${pkg_list[*]} " =~ ' edk2-shell ' ]]; then
  950. _msg_info "'edk2-shell' is not in the package list. The ISO will not contain a bootable UEFI shell."
  951. fi
  952. }
  953. _validate_requirements_bootmode_uefi-x64.systemd-boot.esp() {
  954. # shellcheck disable=SC2076
  955. if [[ " ${bootmodes[*]} " =~ ' uefi-x64.grub.esp ' ]]; then
  956. _msg_error "Validating '${bootmode}': cannot be used with bootmode uefi-x64.grub.esp!" 0
  957. fi
  958. _validate_requirements_common_systemd-boot
  959. }
  960. _validate_requirements_bootmode_uefi-x64.systemd-boot.eltorito() {
  961. # shellcheck disable=SC2076
  962. if [[ " ${bootmodes[*]} " =~ ' uefi-x64.grub.eltorito ' ]]; then
  963. _msg_error "Validating '${bootmode}': cannot be used with bootmode uefi-x64.grub.eltorito!" 0
  964. fi
  965. # uefi-x64.systemd-boot.eltorito has the exact same requirements as uefi-x64.systemd-boot.esp
  966. _validate_requirements_bootmode_uefi-x64.systemd-boot.esp
  967. }
  968. _validate_requirements_bootmode_uefi-ia32.systemd-boot.esp() {
  969. # shellcheck disable=SC2076
  970. if [[ " ${bootmodes[*]} " =~ ' uefi-ia32.grub.esp ' ]]; then
  971. _msg_error "Validating '${bootmode}': cannot be used with bootmode uefi-ia32.grub.esp!" 0
  972. fi
  973. _validate_requirements_common_systemd-boot
  974. }
  975. _validate_requirements_bootmode_uefi-ia32.systemd-boot.eltorito() {
  976. # shellcheck disable=SC2076
  977. if [[ " ${bootmodes[*]} " =~ ' uefi-ia32.grub.eltorito ' ]]; then
  978. _msg_error "Validating '${bootmode}': cannot be used with bootmode uefi-ia32.grub.eltorito!" 0
  979. fi
  980. # uefi-ia32.systemd-boot.eltorito has the exact same requirements as uefi-ia32.systemd-boot.esp
  981. _validate_requirements_bootmode_uefi-x64.systemd-boot.esp
  982. }
  983. _validate_requirements_bootmode_uefi-ia32.grub.esp() {
  984. # Check if GRUB is available
  985. if ! command -v grub-mkstandalone &>/dev/null; then
  986. (( validation_error=validation_error+1 ))
  987. _msg_error "Validating '${bootmode}': grub-install is not available on this host. Install 'grub'!" 0
  988. fi
  989. # shellcheck disable=SC2076
  990. if [[ " ${bootmodes[*]} " =~ ' uefi-x64.systemd-boot.esp ' ]]; then
  991. _validate_requirements_bootmode_uefi-x64.systemd-boot.esp
  992. elif [[ " ${bootmodes[*]} " =~ ' uefi-x64.grub.esp ' ]]; then
  993. _validate_requirements_bootmode_uefi-x64.grub.esp
  994. else
  995. _msg_error "Validating '${bootmode}': requires one of bootmode uefi-x64.systemd-boot.esp or uefi-x64.grub.esp" 0
  996. fi
  997. }
  998. _validate_requirements_bootmode_uefi-ia32.grub.eltorito() {
  999. # uefi-ia32.grub.eltorito has the exact same requirements as uefi-ia32.grub.esp
  1000. _validate_requirements_bootmode_uefi-ia32.grub.esp
  1001. }
  1002. _validate_requirements_bootmode_uefi-x64.grub.esp() {
  1003. # shellcheck disable=SC2076
  1004. if [[ " ${bootmodes[*]} " =~ ' uefi-x64.systemd-boot.esp ' ]]; then
  1005. _msg_error "Validating '${bootmode}': cannot be used with bootmode uefi-x64.systemd-boot.esp!" 0
  1006. fi
  1007. # Check if GRUB is available
  1008. if ! command -v grub-mkstandalone &>/dev/null; then
  1009. (( validation_error=validation_error+1 ))
  1010. _msg_error "Validating '${bootmode}': grub-install is not available on this host. Install 'grub'!" 0
  1011. fi
  1012. # Check if mkfs.fat is available
  1013. if ! command -v mkfs.fat &>/dev/null; then
  1014. (( validation_error=validation_error+1 ))
  1015. _msg_error "Validating '${bootmode}': mkfs.fat is not available on this host. Install 'dosfstools'!" 0
  1016. fi
  1017. # Check if mmd and mcopy are available
  1018. if ! { command -v mmd &>/dev/null && command -v mcopy &>/dev/null; }; then
  1019. _msg_error "Validating '${bootmode}': mmd and/or mcopy are not available on this host. Install 'mtools'!" 0
  1020. fi
  1021. # Check if GRUB configuration files exist
  1022. if [[ ! -d "${profile}/grub" ]]; then
  1023. (( validation_error=validation_error+1 ))
  1024. _msg_error "Validating '${bootmode}': The '${profile}/grub' directory is missing!" 0
  1025. else
  1026. if [[ ! -e "${profile}/grub/grub.cfg" ]]; then
  1027. (( validation_error=validation_error+1 ))
  1028. _msg_error "Validating '${bootmode}': File '${profile}/grub/grub.cfg' not found!" 0
  1029. fi
  1030. local conffile
  1031. for conffile in "${profile}/grub/"*'.cfg'; do
  1032. if [[ -e "${conffile}" ]]; then
  1033. break
  1034. else
  1035. (( validation_error=validation_error+1 ))
  1036. _msg_error "Validating '${bootmode}': No configuration file found in '${profile}/grub/'!" 0
  1037. fi
  1038. done
  1039. fi
  1040. # Check for optional packages
  1041. # shellcheck disable=SC2076
  1042. if [[ ! " ${pkg_list[*]} " =~ ' edk2-shell ' ]]; then
  1043. _msg_info "'edk2-shell' is not in the package list. The ISO will not contain a bootable UEFI shell."
  1044. fi
  1045. # shellcheck disable=SC2076
  1046. if [[ ! " ${pkg_list[*]} " =~ ' memtest86+-efi ' ]]; then
  1047. _msg_info "Validating '${bootmode}': 'memtest86+-efi' is not in the package list. Memory testing will not be available from GRUB."
  1048. fi
  1049. }
  1050. _validate_requirements_bootmode_uefi-x64.grub.eltorito() {
  1051. # shellcheck disable=SC2076
  1052. if [[ " ${bootmodes[*]} " =~ ' uefi-x64.systemd-boot.eltorito ' ]]; then
  1053. _msg_error "Validating '${bootmode}': cannot be used with bootmode uefi-x64.systemd-boot.eltorito!" 0
  1054. fi
  1055. # uefi-x64.grub.eltorito has the exact same requirements as uefi-x64.grub.esp
  1056. _validate_requirements_bootmode_uefi-x64.grub.esp
  1057. }
  1058. # Build airootfs filesystem image
  1059. _prepare_airootfs_image() {
  1060. _run_once "_mkairootfs_${airootfs_image_type}"
  1061. _mkchecksum
  1062. if [[ -e "${isofs_dir}/${install_dir}/${arch}/airootfs.sfs" ]]; then
  1063. airootfs_image_filename="${isofs_dir}/${install_dir}/${arch}/airootfs.sfs"
  1064. elif [[ -e "${isofs_dir}/${install_dir}/${arch}/airootfs.erofs" ]]; then
  1065. airootfs_image_filename="${isofs_dir}/${install_dir}/${arch}/airootfs.erofs"
  1066. fi
  1067. if [[ -n "${gpg_key}" ]]; then
  1068. _mk_pgp_signature "${airootfs_image_filename}"
  1069. fi
  1070. if [[ -v cert_list ]]; then
  1071. _cms_sign_artifact "${airootfs_image_filename}"
  1072. fi
  1073. }
  1074. # export build artifacts for netboot
  1075. _export_netboot_artifacts() {
  1076. _msg_info "Exporting netboot artifacts..."
  1077. install -d -m 0755 "${out_dir}"
  1078. cp -a -- "${isofs_dir}/${install_dir}/" "${out_dir}/"
  1079. # Remove grubenv since it serves no purpose in netboot artifacts
  1080. rm -f -- "${out_dir}/${install_dir}/grubenv"
  1081. _msg_info "Done!"
  1082. du -hs -- "${out_dir}/${install_dir}"
  1083. }
  1084. _cms_sign_artifact() {
  1085. local artifact="${1}"
  1086. local openssl_flags=(
  1087. "-sign"
  1088. "-binary"
  1089. "-nocerts"
  1090. "-noattr"
  1091. "-outform" "DER" "-out" "${artifact}.cms.sig"
  1092. "-in" "${artifact}"
  1093. "-signer" "${cert_list[0]}"
  1094. "-inkey" "${cert_list[1]}"
  1095. )
  1096. if (( ${#cert_list[@]} > 2 )); then
  1097. openssl_flags+=("-certfile" "${cert_list[2]}")
  1098. fi
  1099. _msg_info "Signing ${artifact} image using openssl cms..."
  1100. rm -f -- "${artifact}.cms.sig"
  1101. openssl cms "${openssl_flags[@]}"
  1102. _msg_info "Done!"
  1103. }
  1104. # sign build artifacts for netboot
  1105. _sign_netboot_artifacts() {
  1106. local _file _dir
  1107. local _files_to_sign=()
  1108. _msg_info "Signing netboot artifacts..."
  1109. _dir="${isofs_dir}/${install_dir}/boot/"
  1110. for _file in "${_files_to_sign[@]}" "${_dir}${arch}/vmlinuz-"!(*.sig) "${_dir}${arch}/initramfs-"*.img; do
  1111. rm -f -- "${_file}".ipxe.sig
  1112. openssl cms \
  1113. -sign \
  1114. -binary \
  1115. -noattr \
  1116. -in "${_file}" \
  1117. -signer "${cert_list[0]}" \
  1118. -inkey "${cert_list[1]}" \
  1119. -outform DER \
  1120. -out "${_file}".ipxe.sig
  1121. done
  1122. _msg_info "Done!"
  1123. }
  1124. _validate_requirements_airootfs_image_type_squashfs() {
  1125. if ! command -v mksquashfs &>/dev/null; then
  1126. (( validation_error=validation_error+1 ))
  1127. _msg_error "Validating '${airootfs_image_type}': mksquashfs is not available on this host. Install 'squashfs-tools'!" 0
  1128. fi
  1129. }
  1130. _validate_requirements_airootfs_image_type_ext4+squashfs() {
  1131. if ! { command -v mkfs.ext4 &>/dev/null && command -v tune2fs &>/dev/null; }; then
  1132. (( validation_error=validation_error+1 ))
  1133. _msg_error "Validating '${airootfs_image_type}': mkfs.ext4 and/or tune2fs is not available on this host. Install 'e2fsprogs'!" 0
  1134. fi
  1135. _validate_requirements_airootfs_image_type_squashfs
  1136. }
  1137. _validate_requirements_airootfs_image_type_erofs() {
  1138. if ! command -v mkfs.erofs &>/dev/nul; then
  1139. (( validation_error=validation_error+1 ))
  1140. _msg_error "Validating '${airootfs_image_type}': mkfs.erofs is not available on this host. Install 'erofs-utils'!" 0
  1141. fi
  1142. }
  1143. _validate_common_requirements_buildmode_all() {
  1144. if ! command -v pacman &>/dev/null; then
  1145. (( validation_error=validation_error+1 ))
  1146. _msg_error "Validating build mode '${_buildmode}': pacman is not available on this host. Install 'pacman'!" 0
  1147. fi
  1148. if ! command -v find &>/dev/null; then
  1149. (( validation_error=validation_error+1 ))
  1150. _msg_error "Validating build mode '${_buildmode}': find is not available on this host. Install 'findutils'!" 0
  1151. fi
  1152. if ! command -v gzip &>/dev/null; then
  1153. (( validation_error=validation_error+1 ))
  1154. _msg_error "Validating build mode '${_buildmode}': gzip is not available on this host. Install 'gzip'!" 0
  1155. fi
  1156. }
  1157. _validate_requirements_buildmode_bootstrap() {
  1158. local bootstrap_pkg_list_from_file=()
  1159. if [[ "${arch}" == "dual" ]]; then
  1160. # Check if packages for the bootstrap image are specified for each architecture
  1161. for bootstrap_packages in ${bootstrap_packages_dual}; do
  1162. if [[ "${bootstrap_packages##*/}" == "bootstrap_packages.both" ]]; then
  1163. if [[ -e "${bootstrap_packages}" ]]; then
  1164. mapfile -t bootstrap_pkg_list_from_file < \
  1165. <(sed '/^[[:blank:]]*#.*/d;s/#.*//;/^[[:blank:]]*$/d' "${bootstrap_packages}")
  1166. bootstrap_pkg_list+=("${bootstrap_pkg_list_from_file[@]}")
  1167. if (( ${#bootstrap_pkg_list_from_file[@]} < 1 )); then
  1168. (( validation_error=validation_error+1 ))
  1169. _msg_error "No package specified in '${bootstrap_packages}'." 0
  1170. fi
  1171. else
  1172. (( validation_error=validation_error+1 ))
  1173. _msg_error "Bootstrap packages file '${bootstrap_packages}' does not exist." 0
  1174. fi
  1175. elif [[ -e "${bootstrap_packages}" ]]; then
  1176. mapfile -t "bootstrap_pkg_list_from_file_${bootstrap_packages##*.}" < \
  1177. <(sed '/^[[:blank:]]*#.*/d;s/#.*//;/^[[:blank:]]*$/d' "${bootstrap_packages}")
  1178. eval "bootstrap_pkg_list_${bootstrap_packages##*.}+=(\${bootstrap_pkg_list_from_file_${bootstrap_packages##*.}[@]})"
  1179. fi
  1180. done
  1181. else
  1182. # Check if packages for the bootstrap image are specified
  1183. if [[ -e "${bootstrap_packages}" ]]; then
  1184. mapfile -t bootstrap_pkg_list_from_file < \
  1185. <(sed '/^[[:blank:]]*#.*/d;s/#.*//;/^[[:blank:]]*$/d' "${bootstrap_packages}")
  1186. bootstrap_pkg_list+=("${bootstrap_pkg_list_from_file[@]}")
  1187. if (( ${#bootstrap_pkg_list_from_file[@]} < 1 )); then
  1188. (( validation_error=validation_error+1 ))
  1189. _msg_error "No package specified in '${bootstrap_packages}'." 0
  1190. fi
  1191. else
  1192. (( validation_error=validation_error+1 ))
  1193. _msg_error "Bootstrap packages file '${bootstrap_packages}' does not exist." 0
  1194. fi
  1195. fi
  1196. _validate_common_requirements_buildmode_all
  1197. if ! command -v bsdtar &>/dev/null; then
  1198. (( validation_error=validation_error+1 ))
  1199. _msg_error "Validating build mode '${_buildmode}': bsdtar is not available on this host. Install 'libarchive'!" 0
  1200. fi
  1201. if [[ "${arch}" == "armv7h" ]] && ! setarch armv7l /bin/true &>/dev/null; then
  1202. if ! command -v qemu-arm-static &>/dev/null; then
  1203. (( validation_error=validation_error+1 ))
  1204. _msg_error "Validating build mode '${_buildmode}': qemu-arm-static is not available on this host. Install 'qemu-user-static'!" 0
  1205. fi
  1206. fi
  1207. }
  1208. _validate_common_requirements_buildmode_iso_netboot() {
  1209. local bootmode
  1210. local pkg_list_from_file=()
  1211. # Check if the package list file exists and read packages from it
  1212. if [[ "${arch}" == "dual" ]]; then
  1213. # Check if the package list files exist and read packages from them for each architecture
  1214. for packages in ${packages_dual}; do
  1215. if [[ "${packages##*/}" == "packages.both" ]]; then
  1216. if [[ -e "${packages}" ]]; then
  1217. mapfile -t pkg_list_from_file < <(sed '/^[[:blank:]]*#.*/d;s/#.*//;/^[[:blank:]]*$/d' "${packages}")
  1218. pkg_list+=("${pkg_list_from_file[@]}")
  1219. if (( ${#pkg_list_from_file[@]} < 1 )); then
  1220. (( validation_error=validation_error+1 ))
  1221. _msg_error "Packages file '${packages}' does not exist." 0
  1222. fi
  1223. else
  1224. (( validation_error=validation_error+1 ))
  1225. _msg_error "Packages file '${packages}' does not exist." 0
  1226. fi
  1227. elif [[ -e "${packages}" ]]; then
  1228. mapfile -t "pkg_list_from_file_${packages##*.}" < <(sed '/^[[:blank:]]*#.*/d;s/#.*//;/^[[:blank:]]*$/d' "${packages}")
  1229. eval "pkg_list_${packages##*.}+=(\${pkg_list_from_file_${packages##*.}[@]})"
  1230. fi
  1231. done
  1232. else
  1233. # Check if the package list file exists and read packages from it
  1234. if [[ -e "${packages}" ]]; then
  1235. mapfile -t pkg_list_from_file < <(sed '/^[[:blank:]]*#.*/d;s/#.*//;/^[[:blank:]]*$/d' "${packages}")
  1236. pkg_list+=("${pkg_list_from_file[@]}")
  1237. if (( ${#pkg_list_from_file[@]} < 1 )); then
  1238. (( validation_error=validation_error+1 ))
  1239. _msg_error "Packages file '${packages}' does not exist." 0
  1240. fi
  1241. else
  1242. (( validation_error=validation_error+1 ))
  1243. _msg_error "Packages file '${packages}' does not exist." 0
  1244. fi
  1245. fi
  1246. if [[ -v cert_list ]]; then
  1247. # Check if the certificate files exist
  1248. for _cert in "${cert_list[@]}"; do
  1249. if [[ ! -e "${_cert}" ]]; then
  1250. (( validation_error=validation_error+1 ))
  1251. _msg_error "File '${_cert}' does not exist." 0
  1252. fi
  1253. done
  1254. # Check if there are at least three certificate files to sign netboot and rootfs.
  1255. if (( ${#cert_list[@]} < 2 )); then
  1256. (( validation_error=validation_error+1 ))
  1257. _msg_error "Two certificates are required for codesigning netboot artifacts, but '${cert_list[*]}' is provided." 0
  1258. fi
  1259. if ! command -v openssl &>/dev/null; then
  1260. (( validation_error=validation_error+1 ))
  1261. _msg_error "Validating build mode '${_buildmode}': openssl is not available on this host. Install 'openssl'!" 0
  1262. fi
  1263. fi
  1264. # Check if the specified airootfs_image_type is supported
  1265. if typeset -f "_mkairootfs_${airootfs_image_type}" &>/dev/null; then
  1266. if typeset -f "_validate_requirements_airootfs_image_type_${airootfs_image_type}" &>/dev/null; then
  1267. "_validate_requirements_airootfs_image_type_${airootfs_image_type}"
  1268. else
  1269. _msg_warning "Function '_validate_requirements_airootfs_image_type_${airootfs_image_type}' does not exist. Validating the requirements of '${airootfs_image_type}' airootfs image type will not be possible."
  1270. fi
  1271. else
  1272. (( validation_error=validation_error+1 ))
  1273. _msg_error "Unsupported image type: '${airootfs_image_type}'" 0
  1274. fi
  1275. }
  1276. _validate_requirements_buildmode_iso() {
  1277. _validate_common_requirements_buildmode_iso_netboot
  1278. _validate_common_requirements_buildmode_all
  1279. # Check if the specified bootmodes are supported
  1280. if (( ${#bootmodes[@]} < 1 )); then
  1281. (( validation_error=validation_error+1 ))
  1282. _msg_error "No boot modes specified in '${profile}/profiledef.sh'." 0
  1283. fi
  1284. for bootmode in "${bootmodes[@]}"; do
  1285. if typeset -f "_make_bootmode_${bootmode}" &>/dev/null; then
  1286. if typeset -f "_validate_requirements_bootmode_${bootmode}" &>/dev/null; then
  1287. "_validate_requirements_bootmode_${bootmode}"
  1288. else
  1289. _msg_warning "Function '_validate_requirements_bootmode_${bootmode}' does not exist. Validating the requirements of '${bootmode}' boot mode will not be possible."
  1290. fi
  1291. else
  1292. (( validation_error=validation_error+1 ))
  1293. _msg_error "${bootmode} is not a valid boot mode!" 0
  1294. fi
  1295. done
  1296. if ! command -v awk &>/dev/null; then
  1297. (( validation_error=validation_error+1 ))
  1298. _msg_error "Validating build mode '${_buildmode}': awk is not available on this host. Install 'awk'!" 0
  1299. fi
  1300. }
  1301. _validate_requirements_buildmode_netboot() {
  1302. _validate_common_requirements_buildmode_iso_netboot
  1303. _validate_common_requirements_buildmode_all
  1304. }
  1305. # SYSLINUX El Torito
  1306. _add_xorrisofs_options_bios.syslinux.eltorito() {
  1307. xorrisofs_options+=(
  1308. # El Torito boot image for x86 BIOS
  1309. '-eltorito-boot' 'boot/syslinux/isolinux.bin'
  1310. # El Torito boot catalog file
  1311. '-eltorito-catalog' 'boot/syslinux/boot.cat'
  1312. # Required options to boot with ISOLINUX
  1313. '-no-emul-boot' '-boot-load-size' '4' '-boot-info-table'
  1314. )
  1315. }
  1316. # SYSLINUX MBR (isohybrid)
  1317. _add_xorrisofs_options_bios.syslinux.mbr() {
  1318. xorrisofs_options+=(
  1319. # SYSLINUX MBR bootstrap code; does not work without "-eltorito-boot syslinux/isolinux.bin"
  1320. '-isohybrid-mbr' "${isofs_dir}/boot/syslinux/isohdpfx.bin"
  1321. # When GPT is used, create an additional partition in the MBR (besides 0xEE) for sectors 0–1 (MBR
  1322. # bootstrap code area) and mark it as bootable
  1323. # May allow booting on some systems
  1324. # https://wiki.archlinux.org/title/Partitioning#Tricking_old_BIOS_into_booting_from_GPT
  1325. '--mbr-force-bootable'
  1326. # Move the first partition away from the start of the ISO to match the expectations of partition editors
  1327. # May allow booting on some systems
  1328. # https://dev.lovelyhq.com/libburnia/libisoburn/src/branch/master/doc/partition_offset.wiki
  1329. '-partition_offset' '16'
  1330. )
  1331. }
  1332. # GRUB in an attached EFI system partition
  1333. _add_xorrisofs_options_uefi-ia32.grub.esp() {
  1334. # TODO: how does the bootmodes systemd-boot vs x64.grub affect ${bootmodes[*]} tests in _add_xorrisofs_options_uefi-x64.systemd-boot.esp etc?
  1335. # shellcheck disable=SC2076
  1336. if [[ ! " ${bootmodes[*]} " =~ ' uefi-x64.systemd-boot.esp ' && ! " ${bootmodes[*]} " =~ ' uefi-x64.grub.esp ' ]]; then
  1337. # _add_xorrisofs_options_uefi-x64.systemd-boot.esp
  1338. _add_xorrisofs_options_uefi-x64.grub.esp
  1339. fi
  1340. }
  1341. # GRUB via El Torito
  1342. _add_xorrisofs_options_uefi-ia32.grub.eltorito() {
  1343. # shellcheck disable=SC2076
  1344. if [[ ! " ${bootmodes[*]} " =~ ' uefi-x64.systemd-boot.eltorito ' && ! " ${bootmodes[*]} " =~ ' uefi-x64.grub.eltorito ' ]]; then
  1345. # _add_xorrisofs_options_uefi-x64.systemd-boot.eltorito
  1346. _add_xorrisofs_options_uefi-x64.grub.eltorito
  1347. fi
  1348. }
  1349. # systemd-boot in an attached EFI system partition
  1350. _add_xorrisofs_options_uefi-x64.systemd-boot.esp() {
  1351. # Move the first partition away from the start of the ISO, otherwise the GPT will not be valid and ISO 9660
  1352. # partition will not be mountable
  1353. # shellcheck disable=SC2076
  1354. [[ " ${xorrisofs_options[*]} " =~ ' -partition_offset ' ]] || xorrisofs_options+=('-partition_offset' '16')
  1355. # Attach efiboot.img as a second partition and set its partition type to "EFI system partition"
  1356. xorrisofs_options+=('-append_partition' '2' 'C12A7328-F81F-11D2-BA4B-00A0C93EC93B' "${efibootimg}")
  1357. # Ensure GPT is used as some systems do not support UEFI booting without it
  1358. # shellcheck disable=SC2076
  1359. if [[ " ${bootmodes[*]} " =~ ' bios.syslinux.mbr ' ]]; then
  1360. # A valid GPT prevents BIOS booting on some systems, instead use an invalid GPT (without a protective MBR).
  1361. # The attached partition will have the EFI system partition type code in MBR, but in the invalid GPT it will
  1362. # have a Microsoft basic partition type code.
  1363. if [[ ! " ${bootmodes[*]} " =~ ' uefi-x64.systemd-boot.eltorito ' && ! " ${bootmodes[*]} " =~ ' uefi-ia32.grub.eltorito ' ]]; then
  1364. # If '-isohybrid-gpt-basdat' is specified before '-e', then the appended EFI system partition will have the
  1365. # EFI system partition type ID/GUID in both MBR and GPT. If '-isohybrid-gpt-basdat' is specified after '-e',
  1366. # the appended EFI system partition will have the Microsoft basic data type GUID in GPT.
  1367. if [[ ! " ${xorrisofs_options[*]} " =~ ' -isohybrid-gpt-basdat ' ]]; then
  1368. xorrisofs_options+=('-isohybrid-gpt-basdat')
  1369. fi
  1370. fi
  1371. else
  1372. # Use valid GPT if BIOS booting support will not be required
  1373. xorrisofs_options+=('-appended_part_as_gpt')
  1374. fi
  1375. }
  1376. # systemd-boot via El Torito
  1377. _add_xorrisofs_options_uefi-x64.systemd-boot.eltorito() {
  1378. # shellcheck disable=SC2076
  1379. if [[ " ${bootmodes[*]} " =~ ' uefi-x64.systemd-boot.esp ' || " ${bootmodes[*]} " =~ ' uefi-ia32.grub.esp ' ]]; then
  1380. # systemd-boot in an attached EFI system partition via El Torito
  1381. xorrisofs_options+=(
  1382. # Start a new El Torito boot entry for UEFI
  1383. '-eltorito-alt-boot'
  1384. # Set the second partition as the El Torito UEFI boot image
  1385. '-e' '--interval:appended_partition_2:all::'
  1386. # Boot image is not emulating floppy or hard disk; required for all known boot loaders
  1387. '-no-emul-boot'
  1388. )
  1389. # A valid GPT prevents BIOS booting on some systems, use an invalid GPT instead.
  1390. if [[ " ${bootmodes[*]} " =~ ' bios.syslinux.mbr ' ]]; then
  1391. # If '-isohybrid-gpt-basdat' is specified before '-e', then the appended EFI system partition will have the
  1392. # EFI system partition type ID/GUID in both MBR and GPT. If '-isohybrid-gpt-basdat' is specified after '-e',
  1393. # the appended EFI system partition will have the Microsoft basic data type GUID in GPT.
  1394. if [[ ! " ${xorrisofs_options[*]} " =~ ' -isohybrid-gpt-basdat ' ]]; then
  1395. xorrisofs_options+=('-isohybrid-gpt-basdat')
  1396. fi
  1397. fi
  1398. else
  1399. # The ISO will not contain a GPT partition table, so to be able to reference efiboot.img, place it as a
  1400. # file inside the ISO 9660 file system
  1401. install -d -m 0755 -- "${isofs_dir}/EFI/parabolaiso"
  1402. cp -a -- "${efibootimg}" "${isofs_dir}/EFI/parabolaiso/efiboot.img"
  1403. # systemd-boot in an embedded efiboot.img via El Torito
  1404. xorrisofs_options+=(
  1405. # Start a new El Torito boot entry for UEFI
  1406. '-eltorito-alt-boot'
  1407. # Set efiboot.img as the El Torito UEFI boot image
  1408. '-e' 'EFI/parabolaiso/efiboot.img'
  1409. # Boot image is not emulating floppy or hard disk; required for all known boot loaders
  1410. '-no-emul-boot'
  1411. )
  1412. fi
  1413. # Specify where to save the El Torito boot catalog file in case it is not already set by bios.syslinux.eltorito
  1414. # shellcheck disable=SC2076
  1415. [[ " ${bootmodes[*]} " =~ ' bios.' ]] || xorrisofs_options+=('-eltorito-catalog' 'EFI/boot.cat')
  1416. }
  1417. # GRUB in an attached EFI system partition.
  1418. # Same as _add_xorrisofs_options_uefi-x64.systemd-boot.esp.
  1419. _add_xorrisofs_options_uefi-x64.grub.esp() {
  1420. # Move the first partition away from the start of the ISO, otherwise the GPT will not be valid and ISO 9660
  1421. # partition will not be mountable
  1422. # shellcheck disable=SC2076
  1423. [[ " ${xorrisofs_options[*]} " =~ ' -partition_offset ' ]] || xorrisofs_options+=('-partition_offset' '16')
  1424. # Attach efiboot.img as a second partition and set its partition type to "EFI system partition"
  1425. xorrisofs_options+=('-append_partition' '2' 'C12A7328-F81F-11D2-BA4B-00A0C93EC93B' "${efibootimg}")
  1426. # Ensure GPT is used as some systems do not support UEFI booting without it
  1427. # shellcheck disable=SC2076
  1428. if [[ " ${bootmodes[*]} " =~ ' bios.syslinux.mbr ' ]]; then
  1429. # A valid GPT prevents BIOS booting on some systems, instead use an invalid GPT (without a protective MBR).
  1430. # The attached partition will have the EFI system partition type code in MBR, but in the invalid GPT it will
  1431. # have a Microsoft basic partition type code.
  1432. if [[ ! " ${bootmodes[*]} " =~ ' uefi-x64.grub.eltorito ' && ! " ${bootmodes[*]} " =~ ' uefi-ia32.grub.eltorito ' ]]; then
  1433. # If '-isohybrid-gpt-basdat' is specified before '-e', then the appended EFI system partition will have the
  1434. # EFI system partition type ID/GUID in both MBR and GPT. If '-isohybrid-gpt-basdat' is specified after '-e',
  1435. # the appended EFI system partition will have the Microsoft basic data type GUID in GPT.
  1436. if [[ ! " ${xorrisofs_options[*]} " =~ ' -isohybrid-gpt-basdat ' ]]; then
  1437. xorrisofs_options+=('-isohybrid-gpt-basdat')
  1438. fi
  1439. fi
  1440. else
  1441. # Use valid GPT if BIOS booting support will not be required
  1442. xorrisofs_options+=('-appended_part_as_gpt')
  1443. fi
  1444. }
  1445. # GRUB via El Torito
  1446. # Same as _add_xorrisofs_options_uefi-x64.systemd-boot.eltorito.
  1447. _add_xorrisofs_options_uefi-x64.grub.eltorito() {
  1448. # shellcheck disable=SC2076
  1449. if [[ " ${bootmodes[*]} " =~ ' uefi-x64.grub.esp ' || " ${bootmodes[*]} " =~ ' uefi-ia32.grub.esp ' ]]; then
  1450. # grub in an attached EFI system partition via El Torito
  1451. xorrisofs_options+=(
  1452. # Start a new El Torito boot entry for UEFI
  1453. '-eltorito-alt-boot'
  1454. # Set the second partition as the El Torito UEFI boot image
  1455. '-e' '--interval:appended_partition_2:all::'
  1456. # Boot image is not emulating floppy or hard disk; required for all known boot loaders
  1457. '-no-emul-boot'
  1458. )
  1459. # A valid GPT prevents BIOS booting on some systems, use an invalid GPT instead.
  1460. if [[ " ${bootmodes[*]} " =~ ' bios.syslinux.mbr ' ]]; then
  1461. # If '-isohybrid-gpt-basdat' is specified before '-e', then the appended EFI system partition will have the
  1462. # EFI system partition type ID/GUID in both MBR and GPT. If '-isohybrid-gpt-basdat' is specified after '-e',
  1463. # the appended EFI system partition will have the Microsoft basic data type GUID in GPT.
  1464. if [[ ! " ${xorrisofs_options[*]} " =~ ' -isohybrid-gpt-basdat ' ]]; then
  1465. xorrisofs_options+=('-isohybrid-gpt-basdat')
  1466. fi
  1467. fi
  1468. else
  1469. # The ISO will not contain a GPT partition table, so to be able to reference efiboot.img, place it as a
  1470. # file inside the ISO 9660 file system
  1471. install -d -m 0755 -- "${isofs_dir}/EFI/parabolaiso"
  1472. cp -a -- "${efibootimg}" "${isofs_dir}/EFI/parabolaiso/efiboot.img"
  1473. # grub in an embedded efiboot.img via El Torito
  1474. xorrisofs_options+=(
  1475. # Start a new El Torito boot entry for UEFI
  1476. '-eltorito-alt-boot'
  1477. # Set efiboot.img as the El Torito UEFI boot image
  1478. '-e' 'EFI/parabolaiso/efiboot.img'
  1479. # Boot image is not emulating floppy or hard disk; required for all known boot loaders
  1480. '-no-emul-boot'
  1481. )
  1482. fi
  1483. # Specify where to save the El Torito boot catalog file in case it is not already set by bios.syslinux.eltorito
  1484. # shellcheck disable=SC2076
  1485. [[ " ${bootmodes[*]} " =~ ' bios.' ]] || xorrisofs_options+=('-eltorito-catalog' 'EFI/boot.cat')
  1486. }
  1487. # Build bootstrap image
  1488. _build_bootstrap_image() {
  1489. local _bootstrap_parent
  1490. _bootstrap_parent="$(dirname -- "${pacstrap_dir}")"
  1491. local image_name_arch
  1492. eval "image_name_arch=\${image_name_${arch}}"
  1493. [[ -z "${image_name_arch}" ]] || image_name="${image_name_arch}"
  1494. [[ -d "${out_dir}" ]] || install -d -- "${out_dir}"
  1495. cd -- "${_bootstrap_parent}"
  1496. _msg_info "Creating ${arch} bootstrap image..."
  1497. bsdtar -cf - "root.${arch}" | gzip -cn9 >"${out_dir}/${image_name}"
  1498. _msg_info "Done!"
  1499. du -h -- "${out_dir}/${image_name}"
  1500. cd -- "${OLDPWD}"
  1501. }
  1502. # Build ISO
  1503. _build_iso_image() {
  1504. local xorriso_options=() xorrisofs_options=()
  1505. local bootmode
  1506. [[ -d "${out_dir}" ]] || install -d -- "${out_dir}"
  1507. # Do not read xorriso startup files to prevent interference and unintended behavior.
  1508. # For it to work, -no_rc must be the first argument passed to xorriso.
  1509. xorriso_options=('-no_rc')
  1510. if [[ "${quiet}" == "y" ]]; then
  1511. # The when xorriso is run in mkisofs compatibility mode (xorrisofs), the mkisofs option -quiet is interpreted
  1512. # too late (e.g. messages about SOURCE_DATE_EPOCH still get shown).
  1513. # Instead use native xorriso option to silence the output.
  1514. xorriso_options+=('-report_about' 'SORRY')
  1515. fi
  1516. # Add required xorrisofs options for each boot mode
  1517. for bootmode in "${bootmodes[@]}"; do
  1518. typeset -f "_add_xorrisofs_options_${bootmode}" &>/dev/null && "_add_xorrisofs_options_${bootmode}"
  1519. done
  1520. rm -f -- "${out_dir}/${image_name}"
  1521. _msg_info "Creating ISO image..."
  1522. xorriso "${xorriso_options[@]}" -as mkisofs \
  1523. -iso-level 3 \
  1524. -full-iso9660-filenames \
  1525. -joliet \
  1526. -joliet-long \
  1527. -rational-rock \
  1528. -volid "${iso_label}" \
  1529. -appid "${iso_application}" \
  1530. -publisher "${iso_publisher}" \
  1531. -preparer "prepared by ${app_name}" \
  1532. "${xorrisofs_options[@]}" \
  1533. -output "${out_dir}/${image_name}" \
  1534. "${isofs_dir}/"
  1535. _msg_info "Done!"
  1536. du -h -- "${out_dir}/${image_name}"
  1537. }
  1538. # Read profile's values from profiledef.sh
  1539. _read_profile() {
  1540. if [[ -z "${profile}" ]]; then
  1541. _msg_error "No profile specified!" 1
  1542. fi
  1543. if [[ ! -d "${profile}" ]]; then
  1544. _msg_error "Profile '${profile}' does not exist!" 1
  1545. elif [[ ! -e "${profile}/profiledef.sh" ]]; then
  1546. _msg_error "Profile '${profile}' is missing 'profiledef.sh'!" 1
  1547. else
  1548. cd -- "${profile}"
  1549. # Source profile's variables
  1550. # shellcheck source=configs/releng/profiledef.sh
  1551. . "${profile}/profiledef.sh"
  1552. [[ -n "$arch" ]] || arch="$(uname -m)"
  1553. if [[ "${arch}" == "dual" ]]; then
  1554. # Resolve paths of files that are expected to reside in the profile's directory for each architecture
  1555. [[ -n "$packages_dual" ]] || packages_files_dual=("${profile}/packages."{both,i686,x86_64})
  1556. packages_dual="$(realpath -- "${packages_files_dual[@]}")"
  1557. pacman_conf="$(realpath -- "${pacman_conf}")"
  1558. # Resolve paths of files that may reside in the profile's directory for each architecture
  1559. if [[ -z "$bootstrap_packages_dual" ]] && [[ -e "${profile}/bootstrap_packages.both" ]]; then
  1560. bootstrap_packages_files_dual=("${profile}/bootstrap_packages."{both,i686,x86_64})
  1561. bootstrap_packages_dual="$(realpath -- "${bootstrap_packages_files_dual[@]}")"
  1562. pacman_conf="$(realpath -- "${pacman_conf}")"
  1563. fi
  1564. else
  1565. # Resolve paths of files that are expected to reside in the profile's directory
  1566. [[ -n "$packages" ]] || packages="${profile}/packages.${arch}"
  1567. packages="$(realpath -- "${packages}")"
  1568. pacman_conf="$(realpath -- "${pacman_conf}")"
  1569. # Resolve paths of files that may reside in the profile's directory
  1570. if [[ -z "$bootstrap_packages" ]] && [[ -e "${profile}/bootstrap_packages.${arch}" ]]; then
  1571. bootstrap_packages="${profile}/bootstrap_packages.${arch}"
  1572. bootstrap_packages="$(realpath -- "${bootstrap_packages}")"
  1573. pacman_conf="$(realpath -- "${pacman_conf}")"
  1574. fi
  1575. fi
  1576. cd -- "${OLDPWD}"
  1577. fi
  1578. }
  1579. # Validate set options
  1580. _validate_options() {
  1581. local validation_error=0 _buildmode certfile
  1582. _msg_info "Validating options..."
  1583. # Check if pacman configuration file exists
  1584. if [[ ! -e "${pacman_conf}" ]]; then
  1585. (( validation_error=validation_error+1 ))
  1586. _msg_error "File '${pacman_conf}' does not exist." 0
  1587. fi
  1588. # Check if the code signing certificate files exist
  1589. for certfile in "${cert_list[@]}"; do
  1590. if [[ ! -e "$certfile" ]]; then
  1591. (( validation_error=validation_error+1 ))
  1592. _msg_error "Code signing certificate '${certfile}' does not exist." 0
  1593. fi
  1594. done
  1595. # Check if the specified buildmodes are supported
  1596. for _buildmode in "${buildmodes[@]}"; do
  1597. if typeset -f "_build_buildmode_${_buildmode}" &>/dev/null; then
  1598. if typeset -f "_validate_requirements_buildmode_${_buildmode}" &>/dev/null; then
  1599. "_validate_requirements_buildmode_${_buildmode}"
  1600. else
  1601. _msg_warning "Function '_validate_requirements_buildmode_${_buildmode}' does not exist. Validating the requirements of '${_buildmode}' build mode will not be possible."
  1602. fi
  1603. else
  1604. (( validation_error=validation_error+1 ))
  1605. _msg_error "${_buildmode} is not a valid build mode!" 0
  1606. fi
  1607. done
  1608. if (( validation_error )); then
  1609. _msg_error "${validation_error} errors were encountered while validating the profile. Aborting." 1
  1610. fi
  1611. _msg_info "Done!"
  1612. }
  1613. # Set defaults and, if present, overrides from mkparabolaiso command line option parameters
  1614. _set_overrides() {
  1615. # Set variables that have command line overrides
  1616. [[ ! -v override_buildmodes ]] || buildmodes=("${override_buildmodes[@]}")
  1617. if (( ${#buildmodes[@]} < 1 )); then
  1618. buildmodes+=('iso')
  1619. fi
  1620. if [[ -v override_work_dir ]]; then
  1621. work_dir="$override_work_dir"
  1622. elif [[ -z "$work_dir" ]]; then
  1623. work_dir='./work'
  1624. fi
  1625. work_dir="$(realpath -- "$work_dir")"
  1626. if [[ -v override_out_dir ]]; then
  1627. out_dir="$override_out_dir"
  1628. elif [[ -z "$out_dir" ]]; then
  1629. out_dir='./out'
  1630. fi
  1631. out_dir="$(realpath -- "$out_dir")"
  1632. if [[ -v override_pacman_conf ]]; then
  1633. pacman_conf="$override_pacman_conf"
  1634. elif [[ -z "$pacman_conf" ]]; then
  1635. pacman_conf="/etc/pacman.conf"
  1636. fi
  1637. pacman_conf="$(realpath -- "$pacman_conf")"
  1638. [[ ! -v override_pkg_list ]] || pkg_list+=("${override_pkg_list[@]}")
  1639. # TODO: allow overriding bootstrap_pkg_list
  1640. if [[ -v override_iso_label ]]; then
  1641. iso_label="$override_iso_label"
  1642. elif [[ -z "$iso_label" ]]; then
  1643. iso_label="${app_name^^}"
  1644. fi
  1645. if [[ -v override_iso_publisher ]]; then
  1646. iso_publisher="$override_iso_publisher"
  1647. elif [[ -z "$iso_publisher" ]]; then
  1648. iso_publisher="${app_name}"
  1649. fi
  1650. if [[ -v override_iso_application ]]; then
  1651. iso_application="$override_iso_application"
  1652. elif [[ -z "$iso_application" ]]; then
  1653. iso_application="${app_name} iso"
  1654. fi
  1655. if [[ -v override_install_dir ]]; then
  1656. install_dir="$override_install_dir"
  1657. elif [[ -z "$install_dir" ]]; then
  1658. install_dir="${app_name}"
  1659. fi
  1660. [[ ! -v override_gpg_key ]] || gpg_key="$override_gpg_key"
  1661. [[ ! -v override_gpg_sender ]] || gpg_sender="$override_gpg_sender"
  1662. [[ ! -v override_cert_list ]] || mapfile -t cert_list < <(realpath -- "${override_cert_list[@]}")
  1663. if [[ -v override_quiet ]]; then
  1664. quiet="$override_quiet"
  1665. elif [[ -z "$quiet" ]]; then
  1666. quiet="y"
  1667. fi
  1668. if [[ -v override_rm_work_dir ]]; then
  1669. rm_work_dir="$override_rm_work_dir"
  1670. fi
  1671. # Set variables that do not have overrides
  1672. [[ -n "$airootfs_image_type" ]] || airootfs_image_type="squashfs"
  1673. [[ -n "$iso_name" ]] || iso_name="${app_name}"
  1674. # Precalculate the ISO's modification date in UTC, i.e. its "UUID"
  1675. TZ=UTC printf -v iso_uuid '%(%F-%H-%M-%S-00)T' "$SOURCE_DATE_EPOCH"
  1676. }
  1677. _export_gpg_publickey() {
  1678. gpg_publickey="${work_dir}/pubkey.gpg"
  1679. rm -f -- "$gpg_publickey"
  1680. gpg --batch --no-armor --output "$gpg_publickey" --export "${gpg_key}"
  1681. [[ -s "$gpg_publickey" ]] || return
  1682. }
  1683. _make_version() {
  1684. local _os_release
  1685. _msg_info "Creating ${arch} version files..."
  1686. # Write version file to system installation dir
  1687. rm -f -- "${pacstrap_dir}/version"
  1688. printf '%s\n' "${iso_version}" >"${pacstrap_dir}/version"
  1689. if [[ "${buildmode}" == @("iso"|"netboot") ]]; then
  1690. install -d -m 0755 -- "${isofs_dir}/${install_dir}"
  1691. # Write version file to ISO 9660
  1692. printf '%s\n' "${iso_version}" >"${isofs_dir}/${install_dir}/version"
  1693. fi
  1694. if [[ "${buildmode}" == "iso" ]]; then
  1695. # Write grubenv with version information to ISO 9660
  1696. # TODO: after sufficient time has passed, do not create this file anymore.
  1697. # _make_common_bootmode_grub_cfg and _make_common_grubenv_and_loopbackcfg already create a
  1698. # ${isofs_dir}/boot/grub/grubenv file
  1699. rm -f -- "${isofs_dir}/${install_dir}/grubenv"
  1700. printf '%.1024s' "$(printf '# GRUB Environment Block\nNAME=%s\nVERSION=%s\n%s' \
  1701. "${iso_name}" "${iso_version}" "$(printf '%0.1s' "#"{1..1024})")" \
  1702. >"${isofs_dir}/${install_dir}/grubenv"
  1703. fi
  1704. # Append IMAGE_ID & IMAGE_VERSION to os-release
  1705. _os_release="$(realpath -- "${pacstrap_dir}/etc/os-release")"
  1706. if [[ ! -e "${pacstrap_dir}/etc/os-release" && -e "${pacstrap_dir}/usr/lib/os-release" ]]; then
  1707. _os_release="$(realpath -- "${pacstrap_dir}/usr/lib/os-release")"
  1708. fi
  1709. if [[ "${_os_release}" != "${pacstrap_dir}"* ]]; then
  1710. _msg_warning "os-release file '${_os_release}' is outside of valid path."
  1711. else
  1712. [[ ! -e "${_os_release}" ]] || sed -i '/^IMAGE_ID=/d;/^IMAGE_VERSION=/d' "${_os_release}"
  1713. printf 'IMAGE_ID=%s\nIMAGE_VERSION=%s\n' "${iso_name}" "${iso_version}" >>"${_os_release}"
  1714. fi
  1715. # Touch /usr/lib/clock-epoch to give another hint on date and time
  1716. # for systems with screwed or broken RTC.
  1717. touch -m -d"@${SOURCE_DATE_EPOCH}" -- "${pacstrap_dir}/usr/lib/clock-epoch"
  1718. _msg_info "Done!"
  1719. }
  1720. _make_pkglist() {
  1721. _msg_info "Creating a list of installed packages on ${arch} live-enviroment..."
  1722. case "${buildmode}" in
  1723. "bootstrap")
  1724. pacman -Q --sysroot "${pacstrap_dir}" >"${pacstrap_dir}/pkglist.${arch}.txt"
  1725. ;;
  1726. "iso"|"netboot")
  1727. install -d -m 0755 -- "${isofs_dir}/${install_dir}"
  1728. pacman -Q --sysroot "${pacstrap_dir}" >"${isofs_dir}/${install_dir}/pkglist.${arch}.txt"
  1729. ;;
  1730. esac
  1731. _msg_info "Done!"
  1732. }
  1733. # Create working directory
  1734. _make_work_dir() {
  1735. if [[ ! -d "${work_dir}" ]]; then
  1736. install -d -- "${work_dir}"
  1737. elif (( rm_work_dir )); then
  1738. rm_work_dir=0
  1739. _msg_warning "Working directory removal requested, but '${work_dir}' already exists. It will not be removed!" 0
  1740. fi
  1741. }
  1742. # build the base for an ISO and/or a netboot target
  1743. _build_iso_base() {
  1744. local run_once_mode="base"
  1745. local buildmode_packages="${packages}"
  1746. if [[ "${arch}" == "dual" ]]; then
  1747. buildmode_packages="${packages_dual}"
  1748. # Set the package list to use for each architecture
  1749. local buildmode_pkg_list_i686=("${pkg_list_i686[@]}")
  1750. local buildmode_pkg_list_x86_64=("${pkg_list_x86_64[@]}")
  1751. fi
  1752. # Set the package list to use
  1753. local buildmode_pkg_list=("${pkg_list[@]}")
  1754. # Set up essential directory paths
  1755. pacstrap_dir="${work_dir}/${arch}/airootfs"
  1756. isofs_dir="${work_dir}/iso"
  1757. # Create working directory
  1758. _run_once _make_work_dir
  1759. # Write build date to file if it does not exist already
  1760. [[ -e "${work_dir}/build_date" ]] || printf '%s\n' "$SOURCE_DATE_EPOCH" >"${work_dir}/build_date"
  1761. [[ "${quiet}" == "y" ]] || _show_config
  1762. _run_dual '_run_once _make_pacman_conf'
  1763. [[ -z "${gpg_key}" ]] || _run_once _export_gpg_publickey
  1764. _run_dual '_run_once _make_custom_airootfs' \
  1765. '_run_once _make_packages'
  1766. _run_dual '_run_once _make_version'
  1767. _run_dual '_run_once _make_customize_airootfs'
  1768. _run_dual '_run_once _make_pkglist'
  1769. if [[ "${buildmode}" == 'netboot' ]]; then
  1770. _run_dual '_run_once _make_boot_on_iso9660'
  1771. else
  1772. _make_bootmodes
  1773. fi
  1774. _run_dual '_run_once _cleanup_pacstrap_dir' \
  1775. '_run_once _prepare_airootfs_image'
  1776. }
  1777. # Build the bootstrap buildmode
  1778. _build_buildmode_bootstrap() {
  1779. local run_once_mode="${buildmode}"
  1780. # shellcheck disable=SC2034
  1781. local image_name_armv7h=""
  1782. local image_name_i686=""
  1783. local image_name_x86_64=""
  1784. if [[ "${arch}" == "dual" ]]; then
  1785. image_name_i686="${iso_name}-bootstrap-${iso_version}-i686.tar.gz"
  1786. image_name_x86_64="${iso_name}-bootstrap-${iso_version}-x86_64.tar.gz"
  1787. local image_name="${image_name_i686} ${image_name_x86_64}"
  1788. local buildmode_packages="${bootstrap_packages_dual}"
  1789. # Set the package lists to use
  1790. local buildmode_pkg_list_i686=("${bootstrap_pkg_list_i686[@]}")
  1791. local buildmode_pkg_list_x86_64=("${bootstrap_pkg_list_x86_64[@]}")
  1792. local buildmode_pkg_list=("${bootstrap_pkg_list[@]}")
  1793. # Set up essential directory paths
  1794. pacstrap_dir_i686="${work_dir}/i686/bootstrap/root.i686"
  1795. pacstrap_dir_x86_64="${work_dir}/x86_64/bootstrap/root.x86_64"
  1796. [[ -d "${work_dir}" ]] || install -d -- "${work_dir}"
  1797. install -d -m 0755 -o 0 -g 0 -- "${pacstrap_dir_i686}" "${pacstrap_dir_x86_64}"
  1798. else
  1799. local image_name="${iso_name}-bootstrap-${iso_version}-${arch}.tar.gz"
  1800. local buildmode_packages="${bootstrap_packages}"
  1801. # Set the package list to use
  1802. local buildmode_pkg_list=("${bootstrap_pkg_list[@]}")
  1803. # Set up essential directory paths
  1804. pacstrap_dir="${work_dir}/${arch}/bootstrap/root.${arch}"
  1805. [[ -d "${work_dir}" ]] || install -d -- "${work_dir}"
  1806. install -d -m 0755 -o 0 -g 0 -- "${pacstrap_dir}"
  1807. fi
  1808. [[ "${quiet}" == "y" ]] || _show_config
  1809. _run_dual '_run_once _make_pacman_conf'
  1810. _run_dual '_run_once _make_packages'
  1811. _run_dual '_run_once _make_version'
  1812. _run_dual '_run_once _make_pkglist'
  1813. _run_dual '_run_once _cleanup_pacstrap_dir'
  1814. _run_dual '_run_once _build_bootstrap_image'
  1815. }
  1816. # Build the netboot buildmode
  1817. _build_buildmode_netboot() {
  1818. local run_once_mode="${buildmode}"
  1819. _build_iso_base
  1820. if [[ -e "${isofs_dir}/${install_dir}/${arch}/airootfs.sfs" ]]; then
  1821. airootfs_image_filename="${isofs_dir}/${install_dir}/${arch}/airootfs.sfs"
  1822. elif [[ -e "${isofs_dir}/${install_dir}/${arch}/airootfs.erofs" ]]; then
  1823. airootfs_image_filename="${isofs_dir}/${install_dir}/${arch}/airootfs.erofs"
  1824. fi
  1825. if [[ -v cert_list ]]; then
  1826. _run_once _sign_netboot_artifacts
  1827. fi
  1828. _run_once _export_netboot_artifacts
  1829. }
  1830. # Build the ISO buildmode
  1831. _build_buildmode_iso() {
  1832. local image_name="${iso_name}-${iso_version}-${arch}.iso"
  1833. local run_once_mode="${buildmode}"
  1834. efibootimg="${work_dir}/efiboot.img"
  1835. _build_iso_base
  1836. _run_once _build_iso_image
  1837. }
  1838. # build all buildmodes
  1839. _build() {
  1840. local buildmode
  1841. local run_once_mode="build"
  1842. for buildmode in "${buildmodes[@]}"; do
  1843. _run_once "_build_buildmode_${buildmode}"
  1844. done
  1845. if (( rm_work_dir )); then
  1846. _msg_info 'Removing the working directory...'
  1847. rm -rf -- "${work_dir:?}/"
  1848. _msg_info 'Done!'
  1849. fi
  1850. }
  1851. while getopts 'c:p:C:L:P:A:D:w:m:o:g:G:vrh?' arg; do
  1852. case "${arg}" in
  1853. p) read -r -a override_pkg_list <<<"${OPTARG}" ;;
  1854. C) override_pacman_conf="${OPTARG}" ;;
  1855. L) override_iso_label="${OPTARG}" ;;
  1856. P) override_iso_publisher="${OPTARG}" ;;
  1857. A) override_iso_application="${OPTARG}" ;;
  1858. D) override_install_dir="${OPTARG}" ;;
  1859. c) read -r -a override_cert_list <<<"${OPTARG}" ;;
  1860. w) override_work_dir="${OPTARG}" ;;
  1861. m) read -r -a override_buildmodes <<<"${OPTARG}" ;;
  1862. o) override_out_dir="${OPTARG}" ;;
  1863. g) override_gpg_key="${OPTARG}" ;;
  1864. G) override_gpg_sender="${OPTARG}" ;;
  1865. v) override_quiet="n" ;;
  1866. r) declare -i override_rm_work_dir=1 ;;
  1867. h|?) _usage 0 ;;
  1868. *)
  1869. _msg_error "Invalid argument '${arg}'" 0
  1870. _usage 1
  1871. ;;
  1872. esac
  1873. done
  1874. shift $((OPTIND - 1))
  1875. if (( $# < 1 )); then
  1876. _msg_error "No profile specified" 0
  1877. _usage 1
  1878. fi
  1879. if (( EUID != 0 )); then
  1880. _msg_error "${app_name} must be run as root." 1
  1881. fi
  1882. # get the absolute path representation of the first non-option argument
  1883. profile="$(realpath -- "${1}")"
  1884. # Read SOURCE_DATE_EPOCH from file early
  1885. build_date_file="$(realpath -q -- "${override_work_dir:-./work}/build_date")" || :
  1886. if [[ -f "$build_date_file" ]]; then
  1887. SOURCE_DATE_EPOCH="$(<"$build_date_file")"
  1888. fi
  1889. unset build_date_file
  1890. _read_profile
  1891. _set_overrides
  1892. _validate_options
  1893. _build
  1894. # vim:ts=4:sw=4:et: