armbian_rootenc_setup.sh 44 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520
  1. #!/usr/bin/env bash
  2. PATH="$PATH:/usr/sbin:/sbin"
  3. RED="\e[31;1m" GREEN="\e[32;1m" YELLOW="\e[33;1m" BLUE="\e[34;1m" PURPLE="\e[35;1m" RESET="\e[0m"
  4. PROGNAME=$(basename $0)
  5. TITLE='Armbian Encrypted Root Filesystem Setup'
  6. CONFIG_VARS='
  7. ARMBIAN_IMAGE
  8. BOOTPART_LABEL
  9. ROOTFS_NAME
  10. DISK_PASSWD
  11. UNLOCKING_USERHOST
  12. SERIAL_CONSOLE
  13. IP_ADDRESS
  14. NETMASK
  15. ADD_ALL_MODS
  16. ADD_MODS
  17. USE_LOCAL_AUTHORIZED_KEYS
  18. USB_GADGET
  19. ETH_DEV
  20. '
  21. STATES='
  22. card_partitioned
  23. bootpart_copied
  24. bootpart_label_created
  25. rootpart_copied
  26. target_configured
  27. '
  28. USER_OPTS_INFO="
  29. NO_CLEANUP - no cleanup of mounts after program run
  30. FORCE_REBUILD - force full rebuild
  31. FORCE_RECONFIGURE - force reconfiguration
  32. FORCE_REFORMAT_ROOT - force reformat of encrypted root partition
  33. ADD_ALL_MODS - add all currently loaded modules to initramfs
  34. ADD_MODS y add specified modules to initramfs
  35. USE_LOCAL_AUTHORIZED_KEYS - use local 'authorized_keys' file if available
  36. PARTITION_ONLY - partition and create filesystems only
  37. ERASE - zero boot sector, boot partition and beginning of root partition
  38. ROOTENC_REUSE_FS - reuse existing filesystems (for development only)
  39. ROOTENC_TESTING - developer tweaks
  40. ROOTENC_PAUSE - pause along the way
  41. ROOTENC_IGNORE_APT_ERRORS - continue even if apt update fails
  42. SERIAL_CONSOLE - enable disk unlocking via serial console
  43. USB_GADGET - enable disk unlocking via SSH over USB (g_ether)
  44. ETH_DEV - select the network device manually (default: auto)
  45. VERBOSE - produce verbose output
  46. "
  47. RSYNC_VERBOSITY='--info=progress2'
  48. print_help() {
  49. echo " ${PROGNAME^^}: Create an Armbian image with encrypted root filesystem
  50. USAGE: $PROGNAME [options] <target device name (e.g. SD card)>
  51. OPTIONS: '-h' Print this help message
  52. '-C' Don't perform unmounts or clean up build directory at exit
  53. '-d' Produce tons of debugging output
  54. '-f' Force reconfiguration of target system
  55. '-F' Force a complete rebuild of target system
  56. '-m' Add all currently loaded modules to the initramfs (may help
  57. fix blank screen on bootup issues)
  58. '-o' Add specified modules to the initramfs (comma-separated list)
  59. '-M' Mount source and target systems and exit
  60. '-U' Unmount source and target systems and exit
  61. '-p' Partition and create filesystems only. Do not copy data
  62. '-R' Force reformat of encrypted root partition
  63. '-s' Use 'authorized_keys' file from working directory, if available
  64. (see below)
  65. '-v' Be more verbose
  66. '-u' Perform an 'apt upgrade' after each 'apt update'
  67. '-z' Erase boot sector and first partition of SD card before partitioning
  68. (an extra paranoia step, but it can’t hurt)
  69. For non-interactive operation, set the following variables in your environment
  70. or on the command line:
  71. ARMBIAN_IMAGE - Armbian image file
  72. ROOTFS_NAME - device mapper name of target root filesystem
  73. IP_ADDRESS - IP address of target (set to 'dhcp' for dynamic IP
  74. or 'none' to disable remote SSH unlocking support)
  75. NETMASK - Netmask of target. Defaults to 255.255.255.0
  76. BOOTPART_LABEL - Boot partition label of target
  77. DISK_PASSWD - Disk password of target root filesystem
  78. UNLOCKING_USERHOST - USER@HOST of remote unlocking host
  79. SERIAL_CONSOLE - Set this to 'y' to enable disk unlocking from the
  80. serial console, or 'n' to disable
  81. USB_GADGET - Set this to 'y' to enable disk unlocking via SSH over
  82. USB (g_ether), or 'n' to disable
  83. ETH_DEV - Select the network device manually (default: auto)
  84. INSTRUCTIONS FOR USE
  85. This script must be invoked as superuser on a running Armbian system.
  86. Packages will be installed using APT, so the system must be Internet-
  87. connected and its clock correctly set.
  88. If remote unlocking via SSH is desired, the unlocking host must be reachable.
  89. Alternatively, SSH public keys for the unlocking host or hosts may be
  90. provided in the file 'authorized_keys' in the current directory. This file
  91. has the same format as a standard SSH 'authorized_keys' file.
  92. Architecture of host and target (e.g. 64-bit or 32-bit ARM) must be the same.
  93. For best results, the host and target hardware should also be identical or
  94. similar. Building on a host with more memory than the target, for example,
  95. may lead to disk unlocking failure on the target. For most users, who’ll be
  96. building for the currently-running board, this point is a non-issue.
  97. 1. Place an Armbian boot image file for the target system in the current
  98. directory. For best results, the image file should match the Debian
  99. or Ubuntu release of the host system.
  100. 2. Insert a USB card reader with a blank micro-SD card for the target
  101. system into the host’s USB port.
  102. 3. Determine the SD card’s device name using 'dmesg' or 'lsblk'.
  103. 4. Invoke the script with the device name as argument. If any options
  104. are desired, they must precede the device name.
  105. If the board has an eMMC, it may be used as the target device instead of
  106. an SD card. Depending on your platform, you may need to run ‘armbian-config’
  107. and select ‘Install to internal storage’ -> ‘Install/Update the bootloader
  108. on eMMC’ to enable booting from the eMMC." | less
  109. }
  110. pause() {
  111. echo -ne $GREEN'(Press any key to continue)'$RESET >&$stderr_dup
  112. read
  113. no_fmsg=1
  114. }
  115. _debug_pause() { [ "$ROOTENC_PAUSE" ] && pause; true; }
  116. imsg() { echo -e "$1" >&$stdout_dup; no_fmsg=1; }
  117. imsg_nonl() { echo -ne "$1" >&$stdout_dup; no_fmsg=1; }
  118. tmsg() {
  119. no_fmsg=1
  120. [ "$ROOTENC_TESTING" ] || return 0
  121. echo -e "$1" >&$stdout_dup
  122. }
  123. warn() { echo -e "$YELLOW$1$RESET" >&$stdout_dup; no_fmsg=1; }
  124. warn_nonl() { echo -ne "$YELLOW$1$RESET" >&$stdout_dup; no_fmsg=1; }
  125. rmsg() { echo -e "$RED$1$RESET" >&$stdout_dup; no_fmsg=1; }
  126. gmsg() { echo -e "$GREEN$1$RESET" >&$stdout_dup; no_fmsg=1; }
  127. pu_msg() { echo -e "$PURPLE$1$RESET" >&$stdout_dup; no_fmsg=1; }
  128. do_partprobe() {
  129. if [ "$VERBOSE" ]; then
  130. partprobe
  131. else
  132. partprobe 2>/dev/null
  133. fi
  134. no_fmsg=1
  135. }
  136. _show_output() { [ "$VERBOSE" ] || exec 1>&$stdout_dup 2>&$stderr_dup; }
  137. _hide_output() { [ "$VERBOSE" ] || exec &>'/dev/null'; }
  138. bailout() { exit; }
  139. die() {
  140. echo -e "$RED$1$RESET" >&$stdout_dup
  141. no_fmsg=1
  142. exit 1
  143. }
  144. _return_handler() {
  145. local funcname=${FUNCNAME[1]} exitval=$? res
  146. if [ "${funcname:0:1}" == '_' -o "$no_fmsg" ]; then
  147. no_fmsg=
  148. return 0
  149. fi
  150. if [ "$exitval" -eq 0 ]; then res='OK'; else res="False ($exitval)"; fi
  151. printf "$BLUE%-32s $res$RESET\n" "$funcname" >&$stdout_dup
  152. }
  153. _sigint_handler() {
  154. warn "\nExiting at user request"
  155. exit 1
  156. }
  157. _error_handler() {
  158. local exitval=$?
  159. warn "$(basename ${BASH_SOURCE[1]}):${BASH_LINENO[0]}: ${FUNCNAME[1]}() failed at command '$BASH_COMMAND'"
  160. rmsg "$SCRIPT_DESC exiting with error ($exitval)"
  161. }
  162. _do_header() {
  163. echo
  164. local reply
  165. if banner=$(toilet --filter=border --filter=gay --width=80 --font=term "*** $TITLE ***" 2>/dev/null); then
  166. while read reply; do
  167. echo -e " $reply"
  168. done <<-EOF
  169. $banner
  170. EOF
  171. else
  172. echo -n ' '
  173. echo $TITLE
  174. echo
  175. fi
  176. echo " For detailed usage information,"
  177. echo " invoke with the '-h' switch"
  178. echo
  179. }
  180. _warn_user_opts() {
  181. local out opt have_optarg text
  182. while read opt have_optarg text; do
  183. [ "$opt" ] || continue
  184. if [ "${!opt}" ]; then
  185. if [ $have_optarg == 'y' ]; then
  186. out+=" + $text (${!opt})\n"
  187. else
  188. out+=" + $text\n"
  189. fi
  190. fi
  191. done <<<$USER_OPTS_INFO
  192. if [ "$out" ]; then
  193. warn "\n The following user options are in effect:"
  194. warn_nonl "${out}"
  195. fi
  196. }
  197. _set_host_vars() {
  198. BUILD_DIR='armbian_rootenc_build'
  199. SRC_ROOT="$BUILD_DIR/src"
  200. BOOT_ROOT="$BUILD_DIR/boot"
  201. TARGET_ROOT="$BUILD_DIR/target"
  202. CONFIG_VARS_FILE="$BOOT_ROOT/.rootenc_config_vars"
  203. host_distro=$(lsb_release --short --codename)
  204. host_kernel=$(ls '/boot' | egrep '^vmlinu[xz]') # allow 'vmlinux' or 'vmlinuz'
  205. }
  206. check_sdcard_name_and_params() {
  207. local dev chk
  208. dev=$1
  209. [ "$dev" ] || die "You must supply a device name"
  210. [ "${dev:0:5}" == '/dev/' ] || dev="/dev/$dev"
  211. [ -e "$dev" ] || die "$dev does not exist"
  212. chk="$(lsblk --noheadings --nodeps --list --output=TYPE $dev 2>/dev/null)"
  213. [ "$chk" != 'disk' -a "$chk" != 'loop' ] && {
  214. [ "$chk" == 'part' ] && die "$dev is a partition, not a block device!"
  215. die "$dev is not a block device!"
  216. }
  217. local pttype size oversize removable non_removable part_sep
  218. pttype=$(blkid --output=udev $dev | grep TYPE | cut -d '=' -f2)
  219. size="$(lsblk --noheadings --nodeps --list --output=SIZE --bytes $dev 2>/dev/null)"
  220. removable="$(lsblk --noheadings --nodeps --list --output=RM $dev 2>/dev/null)"
  221. oversize=$([ $size -gt 137438953472 ] && echo 'Size is > 128GiB') || true
  222. non_removable=$([ $removable -ne 0 ] || echo 'Device is non-removable')
  223. SD_INFO="$(lsblk --noheadings --nodeps --list --output=VENDOR,MODEL,SIZE $dev 2>/dev/null)"
  224. SD_INFO=${SD_INFO// / }
  225. if [ "$oversize" -o "$non_removable" ]; then
  226. warn " $dev ($SD_INFO) doesn’t appear to be an SD card"
  227. warn " for the following reasons:"
  228. if [ "$non_removable" ]; then warn " $non_removable"; fi
  229. if [ "$oversize" ]; then warn " $oversize"; fi
  230. _user_confirm ' Are you sure this is the device you want to install on?' 'no'
  231. fi
  232. SDCARD_DEVNAME=${dev:5}
  233. [ "${SDCARD_DEVNAME%[0-9]}" == $SDCARD_DEVNAME ] || part_sep='p'
  234. BOOT_DEVNAME=$SDCARD_DEVNAME${part_sep}1
  235. ROOT_DEVNAME=$SDCARD_DEVNAME${part_sep}2
  236. [ "$SDCARD_DEVNAME" ] || die 'You must supply a device name for the SD card!'
  237. pu_msg "Will write to target $dev ($SD_INFO)"
  238. }
  239. _get_user_var() {
  240. local var desc dfl prompt pat pat_errmsg vtest cprompt seen_prompt reply redo
  241. var=$1 desc=$2 dfl=$3 prompt=$4 pat=$5 pat_errmsg=$6 vtest=$7
  242. [ "$MOUNT_TARGET_ONLY" ] && [[ ! $var =~ ^(DISK_PASSWD|ROOTFS_NAME)$ ]] && {
  243. eval "$var=$dfl"
  244. return 0
  245. }
  246. if [ "$pat" == 'bool' ]; then READOPTS='-n1' READNL="\n"; else READOPTS='' READNL=''; fi
  247. while true; do
  248. if [ -z "${!var}" -o "$seen_prompt" -o "$redo" ]; then
  249. if [ "$seen_prompt" ]; then
  250. echo -n " Enter $desc: "
  251. else
  252. cprompt=
  253. while read reply; do
  254. cprompt+=" ${reply## }\n"
  255. done <<-EOF
  256. $prompt
  257. EOF
  258. echo
  259. if [ "$dfl" ]; then
  260. printf "${cprompt:0:-2} " "$dfl"
  261. else
  262. echo -ne "${cprompt:0:-2} "
  263. fi
  264. seen_prompt=1
  265. fi
  266. eval "read $READOPTS $var"; echo -ne "$READNL"
  267. fi
  268. redo=1
  269. if [ -z "${!var}" -a "$dfl" ]; then eval "$var=$dfl"; fi
  270. [ "${!var}" ] || {
  271. rmsg " $desc must not be empty"
  272. continue
  273. }
  274. if [ "$pat" ]; then
  275. local rpat=$pat
  276. if [ "$pat" == 'bool' ]; then
  277. rpat='^[ynYN]*$'
  278. pat_errmsg="You must type 'y' or 'n'"
  279. fi
  280. echo "${!var}" | egrep -qi "$rpat" || {
  281. rmsg " ${!var}: $pat_errmsg"
  282. continue
  283. }
  284. if [ "$pat" == 'bool' ]; then
  285. if [[ ${!var} =~ ^[Yy]$ ]]; then eval "$var=yes"; else eval "$var="; fi
  286. fi
  287. fi
  288. if [ "$vtest" ]; then
  289. $vtest || continue
  290. fi
  291. break
  292. done
  293. }
  294. _check_eth_dev() {
  295. if [ "$ETH_DEV" ]; then
  296. ip link show $ETH_DEV || {
  297. warn "User-selected network device '$ETH_DEV' could not be found"
  298. _user_confirm "Are you sure you want to continue?" "no"
  299. }
  300. fi
  301. }
  302. _get_user_vars() {
  303. local dq='[0-9]{1,3}'
  304. _get_user_var 'IP_ADDRESS' 'IP address' '' \
  305. "Enter the IP address of the target machine.
  306. Enter 'dhcp' for a dynamic IP or 'none' for no remote SSH unlocking support
  307. IP address:" \
  308. "^(dhcp|none|$dq\.$dq\.$dq\.$dq)$" \
  309. 'malformed IP address'
  310. IP_ADDRESS=${IP_ADDRESS,,}
  311. [[ $IP_ADDRESS =~ ^(dhcp|none)$ ]] || {
  312. _get_user_var 'NETMASK' 'netmask' '255.255.255.0' \
  313. "Enter the netmask of the target machine,
  314. or hit ENTER for the default (%s): " \
  315. "^($dq\.$dq\.$dq\.$dq)$" \
  316. 'malformed netmask'
  317. }
  318. _get_user_var 'BOOTPART_LABEL' 'boot partition label' 'ARMBIAN_BOOT' \
  319. "Enter a boot partition label for the target machine,
  320. or hit ENTER for the default (%s): " \
  321. '^[A-Za-z0-9_]{1,16}$' \
  322. "Label must contain no more than 16 characters in the set 'A-Za-z0-9_'"
  323. _get_user_var 'ROOTFS_NAME' 'root filesystem device name' 'rootfs' \
  324. "Enter a device name for the encrypted root filesystem,
  325. or hit ENTER for the default (%s):" \
  326. '^[a-z0-9_]{1,48}$' \
  327. "Name must contain no more than 48 characters in the set 'a-z0-9_'" \
  328. '_test_rootfs_mounted'
  329. if [ "$MOUNT_TARGET_ONLY" ]; then
  330. local pw_prompt="Enter disk password:"
  331. else
  332. local pw_prompt="Choose a simple disk password for the installation process.
  333. Once your encrypted system is up and running, you can change
  334. the password using the 'cryptsetup' command.
  335. Enter password:"
  336. fi
  337. _get_user_var 'DISK_PASSWD' 'disk password' '' "$pw_prompt" \
  338. '^[A-Za-z0-9_ ]{1,10}$' \
  339. "Temporary disk password must contain no more than 10 characters in the set 'A-Za-z0-9_ '"
  340. if [ "$IP_ADDRESS" == 'none' ]; then
  341. UNLOCKING_USERHOST=
  342. else
  343. _get_user_var 'UNLOCKING_USERHOST' 'USER@HOST' '' \
  344. "Enter the user@host of the machine you'll be unlocking from:" \
  345. '\S+@\S+' \
  346. 'malformed USER@HOST'
  347. fi
  348. _get_user_var 'SERIAL_CONSOLE' 'serial console unlocking' '' \
  349. "Unlock the disk from the serial console. WARNING: enabling this will
  350. make it impossible to unlock the disk using the keyboard and monitor,
  351. though unlocking via SSH will still work.
  352. Enable unlocking via serial console? (y/n):" \
  353. 'bool'
  354. _get_user_var 'USB_GADGET' 'disk unlocking via SSH over USB (g_ether)' '' \
  355. "Unlock the disk via SSH over USB (g_ether). Enable this only if your board
  356. supports USB gadget mode, i.e. if it has a USB OTG port. WARNING: enabling this
  357. will make it impossible to unlock the disk over the Ethernet interface (eth0).
  358. Enable unlocking via SSH over USB? (y/n):" \
  359. 'bool'
  360. true
  361. }
  362. _test_rootfs_mounted() {
  363. [ -e "/dev/mapper/$ROOTFS_NAME" ] && {
  364. local mnt=$(lsblk --list --noheadings --output=MOUNTPOINT /dev/mapper/$ROOTFS_NAME)
  365. [ "$mnt" ] && {
  366. rmsg " Device '$ROOTFS_NAME' is in use and mounted on $mnt"
  367. return 1
  368. }
  369. }
  370. return 0
  371. }
  372. _test_unlocking_host_available() {
  373. local ul_host=${UNLOCKING_USERHOST#*@}
  374. ping -c1 $ul_host &>/dev/null || {
  375. rmsg " Unable to ping host '$ul_host'"
  376. return 1
  377. }
  378. }
  379. _test_sdcard_mounted() {
  380. local chk="$(lsblk --noheadings --list --output=MOUNTPOINT /dev/$SDCARD_DEVNAME)"
  381. [ -z "$chk" ] || {
  382. lsblk --output=NAME,SIZE,TYPE,FSTYPE,MOUNTPOINT /dev/$SDCARD_DEVNAME
  383. die "Device /dev/$SDCARD_DEVNAME has mounted partitions!"
  384. }
  385. }
  386. get_authorized_keys() {
  387. [ -f 'authorized_keys' ] && rm -rf 'authorized_keys' # remove legacy file if present
  388. authorized_keys_dir="authorized_keys-$UNLOCKING_USERHOST"
  389. [ -e $authorized_keys_dir -a "$USE_LOCAL_AUTHORIZED_KEYS" ] || {
  390. _test_unlocking_host_available
  391. mkdir -p $authorized_keys_dir
  392. rsync "$UNLOCKING_USERHOST:.ssh/id_*.pub" $authorized_keys_dir
  393. NEW_AUTHORIZED_KEYS='y'
  394. }
  395. }
  396. _apt_update() {
  397. [ "$ROOTENC_IGNORE_APT_ERRORS" ] && set +e
  398. apt --yes update
  399. [ "$APT_UPGRADE" ] && apt --yes upgrade
  400. [ "$ROOTENC_IGNORE_APT_ERRORS" ] && set -e
  401. true
  402. }
  403. _print_pkgs_to_install() {
  404. local pkgs pkgs_ssh
  405. case $1 in
  406. 'host')
  407. case "$host_distro" in
  408. bionic|buster|focal|bullseye|jammy|bookworm|noble|trixie)
  409. pkgs='cryptsetup ed' ;;
  410. *)
  411. pkgs='cryptsetup ed'
  412. warn "Warning: unrecognized host distribution '$host_distro'" ;;
  413. esac ;;
  414. 'target')
  415. case "$target_distro" in
  416. buster|focal|bullseye|jammy|bookworm|noble|trixie)
  417. pkgs='cryptsetup-initramfs' pkgs_ssh='dropbear-initramfs' ;;
  418. bionic)
  419. pkgs='cryptsetup' pkgs_ssh='dropbear-initramfs' ;;
  420. *)
  421. pkgs='cryptsetup-initramfs' pkgs_ssh='dropbear-initramfs'
  422. warn "Warning: unrecognized target distribution '$target_distro'" ;;
  423. esac
  424. [ "$IP_ADDRESS" != 'none' ] && pkgs+=" $pkgs_ssh" ;;
  425. esac
  426. for i in $pkgs; do
  427. dpkg -l $i 2>/dev/null | grep -q ^ii || echo $i
  428. done
  429. }
  430. apt_install_host_pkgs() {
  431. local pkgs=$(_print_pkgs_to_install 'host')
  432. [ "$pkgs" ] && {
  433. _apt_update
  434. apt --yes install $pkgs
  435. }
  436. true
  437. }
  438. create_build_dir() {
  439. mkdir -p $BUILD_DIR
  440. mkdir -p $SRC_ROOT
  441. mkdir -p $BOOT_ROOT
  442. mkdir -p $TARGET_ROOT
  443. }
  444. umount_target() {
  445. for i in $BOOT_ROOT $TARGET_ROOT; do
  446. while mountpoint -q $i; do
  447. umount -Rl $i
  448. done
  449. done
  450. }
  451. remove_build_dir() {
  452. [ -d $TARGET_ROOT ] && rmdir $TARGET_ROOT
  453. [ -d $BOOT_ROOT ] && rmdir $BOOT_ROOT
  454. [ -d $SRC_ROOT ] && rmdir $SRC_ROOT
  455. [ -d $BUILD_DIR ] && rmdir $BUILD_DIR
  456. true
  457. }
  458. _get_device_maps() {
  459. local dm_type=$1 varname="device_maps_$1" dm_name ls mp
  460. eval "$varname="
  461. while read dm_name; do
  462. [ "$dm_name" == 'No devices found' ] && break
  463. fstype="$(lsblk --noheadings --nodeps -o fstype "/dev/mapper/$dm_name")"
  464. [ "$fstype" == 'ext4' ] || continue
  465. ls=$(findmnt -n --source "/dev/mapper/$dm_name" | cut -f 1 -d ' ')
  466. if [ "$ls" -a "$dm_type" == 'mounted_on_target' ]; then
  467. while read mp; do
  468. if [ "${mp: -${#TARGET_ROOT}}" == "$TARGET_ROOT" ]; then
  469. eval "$varname+='$dm_name '"
  470. fi
  471. done <<<"$ls"
  472. elif [ -z "$ls" -a "$dm_type" == 'unmounted' ]; then
  473. eval "$varname+='$dm_name '"
  474. fi
  475. done <<<$(dmsetup ls | cut -f 1)
  476. tmsg "$varname=[${!varname}]"
  477. }
  478. _close_device_maps() {
  479. local dm_type=$1
  480. local varname="device_maps_${dm_type}"
  481. for i in ${!varname}; do
  482. tmsg "closing $i"
  483. cryptsetup status $i > '/dev/null' && cryptsetup luksClose $i
  484. done
  485. true
  486. }
  487. _preclean() {
  488. close_loopmount
  489. _get_device_maps 'unmounted'
  490. _close_device_maps 'unmounted'
  491. _get_device_maps 'mounted_on_target'
  492. umount_target
  493. _close_device_maps 'mounted_on_target'
  494. remove_build_dir
  495. }
  496. _print_success_msg() {
  497. gmsg "All done!"
  498. imsg ""
  499. imsg " You may now shut down your board, switch removable media if applicable,"
  500. imsg " and restart."
  501. if [ "$IP_ADDRESS" != "none" ]; then
  502. imsg ""
  503. imsg " To unlock the disk on the target machine, execute the following from the"
  504. imsg " unlocking host:"
  505. imsg ""
  506. imsg " ssh -p 2222 root@${IP_ADDRESS/dhcp/TARGET_IP}"
  507. fi
  508. }
  509. _clean() {
  510. pu_msg "Cleaning up, please wait..."
  511. _show_output
  512. close_loopmount
  513. _get_device_maps 'mounted_on_target'
  514. umount_target
  515. update_config_vars_file
  516. _close_device_maps 'mounted_on_target'
  517. remove_build_dir
  518. [ "$build_success" ] && _print_success_msg
  519. true
  520. }
  521. get_armbian_image() {
  522. ARMBIAN_IMAGE="$(echo *.img)"
  523. [ "$ARMBIAN_IMAGE" != '*.img' ] || {
  524. die 'No image file found: You must place an Armbian image in the current directory!'
  525. }
  526. local count=$(echo "$ARMBIAN_IMAGE" | wc -w)
  527. [ "$count" == 1 ] || die "More than one image file present!:\n$ARMBIAN_IMAGE"
  528. }
  529. get_partition_info() {
  530. local cmdout
  531. cmdout=$(fdisk --list --output='Start' $ARMBIAN_IMAGE)
  532. partition_table_type=$(echo "$cmdout" | grep '^Disklabel' | cut --delimiter=' ' --fields=3)
  533. case $partition_table_type in
  534. 'gpt')
  535. cmdout=$(fdisk --list-details --output='Start,Type-UUID' $ARMBIAN_IMAGE)
  536. read start_sector partition_type_uuid <<<$(echo "$cmdout" | tail --lines=1)
  537. ;;
  538. 'dos')
  539. read start_sector <<<$(echo "$cmdout" | tail --lines=1)
  540. ;;
  541. *) die "$partition_table_type: unrecognized partition table type!" ;;
  542. esac
  543. [ $((start_sector % 8)) -eq 0 ] || die "start sector: $start_sector: first partition misaligned!"
  544. boot_partition_sectors=409600 # 200MB
  545. pu_msg "Image partition table type: ${partition_table_type^^}"
  546. }
  547. _confirm_user_vars() {
  548. echo
  549. echo " Armbian image: $ARMBIAN_IMAGE"
  550. echo " Target device: /dev/$SDCARD_DEVNAME ($SD_INFO)"
  551. echo " Root filesystem device name: /dev/mapper/$ROOTFS_NAME"
  552. echo " Target IP address: $IP_ADDRESS"
  553. [ "$NETMASK" ] && echo " Target netmask: $NETMASK"
  554. echo " Boot partition label: $BOOTPART_LABEL"
  555. echo " Disk password: $DISK_PASSWD"
  556. [ "$UNLOCKING_USERHOST" ] && echo " unlocking user@host: $UNLOCKING_USERHOST"
  557. echo " Serial console unlocking: ${SERIAL_CONSOLE:-no}"
  558. echo " SSH over USB unlocking: ${USB_GADGET:-no}"
  559. echo " Ethernet device: ${ETH_DEV:-auto}"
  560. echo
  561. _user_confirm ' Are these settings correct?' 'yes'
  562. }
  563. setup_loopmount() {
  564. LOOP_DEV=$(losetup -f)
  565. losetup -P $LOOP_DEV $ARMBIAN_IMAGE
  566. mount ${LOOP_DEV}p1 $SRC_ROOT
  567. }
  568. _umount_with_check() {
  569. mountpoint -q $1 && umount $1
  570. }
  571. update_config_vars_file() {
  572. mount "/dev/$BOOT_DEVNAME" $BOOT_ROOT
  573. _print_config_vars $CONFIG_VARS_FILE
  574. umount $BOOT_ROOT
  575. }
  576. _print_states() {
  577. for i in $STATES; do
  578. echo $i: ${!i}
  579. done
  580. }
  581. _update_state_from_config_vars() {
  582. [ -e $CONFIG_VARS_FILE ] || return 0
  583. local reply
  584. while read reply; do
  585. eval "c$reply"
  586. done <<<$(cat $CONFIG_VARS_FILE)
  587. local saved_states cfgvar_changed
  588. saved_states="$(_print_states)"
  589. cfgvar_changed=
  590. [ "$NEW_AUTHORIZED_KEYS" ] && cfgvar_changed+=' NEW_AUTHORIZED_KEYS' target_configured='n'
  591. [ $cARMBIAN_IMAGE != $ARMBIAN_IMAGE ] && cfgvar_changed+=' ARMBIAN_IMAGE' card_partitioned='n'
  592. [ $cBOOTPART_LABEL != $BOOTPART_LABEL ] && cfgvar_changed+=' BOOTPART_LABEL' bootpart_label_created='n'
  593. [ $cROOTFS_NAME != $ROOTFS_NAME ] && cfgvar_changed+=' ROOTFS_NAME' target_configured='n'
  594. [ $cDISK_PASSWD != $DISK_PASSWD ] && cfgvar_changed+=' DISK_PASSWD' rootpart_copied='n'
  595. [ "$UNLOCKING_USERHOST" -a "$cUNLOCKING_USERHOST" != "$UNLOCKING_USERHOST" ] && {
  596. cfgvar_changed+=' UNLOCKING_USERHOST' target_configured='n'
  597. }
  598. [ "$cSERIAL_CONSOLE" != "$SERIAL_CONSOLE" ] && {
  599. cfgvar_changed+=' SERIAL_CONSOLE' target_configured='n'
  600. }
  601. [ $cIP_ADDRESS != $IP_ADDRESS ] && cfgvar_changed+=' IP_ADDRESS' target_configured='n'
  602. [ "$cNETMASK" != "$NETMASK" ] && cfgvar_changed+=' NETMASK' target_configured='n'
  603. [ "$cADD_ALL_MODS" != "$ADD_ALL_MODS" ] && cfgvar_changed+=' ADD_ALL_MODS' target_configured='n'
  604. [ "$cADD_MODS" != "$ADD_MODS" ] && cfgvar_changed+=' ADD_MODS' target_configured='n'
  605. [ "$cUSB_GADGET" != "$USB_GADGET" ] && cfgvar_changed+=' USB_GADGET' target_configured='n'
  606. [ "$cETH_DEV" != "$ETH_DEV" ] && cfgvar_changed+=' ETH_DEV' target_configured='n'
  607. [ "$IP_ADDRESS" -a "$cUSE_LOCAL_AUTHORIZED_KEYS" != "$USE_LOCAL_AUTHORIZED_KEYS" ] && {
  608. cfgvar_changed+=' USE_LOCAL_AUTHORIZED_KEYS' target_configured='n'
  609. }
  610. [ $card_partitioned == 'n' ] && {
  611. bootpart_copied='n'
  612. bootpart_label_created='n'
  613. rootpart_copied='n'
  614. target_configured='n'
  615. }
  616. [ $bootpart_copied == 'n' ] && bootpart_label_created='n'
  617. [ $rootpart_copied == 'n' ] && target_configured='n'
  618. [ "$saved_states" != "$(_print_states)" ] && {
  619. warn "Install state altered due to changed config vars:$cfgvar_changed"
  620. for i in $STATES; do
  621. if [ "${!i}" == 'n' ]; then
  622. imsg " $i: ${RED}no$RESET"
  623. else
  624. imsg " $i: ${GREEN}yes$RESET"
  625. fi
  626. done
  627. _delete_state_files
  628. }
  629. true
  630. }
  631. _add_state_file() {
  632. local state=$1 cmd=$2
  633. if [ "$cmd" == 'target' ]; then
  634. touch "$TARGET_ROOT/boot/.rootenc_install_state/$state"
  635. else
  636. [ "$cmd" == 'mount' ] && mount "/dev/$BOOT_DEVNAME" $BOOT_ROOT
  637. mkdir -p "$BOOT_ROOT/.rootenc_install_state"
  638. touch "$BOOT_ROOT/.rootenc_install_state/$state"
  639. [ "$cmd" == 'mount' ] && umount $BOOT_ROOT
  640. fi
  641. eval "$state='y'"
  642. tmsg "added state file '$state'"
  643. }
  644. _delete_state_files() {
  645. for i in $STATES; do
  646. local fn="$BOOT_ROOT/.rootenc_install_state/$i"
  647. [ ${!i} == 'n' -a -e $fn ] && /bin/rm $fn
  648. done
  649. true
  650. }
  651. _get_state_from_state_files() {
  652. for i in $STATES; do
  653. if [ -e "$BOOT_ROOT/.rootenc_install_state/$i" ]; then
  654. eval "$i=y"
  655. else
  656. eval "$i=n"
  657. fi
  658. done
  659. }
  660. _print_state_from_state_files() {
  661. imsg 'Install state:'
  662. for i in $STATES; do
  663. if [ -e "$BOOT_ROOT/.rootenc_install_state/$i" ]; then
  664. imsg " $i: ${GREEN}yes$RESET"
  665. else
  666. imsg " $i: ${RED}no$RESET"
  667. fi
  668. done
  669. }
  670. check_install_state() {
  671. for i in $STATES; do eval "$i=n"; done
  672. if [ "$FORCE_REBUILD" ]; then
  673. return
  674. else
  675. do_partprobe
  676. lsblk --noheadings --list /dev/$SDCARD_DEVNAME -o 'NAME' | grep -q $BOOT_DEVNAME || return 0
  677. lsblk --noheadings --list /dev/$BOOT_DEVNAME -o 'FSTYPE' | grep -q 'ext4' || return 0
  678. mount "/dev/$BOOT_DEVNAME" $BOOT_ROOT
  679. _get_state_from_state_files
  680. if [ "$target_configured" == 'y' -a "$FORCE_RECONFIGURE" ]; then
  681. target_configured='n'
  682. _delete_state_files
  683. fi
  684. _print_state_from_state_files
  685. _update_state_from_config_vars
  686. _umount_with_check $BOOT_ROOT
  687. fi
  688. }
  689. close_loopmount() {
  690. while mountpoint -q $SRC_ROOT; do
  691. umount $SRC_ROOT
  692. done
  693. for i in $(losetup --noheadings --raw --list -j $ARMBIAN_IMAGE | awk '{print $1}'); do
  694. losetup -d $i
  695. done
  696. }
  697. _user_confirm() {
  698. local prompt1 prompt2 dfl_action reply
  699. prompt1=$1 dfl_action=$2
  700. if [ "$dfl_action" == 'yes' ]; then
  701. prompt2='(Y/n)'
  702. else
  703. prompt2='(y/N)'
  704. fi
  705. imsg_nonl "$prompt1 $prompt2 "
  706. read -n1 reply
  707. [ "$reply" ] && imsg ''
  708. [ "$dfl_action" == 'yes' -a -z "$reply" ] && return
  709. [ "$reply" == 'y' -o "$reply" == 'Y' ] && return
  710. warn "Exiting at user request"
  711. exit 1
  712. }
  713. erase_boot_sector_and_first_partition() {
  714. local sectors count
  715. sectors=$((start_sector + boot_partition_sectors + 100))
  716. count=$((sectors / 8192 + 1))
  717. pu_msg "Erasing up to beginning of second partition ($sectors sectors, ${count}M):"
  718. _show_output
  719. dd if=/dev/zero \
  720. of=/dev/$SDCARD_DEVNAME \
  721. status=progress \
  722. bs=$((512*8192)) \
  723. count=$count
  724. _hide_output
  725. }
  726. create_partition_label() {
  727. local label_code
  728. pu_msg "Creating new partition label on /dev/$SDCARD_DEVNAME"
  729. case $partition_table_type in 'dos') label_code='o';; 'gpt') label_code='g';; esac
  730. set +e
  731. echo -e "$label_code\nw\n" | fdisk "/dev/$SDCARD_DEVNAME"
  732. set -e
  733. do_partprobe
  734. }
  735. copy_boot_loader() {
  736. local skip count
  737. # GPT bootloader starts at LBA 8 (0x8000, sector 64):
  738. case $partition_table_type in 'dos') skip=0;; 'gpt') skip=64;; esac
  739. count=$((start_sector - skip))
  740. pu_msg "Copying boot loader ($count sectors, starting at sector $skip [LBA $((skip / 8))]):"
  741. _show_output
  742. dd if=$ARMBIAN_IMAGE of="/dev/$SDCARD_DEVNAME" status='progress' bs=512 count=$count skip=$skip seek=$skip
  743. _hide_output
  744. do_partprobe
  745. }
  746. _print_config_vars() {
  747. local outfile=$1
  748. local data="$(for i in $CONFIG_VARS; do echo "$i='${!i}'"; done)"
  749. if [ "$outfile" ]; then echo "$data" > $outfile; else echo "$data"; fi
  750. }
  751. create_partitions() {
  752. local p1_end p2_start cmds1 cmds2 bname rname fstype
  753. p1_end=$((start_sector + boot_partition_sectors - 1))
  754. p2_start=$((p1_end + 1))
  755. case $partition_table_type in
  756. 'gpt')
  757. cmds1="g\nn\n1\n$start_sector\n$p1_end\nn\n2\n$p2_start\n\n"
  758. cmds2="t\n1\n$partition_type_uuid\nt\n2\n$partition_type_uuid\n"
  759. ;;
  760. 'dos')
  761. cmds1="o\nn\np\n1\n$start_sector\n$p1_end\nn\np\n2\n$p2_start\n\n"
  762. cmds2=""
  763. ;;
  764. esac
  765. set +e; trap - ERR # fdisk exits with error if partition table cannot be re-read
  766. echo -e "${cmds1}${cmds2}w\n" | fdisk "/dev/$SDCARD_DEVNAME"
  767. set -e; trap '_error_handler' ERR
  768. do_partprobe
  769. [ "$VERBOSE" ] && {
  770. pu_msg 'Done partitioning:'
  771. fdisk --list "/dev/$SDCARD_DEVNAME"
  772. imsg ""
  773. }
  774. bname="$(lsblk --noheadings --list --output=NAME /dev/$BOOT_DEVNAME)"
  775. [ "$bname" == $BOOT_DEVNAME ] || die 'Partitioning failed!'
  776. rname="$(lsblk --noheadings --list --output=NAME /dev/$ROOT_DEVNAME)"
  777. [ "$rname" == $ROOT_DEVNAME ] || die 'Partitioning failed!'
  778. # filesystem is required by call to _add_state_file(), so we must create it here
  779. fstype=$(lsblk --noheadings --list --output=FSTYPE "/dev/$BOOT_DEVNAME")
  780. [ "$fstype" == 'ext4' -a "$ROOTENC_REUSE_FS" ] || mkfs.ext4 -F "/dev/$BOOT_DEVNAME"
  781. do_partprobe
  782. _add_state_file 'card_partitioned' 'mount'
  783. }
  784. _do_partition() {
  785. imsg "All data on /dev/$SDCARD_DEVNAME ($SD_INFO) will be destroyed!!!"
  786. _user_confirm 'Are you sure you want to continue?' 'no'
  787. [ "$ERASE" ] && erase_boot_sector_and_first_partition
  788. create_partition_label
  789. copy_boot_loader
  790. create_partitions
  791. }
  792. copy_system_boot() {
  793. [ "$PARTITION_ONLY" ] && {
  794. _add_state_file 'bootpart_copied' 'mount'
  795. return
  796. }
  797. mount "/dev/$BOOT_DEVNAME" $BOOT_ROOT
  798. pu_msg "Copying files to boot partition:"
  799. _show_output
  800. rsync $RSYNC_VERBOSITY --archive $SRC_ROOT/boot/* $BOOT_ROOT
  801. _hide_output
  802. [ -e "$BOOT_ROOT/boot" ] || (cd $BOOT_ROOT && ln -s . 'boot')
  803. _add_state_file 'bootpart_copied'
  804. umount $BOOT_ROOT
  805. }
  806. create_bootpart_label() {
  807. e2label "/dev/$BOOT_DEVNAME" "$BOOTPART_LABEL"
  808. do_partprobe
  809. _add_state_file 'bootpart_label_created' 'mount'
  810. }
  811. copy_system_root() {
  812. if [ "$FORCE_REFORMAT_ROOT" ] || ! cryptsetup isLuks "/dev/$ROOT_DEVNAME"; then
  813. pu_msg "Formatting encrypted root partition:"
  814. echo -n $DISK_PASSWD | cryptsetup luksFormat "/dev/$ROOT_DEVNAME" '-'
  815. fi
  816. echo $DISK_PASSWD | cryptsetup luksOpen "/dev/$ROOT_DEVNAME" $ROOTFS_NAME
  817. local fstype=$(lsblk --noheadings --list --output=FSTYPE "/dev/mapper/$ROOTFS_NAME")
  818. [ "$fstype" == 'ext4' -a "$ROOTENC_REUSE_FS" ] || mkfs.ext4 -F "/dev/mapper/$ROOTFS_NAME"
  819. [ "$PARTITION_ONLY" ] || {
  820. mount "/dev/mapper/$ROOTFS_NAME" $TARGET_ROOT
  821. pu_msg "Copying system to encrypted root partition:"
  822. _show_output
  823. rsync $RSYNC_VERBOSITY --archive --exclude='boot' --exclude='var/cache/apt/archives' $SRC_ROOT/* $TARGET_ROOT
  824. _hide_output
  825. sync
  826. mkdir -p "$TARGET_ROOT/boot"
  827. touch "$TARGET_ROOT/root/.no_rootfs_resize"
  828. umount $TARGET_ROOT
  829. }
  830. cryptsetup luksClose $ROOTFS_NAME
  831. do_partprobe
  832. _add_state_file 'rootpart_copied' 'mount'
  833. }
  834. mount_target() {
  835. echo $DISK_PASSWD | cryptsetup luksOpen "/dev/$ROOT_DEVNAME" $ROOTFS_NAME
  836. mount "/dev/mapper/$ROOTFS_NAME" $TARGET_ROOT
  837. mount "/dev/$BOOT_DEVNAME" "$TARGET_ROOT/boot"
  838. local src dest args
  839. while read src dest args; do
  840. mount $args $src $TARGET_ROOT/$dest
  841. done <<-EOF
  842. udev dev -t devtmpfs -o rw,relatime,nosuid,mode=0755
  843. devpts dev/pts -t devpts
  844. tmpfs dev/shm -t tmpfs -o rw,nosuid,nodev,relatime
  845. proc proc -t proc
  846. sys sys -t sysfs
  847. EOF
  848. }
  849. _copy_to_target() {
  850. local fn=$1
  851. if [ -e $fn ]; then
  852. echo "Copying '$fn'"
  853. rm -rf $TARGET_ROOT/$fn
  854. cat $fn > $TARGET_ROOT/$fn
  855. else
  856. imsg "Unable to copy '$fn' to target (file does not exist)"
  857. false
  858. fi
  859. }
  860. create_etc_crypttab() {
  861. local root_uuid="$(lsblk --noheadings --list --nodeps --output=UUID /dev/$ROOT_DEVNAME)"
  862. echo "$ROOTFS_NAME UUID=$root_uuid none initramfs,luks" > "$TARGET_ROOT/etc/crypttab"
  863. _display_file "$TARGET_ROOT/etc/crypttab"
  864. }
  865. copy_etc_files() {
  866. _copy_to_target '/etc/resolv.conf'
  867. _copy_to_target '/etc/hosts'
  868. set +e
  869. _copy_to_target /etc/apt/apt.conf.d/*proxy
  870. set -e
  871. }
  872. _print_net_dev() {
  873. local text net_pfx
  874. text="$(ip --brief link)"
  875. for net_pfx in eth enp end eno enP enD enO en; do
  876. grepout="$(echo "$text" | grep ^$net_pfx)" || true
  877. if [ "$grepout" ]; then
  878. echo "$grepout" | head --lines=1 | cut --delimiter=' ' --field=1
  879. return 0
  880. fi
  881. done
  882. }
  883. _set_target_vars() {
  884. target_distro=$(chroot $TARGET_ROOT 'lsb_release' '--short' '--codename')
  885. target_kernel=$(chroot $TARGET_ROOT 'ls' '/boot' | egrep '^vmlinu[xz]')
  886. target_armbian_keyring_signed=
  887. local dfl_eth_dev
  888. case $target_distro in
  889. bionic|buster|focal)
  890. dfl_eth_dev='eth0'
  891. dropbear_dir='/etc/dropbear-initramfs'
  892. dropbear_conf='config' ;;
  893. bullseye|jammy)
  894. dfl_eth_dev='eth0'
  895. dropbear_dir='/etc/dropbear/initramfs'
  896. dropbear_conf='config' ;;
  897. bookworm|noble|trixie|*)
  898. dfl_eth_dev='end0'
  899. dropbear_dir='/etc/dropbear/initramfs'
  900. dropbear_conf='dropbear.conf'
  901. target_armbian_keyring_signed='y' ;;
  902. esac
  903. host_eth_dev=$(_print_net_dev)
  904. [ "$host_eth_dev" ] || die 'Unable to find default wired network device'
  905. if [ "$ETH_DEV" ]; then
  906. eth_dev=$ETH_DEV
  907. elif _distros_match; then
  908. eth_dev=$host_eth_dev
  909. else
  910. eth_dev=$dfl_eth_dev
  911. fi
  912. imsg "$(printf '%-8s %-28s %s' '' 'Host' 'Target')"
  913. imsg "$(printf '%-8s %-28s %s' '' '----' '------')"
  914. imsg "$(printf '%-8s %-28s %s' 'distro:' $host_distro $target_distro)"
  915. imsg "$(printf '%-8s %-28s %s' 'kernel:' $host_kernel $target_kernel)"
  916. imsg "network device: $YELLOW$eth_dev$RESET"
  917. }
  918. _distros_match() {
  919. [ $host_distro == $target_distro ]
  920. }
  921. _kernels_match() {
  922. [ ${host_kernel%.*} == ${target_kernel%.*} ] || return 1
  923. [ ${host_kernel##*-} == ${target_kernel##*-} ]
  924. }
  925. copy_etc_files_distro_specific() {
  926. local files='/etc/apt/sources.list /etc/apt/sources.list.d/armbian.list'
  927. if _distros_match; then
  928. for i in $files; do _copy_to_target $i; done
  929. else
  930. warn 'Warning: host and target distros do not match, attempting to rewrite files:'
  931. for i in $files; do
  932. imsg " rewriting $i"
  933. if [ "$target_armbian_keyring_signed" -a $(basename $i) == 'armbian.list' ]; then
  934. repl='deb [signed-by=\/usr\/share\/keyrings\/armbian.gpg] http'
  935. sed "s/$host_distro/$target_distro/g;s/deb http/$repl/" <$i >$TARGET_ROOT/$i
  936. else
  937. sed "s/$host_distro/$target_distro/g" <$i >$TARGET_ROOT/$i
  938. fi
  939. _display_file $TARGET_ROOT$i
  940. done
  941. fi
  942. }
  943. _display_file() {
  944. local name text reply
  945. if [ "$2" ]; then
  946. name="$1"
  947. text="$2"
  948. else
  949. name=${1#$TARGET_ROOT}
  950. text="$(cat $1)"
  951. fi
  952. hl='────────────────────────────────────────'
  953. hl="$hl$hl$hl"
  954. hls=${hl:0:${#name}+1}
  955. echo "┌─$hls─┐"
  956. echo "│ $name: │"
  957. echo "├─$hls─┘"
  958. echo "$text" | sed 's/^/│ /'
  959. }
  960. edit_armbianEnv() {
  961. local file=$TARGET_ROOT/$1 text console_arg
  962. ed $file <<-'EOF'
  963. g/^\s*rootdev=/d
  964. g/^\s*console=/d
  965. g/^\s*bootlogo=/d
  966. wq
  967. EOF
  968. case $SERIAL_CONSOLE in
  969. 'yes') console_arg='serial' ;;
  970. *) console_arg='display' ;;
  971. esac
  972. text="rootdev=/dev/mapper/$ROOTFS_NAME
  973. console=$console_arg
  974. bootlogo=false"
  975. echo "$text" >> $file
  976. _display_file $file
  977. }
  978. edit_extlinux_conf() {
  979. local file=$TARGET_ROOT/$1 text console_arg
  980. /bin/cp $SRC_ROOT/$1 $TARGET_ROOT/$1
  981. case $SERIAL_CONSOLE in
  982. 'yes') console_pat='console=tty1' ;;
  983. *) console_pat='console=ttyS\S+' ;;
  984. esac
  985. ed --extended-regexp $file <<-EOF
  986. s/root=\S+/root=\/dev\/mapper\/$ROOTFS_NAME/
  987. s/$console_pat *//
  988. wq
  989. EOF
  990. if grep -q 'splash plymouth' $file; then
  991. ed --extended-regexp $file <<-EOF
  992. s/splash plymouth\S+/splash=verbose/
  993. wq
  994. EOF
  995. fi
  996. _display_file $file
  997. }
  998. # Add the following lines to '/etc/initramfs-tools/initramfs.conf'. If
  999. # your board’s IP address will be statically configured, substitute the
  1000. # correct static IP address after 'IP='. If it will be configured via
  1001. # DHCP, omit the IP line entirely:
  1002. edit_initramfs_conf() {
  1003. local file="$TARGET_ROOT/etc/initramfs-tools/initramfs.conf" dev=$eth_dev
  1004. [ "$USB_GADGET" ] && dev='usb0'
  1005. ed $file <<-'EOF'
  1006. g/^\s*IP=/s/^/# /
  1007. g/^\s*DEVICE=/d
  1008. wq
  1009. EOF
  1010. [ "$IP_ADDRESS" == 'dhcp' -o "$IP_ADDRESS" == 'none' ] || {
  1011. echo "IP=$IP_ADDRESS:::$NETMASK::$dev:off" >> $file
  1012. }
  1013. [ "$IP_ADDRESS" == 'none' ] || echo "DEVICE=$dev" >> $file
  1014. _display_file $file
  1015. }
  1016. edit_initramfs_modules() {
  1017. local modlist file hdr
  1018. [ "$ADD_ALL_MODS" -o "$ADD_MODS" -o "$USB_GADGET" ] && {
  1019. if ! _kernels_match; then
  1020. warn 'Host and target kernels do not match. Not adding modules to initramfs'
  1021. elif ! _distros_match; then
  1022. warn 'Host and target distros do not match. Not adding modules to initramfs'
  1023. else
  1024. local g_mods='libcomposite u_ether usb_f_rndis g_ether usb_f_eem'
  1025. [ "$ADD_ALL_MODS" ] && modlist=$(lsmod | cut -d ' ' -f1 | tail -n+2)
  1026. [ "$ADD_MODS" ] && modlist+=$(echo; for m in ${ADD_MODS//,/ }; do echo $m; done)
  1027. [ "$USB_GADGET" ] && modlist+=$(echo; for m in $g_mods; do echo $m; done)
  1028. fi
  1029. }
  1030. file="$TARGET_ROOT/etc/initramfs-tools/modules"
  1031. hdr="# List of modules that you want to include in your initramfs.
  1032. # They will be loaded at boot time in the order below.
  1033. #
  1034. # Syntax: module_name [args ...]
  1035. #
  1036. # You must run update-initramfs(8) to effect this change.
  1037. #
  1038. "
  1039. echo "$hdr$modlist" > $file
  1040. _display_file $file
  1041. }
  1042. copy_authorized_keys() {
  1043. local dest="$TARGET_ROOT$dropbear_dir"
  1044. mkdir -p $dest
  1045. /bin/cat $authorized_keys_dir/* > "$dest/authorized_keys"
  1046. chmod 644 "$dest/authorized_keys"
  1047. _display_file "$dest/authorized_keys"
  1048. }
  1049. create_fstab() {
  1050. local boot_uuid file text
  1051. boot_uuid="$(lsblk --noheadings --list --output=UUID /dev/$BOOT_DEVNAME)"
  1052. file="$TARGET_ROOT/etc/fstab"
  1053. text="/dev/mapper/$ROOTFS_NAME / ext4 defaults,noatime,nodiratime,commit=600,errors=remount-ro 0 1
  1054. UUID=$boot_uuid /boot ext4 defaults,noatime,nodiratime,commit=600,errors=remount-ro 0 2
  1055. tmpfs /tmp tmpfs defaults,nosuid 0 0"
  1056. echo "$text" > $file
  1057. _display_file $file
  1058. }
  1059. edit_dropbear_cfg() {
  1060. local dest file text
  1061. dest="$TARGET_ROOT$dropbear_dir"
  1062. file="$dest/$dropbear_conf"
  1063. text='DROPBEAR_OPTIONS="-p 2222"
  1064. DROPBEAR=y'
  1065. if [ "$IP_ADDRESS" == 'none' ]; then
  1066. [ -e $file ] && rm -v $file
  1067. true
  1068. else
  1069. mkdir -p $dest
  1070. [ -e $file ] && grep -q '^DROPBEAR_OPTIONS="-p 2222"' $file || echo "$text" >> $file
  1071. _display_file $file
  1072. fi
  1073. }
  1074. netman_manage_usb0() {
  1075. local file bu_file text
  1076. file="$TARGET_ROOT/etc/NetworkManager/NetworkManager.conf"
  1077. bu_file="$file.rootenc.orig"
  1078. text='
  1079. [device]
  1080. match-device=interface-name:$eth_dev
  1081. managed=0
  1082. match-device=interface-name:usb0
  1083. managed=1'
  1084. if [ -e $file ]; then
  1085. if [ "$USB_GADGET" ]; then
  1086. grep -q '^match-device=interface-name:usb0' $file || {
  1087. /bin/cp $file $bu_file
  1088. echo "$text" >> $file
  1089. }
  1090. else
  1091. [ -e $bu_file ] && /bin/mv $bu_file $file
  1092. fi
  1093. _display_file $file
  1094. elif [ "$USB_GADGET" ]; then
  1095. warn "$file does not exist, not enabling managed usb0"
  1096. fi
  1097. }
  1098. ifupdown_config_usb0() {
  1099. local file bu_file text
  1100. file="$TARGET_ROOT/etc/network/interfaces"
  1101. bu_file="$file.rootenc.orig"
  1102. text="\nauto usb0\niface usb0 inet static\n\taddress $IP_ADDRESS\n\tnetmask $NETMASK"
  1103. if [ -e $file ]; then
  1104. if [ "$USB_GADGET" -a "$IP_ADDRESS" != 'dhcp' ]; then
  1105. grep -q '^auto usb0' $file || {
  1106. /bin/cp $file $bu_file
  1107. echo -e "$text" >> $file
  1108. }
  1109. systemctl mask network-manager
  1110. else
  1111. [ -e $bu_file ] && /bin/mv $bu_file $file
  1112. systemctl unmask network-manager
  1113. fi
  1114. _display_file $file
  1115. elif [ "$USB_GADGET" ]; then
  1116. warn "$file does not exist, not configuring static usb0"
  1117. fi
  1118. }
  1119. create_cryptroot_unlock_sh() {
  1120. local dest file text
  1121. dest="$TARGET_ROOT/etc/initramfs-tools/hooks"
  1122. file="$dest/cryptroot-unlock.sh"
  1123. text='#!/bin/sh
  1124. if [ "$1" = "prereqs" ]; then echo "dropbear-initramfs"; exit 0; fi
  1125. . /usr/share/initramfs-tools/hook-functions
  1126. source="/tmp/cryptroot-unlock-profile"
  1127. root_home=$(echo $DESTDIR/root-*)
  1128. root_home=${root_home#$DESTDIR}
  1129. echo "if [ \"\$SSH_CLIENT\" ]; then /usr/bin/cryptroot-unlock; fi" > $source
  1130. copy_file ssh_login_profile $source $root_home/.profile
  1131. exit 0'
  1132. mkdir -p $dest
  1133. echo "$text" > $file
  1134. chmod 755 $file
  1135. _display_file $file
  1136. }
  1137. # begin chroot functions:
  1138. _pkg_remove() {
  1139. dpkg -s $1 2>/dev/null | grep ^Status | grep -q installed && apt-get --yes purge $1 || :
  1140. }
  1141. apt_remove_target_pkgs() {
  1142. [ "$IP_ADDRESS" == 'none' ] && _pkg_remove 'dropbear-initramfs'
  1143. _pkg_remove 'bash-completion'
  1144. _pkg_remove 'command-not-found'
  1145. true
  1146. }
  1147. apt_install_target_pkgs() {
  1148. local pkgs=$(_print_pkgs_to_install 'target')
  1149. [ "$pkgs" ] && {
  1150. echo "target packages to install: $pkgs"
  1151. local ls1 ls2
  1152. _show_output
  1153. ls1=$(ls -l /boot/initrd.img-*)
  1154. # DEBUG:
  1155. # dpkg-reconfigure $pkgs # doesn't work in chroot
  1156. # apt --yes purge $pkgs
  1157. # apt-get --yes --purge autoremove
  1158. dpkg --configure --pending --force-confdef
  1159. _apt_update
  1160. echo 'force-confdef' > /root/.dpkg.cfg
  1161. apt --yes install $pkgs
  1162. rm /root/.dpkg.cfg
  1163. apt --yes autoremove
  1164. ls2=$(ls -l /boot/initrd.img-*)
  1165. [ "$ls1" != "$ls2" ] && initramfs_updated='y'
  1166. _hide_output
  1167. }
  1168. true
  1169. }
  1170. update_initramfs() {
  1171. [ "$ROOTENC_TESTING" ] && return 0
  1172. _show_output
  1173. local ver=$(echo /boot/vmlinu?-* | sed 's/.boot.vmlinu.-//')
  1174. update-initramfs -k $ver -c
  1175. _hide_output
  1176. }
  1177. gen_target_ssh_host_keys() {
  1178. ssh-keygen -A
  1179. }
  1180. check_initramfs() {
  1181. local text chk count
  1182. text="$(lsinitramfs /boot/initrd.img-*)"
  1183. set +e
  1184. chk=$(echo "$text" | grep 'cryptsetup')
  1185. count=$(echo "$chk" | wc -l)
  1186. _display_file "lsinitramfs /boot/initrd.img* | grep 'cryptsetup'" "$chk"
  1187. [ "$count" -gt 5 ] || { echo "$text"; die 'Cryptsetup scripts missing in initramfs image'; }
  1188. [ "$IP_ADDRESS" == 'none' ] || {
  1189. chk=$(echo "$text" | grep 'dropbear')
  1190. count=$(echo "$chk" | wc -l)
  1191. _display_file "lsinitramfs /boot/initrd.img* | grep 'dropbear'" "$chk"
  1192. [ "$count" -gt 5 ] || { echo "$text"; die 'Dropbear scripts missing in initramfs image'; }
  1193. chk=$(echo "$text" | grep 'authorized_keys')
  1194. count=$(echo "$chk" | wc -l)
  1195. _display_file "lsinitramfs /boot/initrd.img* | grep 'authorized_keys'" "$chk"
  1196. [ "$count" -eq 1 ] || { echo "$text"; die 'authorized_keys missing in initramfs image'; }
  1197. }
  1198. set -e
  1199. }
  1200. configure_target() {
  1201. [ "$PARTITION_ONLY" ] && return
  1202. mount_target
  1203. _set_target_vars
  1204. copy_etc_files
  1205. copy_etc_files_distro_specific
  1206. edit_initramfs_conf
  1207. edit_initramfs_modules
  1208. [ "$IP_ADDRESS" == 'none' ] || copy_authorized_keys
  1209. create_etc_crypttab
  1210. create_fstab
  1211. edit_dropbear_cfg
  1212. netman_manage_usb0
  1213. ifupdown_config_usb0
  1214. [ "$IP_ADDRESS" == 'none' ] || create_cryptroot_unlock_sh
  1215. armbian_env="boot/armbianEnv.txt"
  1216. extlinux_conf="boot/extlinux/extlinux.conf"
  1217. if [ -e $SRC_ROOT/$armbian_env ]; then
  1218. edit_armbianEnv $armbian_env
  1219. elif [ -e $SRC_ROOT/$extlinux_conf ]; then
  1220. edit_extlinux_conf $extlinux_conf
  1221. else
  1222. die "could not find $SRC_ROOT/$armbian_env or $SRC_ROOT/$extlinux_conf"
  1223. fi
  1224. _debug_pause
  1225. _show_output # this must be done before entering chroot
  1226. /bin/cp $0 $TARGET_ROOT
  1227. export 'ROOTFS_NAME' 'IP_ADDRESS' 'target_distro' 'ROOTENC_TESTING' 'ROOTENC_PAUSE' 'ROOTENC_IGNORE_APT_ERRORS' 'APT_UPGRADE'
  1228. chroot $TARGET_ROOT "./$PROGNAME" $ORIG_OPTS 'in_target'
  1229. /bin/cp -a '/etc/resolv.conf' "$TARGET_ROOT/etc" # this could be a symlink
  1230. /bin/rm "$TARGET_ROOT/$PROGNAME"
  1231. _add_state_file 'target_configured' 'target'
  1232. }
  1233. _set_env_vars() {
  1234. shopt -s extglob
  1235. local name val
  1236. while [ $# -gt 0 ]; do
  1237. name=${1%=?*} val=${1#+([A-Z_])=}
  1238. [ "$name" == "$1" -o "$val" == "$1" ] && die "$1: illegal argument (must be in format 'NAME=value')"
  1239. eval "$name=$val"
  1240. shift
  1241. done
  1242. shopt -u extglob
  1243. }
  1244. _mount_target_and_exit() {
  1245. setup_loopmount
  1246. mount_target
  1247. _set_target_vars
  1248. rmdir $BOOT_ROOT
  1249. exit
  1250. }
  1251. # begin execution
  1252. set -e
  1253. while getopts hCfFmo:MUpRsudvz OPT
  1254. do
  1255. case "$OPT" in
  1256. h) print_help; exit ;;
  1257. C) NO_CLEANUP='y' ;;
  1258. f) FORCE_RECONFIGURE='y' ;;
  1259. F) FORCE_REBUILD='y' ;;
  1260. m) ADD_ALL_MODS='y' ;;
  1261. o) ADD_MODS=$OPTARG ;;
  1262. M) MOUNT_TARGET_ONLY='y' ;;
  1263. U) UMOUNT_TARGET_ONLY='y' ;;
  1264. p) PARTITION_ONLY='y' ;;
  1265. R) FORCE_REFORMAT_ROOT='y' ;;
  1266. s) USE_LOCAL_AUTHORIZED_KEYS='y' ;;
  1267. u) APT_UPGRADE='y' ;;
  1268. d) DEBUG='y' ;&
  1269. v) VERBOSE='y' RSYNC_VERBOSITY='--verbose' ;;
  1270. z) ERASE='y' ;;
  1271. *) exit ;;
  1272. esac
  1273. ORIG_OPTS+="-$OPT $OPTARG "
  1274. done
  1275. shift $((OPTIND-1))
  1276. trap '_return_handler' RETURN
  1277. trap '_sigint_handler' INT
  1278. trap '_error_handler' ERR
  1279. set -o functrace
  1280. set -o errtrace
  1281. exec {stdout_dup}>&1
  1282. exec {stderr_dup}>&2
  1283. [ $UID == 0 -o $EUID == 0 ] || die 'This program must be run as root!'
  1284. export HOME='/root'
  1285. [ "$DEBUG" ] && set -x
  1286. ARG1=$1
  1287. [ "$ARG1" ] || die 'You must supply a target device name'
  1288. shift
  1289. _set_env_vars $@
  1290. if [ "$ARG1" == 'in_target' ]; then
  1291. SCRIPT_DESC='Target script'
  1292. _hide_output
  1293. [ "$target_distro" == 'bionic' ] && {
  1294. echo 'export CRYPTSETUP=y' > '/etc/initramfs-tools/conf.d/cryptsetup'
  1295. }
  1296. apt_remove_target_pkgs
  1297. apt_install_target_pkgs
  1298. [ "$initramfs_updated" ] || update_initramfs
  1299. gen_target_ssh_host_keys
  1300. check_initramfs
  1301. else
  1302. SCRIPT_DESC='Host script'
  1303. _check_eth_dev
  1304. _do_header
  1305. _set_host_vars
  1306. [ "$ARMBIAN_IMAGE" ] || get_armbian_image
  1307. get_partition_info
  1308. apt_install_host_pkgs # _preclean requires cryptsetup
  1309. _preclean
  1310. [ "$UMOUNT_TARGET_ONLY" ] && exit
  1311. check_sdcard_name_and_params $ARG1
  1312. create_build_dir
  1313. [ "$MOUNT_TARGET_ONLY" ] && warn 'Mounting source and target and exiting at user request'
  1314. _get_user_vars
  1315. _test_sdcard_mounted
  1316. [ "$MOUNT_TARGET_ONLY" ] && _mount_target_and_exit
  1317. _warn_user_opts
  1318. _confirm_user_vars
  1319. [ "$IP_ADDRESS" == 'none' ] || get_authorized_keys
  1320. [ "$NO_CLEANUP" ] || trap '_clean' EXIT
  1321. setup_loopmount
  1322. _debug_pause
  1323. check_install_state
  1324. _hide_output
  1325. [ "$card_partitioned" == 'n' ] && _do_partition
  1326. _debug_pause
  1327. [ "$bootpart_copied" == 'n' ] && copy_system_boot
  1328. [ "$bootpart_label_created" == 'n' ] && create_bootpart_label
  1329. [ "$rootpart_copied" == 'n' ] && copy_system_root
  1330. [ "$target_configured" == 'n' ] && configure_target
  1331. sync
  1332. build_success=1
  1333. fi