armbian_rootenc_setup.sh 38 KB

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