armbian_rootenc_setup.sh 38 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337
  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 listed
  83. in the file 'authorized_keys' in the current directory. This file has the
  84. 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' ] && {
  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. while true; do
  236. if [ -z "${!var}" -o "$seen_prompt" -o "$redo" ]; then
  237. if [ "$seen_prompt" ]; then
  238. echo -n " Enter $desc: "
  239. else
  240. cprompt=
  241. while read reply; do
  242. cprompt+=" ${reply## }\n"
  243. done <<-EOF
  244. $prompt
  245. EOF
  246. echo
  247. if [ "$dfl" ]; then
  248. printf "${cprompt:0:-2} " "$dfl"
  249. else
  250. echo -ne "${cprompt:0:-2} "
  251. fi
  252. seen_prompt=1
  253. fi
  254. eval "read $var"
  255. fi
  256. redo=1
  257. if [ -z "${!var}" -a "$dfl" ]; then eval "$var=$dfl"; fi
  258. [ "${!var}" ] || {
  259. rmsg " $desc must not be empty"
  260. continue
  261. }
  262. if [ "$pat" ]; then
  263. local rpat=$pat
  264. if [ "$pat" == 'bool' ]; then
  265. rpat='^[ynYN]*$'
  266. pat_errmsg="You must type 'y' or 'n'"
  267. fi
  268. echo "${!var}" | egrep -qi "$rpat" || {
  269. rmsg " ${!var}: $pat_errmsg"
  270. continue
  271. }
  272. if [ "$pat" == 'bool' ]; then
  273. if [[ ${!var} =~ ^[Yy]$ ]]; then eval "$var=yes"; else eval "$var="; fi
  274. fi
  275. fi
  276. if [ "$vtest" ]; then
  277. $vtest || continue
  278. fi
  279. break
  280. done
  281. }
  282. _get_user_vars() {
  283. local dq='[0-9]{1,3}'
  284. _get_user_var 'IP_ADDRESS' 'IP address' '' \
  285. "Enter the IP address of the target machine.
  286. Enter 'dhcp' for a dynamic IP or 'none' for no remote SSH unlocking support
  287. IP address:" \
  288. "^(dhcp|none|$dq\.$dq\.$dq\.$dq)$" \
  289. 'malformed IP address'
  290. IP_ADDRESS=${IP_ADDRESS,,}
  291. [[ $IP_ADDRESS =~ ^(dhcp|none)$ ]] || {
  292. _get_user_var 'NETMASK' 'netmask' '255.255.255.0' \
  293. "Enter the netmask of the target machine,
  294. or hit ENTER for the default (%s): " \
  295. "^($dq\.$dq\.$dq\.$dq)$" \
  296. 'malformed netmask'
  297. }
  298. _get_user_var 'BOOTPART_LABEL' 'boot partition label' 'ARMBIAN_BOOT' \
  299. "Enter a boot partition label for the target machine,
  300. or hit ENTER for the default (%s): " \
  301. '^[A-Za-z0-9_]{1,16}$' \
  302. "Label must contain no more than 16 characters in the set 'A-Za-z0-9_'"
  303. _get_user_var 'ROOTFS_NAME' 'root filesystem device name' 'rootfs' \
  304. "Enter a device name for the encrypted root filesystem,
  305. or hit ENTER for the default (%s):" \
  306. '^[a-z0-9_]{1,48}$' \
  307. "Name must contain no more than 48 characters in the set 'a-z0-9_'" \
  308. '_test_rootfs_mounted'
  309. if [ "$MOUNT_TARGET_ONLY" ]; then
  310. local pw_prompt="Enter disk password:"
  311. else
  312. local pw_prompt="Choose a simple disk password for the installation process.
  313. Once your encrypted system is up and running, you can change
  314. the password using the 'cryptsetup' command.
  315. Enter password:"
  316. fi
  317. _get_user_var 'DISK_PASSWD' 'disk password' '' "$pw_prompt" \
  318. '^[A-Za-z0-9_ ]{1,10}$' \
  319. "Temporary disk password must contain no more than 10 characters in the set 'A-Za-z0-9_ '"
  320. if [ "$IP_ADDRESS" == 'none' ]; then
  321. UNLOCKING_USERHOST=
  322. elif [ -e 'authorized_keys' -a "$USE_LOCAL_AUTHORIZED_KEYS" ]; then
  323. UNLOCKING_USERHOST=
  324. else
  325. _get_user_var 'UNLOCKING_USERHOST' 'USER@HOST' '' \
  326. "Enter the user@host of the machine you'll be unlocking from:" \
  327. '\S+@\S+' \
  328. 'malformed USER@HOST' \
  329. '_test_unlocking_host_available'
  330. fi
  331. _get_user_var 'SERIAL_CONSOLE' 'serial console unlocking' '' \
  332. "Unlock the disk from the serial console. WARNING: enabling this will
  333. make it impossible to unlock the disk using the keyboard and monitor,
  334. though unlocking via SSH will still work.
  335. Enable unlocking via serial console? (y/n):" \
  336. 'bool'
  337. _get_user_var 'USB_GADGET' 'disk unlocking via SSH over USB (g_ether)' '' \
  338. "Unlock the disk via SSH over USB (g_ether). Enable this only if your board
  339. supports USB gadget mode, i.e. if it has a USB OTG port. WARNING: enabling this
  340. will make it impossible to unlock the disk over the Ethernet interface (eth0).
  341. Enable unlocking via SSH over USB? (y/n):" \
  342. 'bool'
  343. true
  344. }
  345. _test_rootfs_mounted() {
  346. [ -e "/dev/mapper/$ROOTFS_NAME" ] && {
  347. local mnt=$(lsblk --list --noheadings --output=MOUNTPOINT /dev/mapper/$ROOTFS_NAME)
  348. [ "$mnt" ] && {
  349. rmsg " Device '$ROOTFS_NAME' is in use and mounted on $mnt"
  350. return 1
  351. }
  352. }
  353. return 0
  354. }
  355. _test_unlocking_host_available() {
  356. local ul_host=${UNLOCKING_USERHOST#*@}
  357. ping -c1 $ul_host &>/dev/null || {
  358. rmsg " Unable to ping host '$ul_host'"
  359. return 1
  360. }
  361. }
  362. _test_sdcard_mounted() {
  363. local chk="$(lsblk --noheadings --list --output=MOUNTPOINT /dev/$SDCARD_DEVNAME)"
  364. [ -z "$chk" ] || {
  365. lsblk --output=NAME,SIZE,TYPE,FSTYPE,MOUNTPOINT /dev/$SDCARD_DEVNAME
  366. die "Device /dev/$SDCARD_DEVNAME has mounted partitions!"
  367. }
  368. }
  369. get_authorized_keys() {
  370. [ -e 'authorized_keys' -a "$USE_LOCAL_AUTHORIZED_KEYS" ] || {
  371. rsync "$UNLOCKING_USERHOST:.ssh/id_*.pub" 'authorized_keys'
  372. }
  373. }
  374. _apt_update() {
  375. [ "$ROOTENC_IGNORE_APT_ERRORS" ] && set +e
  376. apt --yes update
  377. [ "$APT_UPGRADE" ] && apt --yes upgrade
  378. [ "$ROOTENC_IGNORE_APT_ERRORS" ] && set -e
  379. true
  380. }
  381. _print_pkgs_to_install() {
  382. local pkgs pkgs_ssh
  383. case $1 in
  384. 'host')
  385. case "$host_distro" in
  386. focal|bionic|buster) pkgs='cryptsetup-bin ed' ;;
  387. *) pkgs='cryptsetup ed'
  388. warn "Warning: unrecognized host distribution '$host_distro'" ;;
  389. esac ;;
  390. 'target')
  391. case "$target_distro" in
  392. focal|buster) pkgs='cryptsetup-initramfs' pkgs_ssh='dropbear-initramfs' ;;
  393. bionic) pkgs='cryptsetup' pkgs_ssh='dropbear-initramfs' ;;
  394. *) pkgs='cryptsetup' pkgs_ssh='dropbear'
  395. warn "Warning: unrecognized target distribution '$target_distro'" ;;
  396. esac
  397. [ "$IP_ADDRESS" != 'none' ] && pkgs+=" $pkgs_ssh" ;;
  398. esac
  399. for i in $pkgs; do
  400. dpkg -l $i 2>/dev/null | grep -q ^ii || echo $i
  401. done
  402. }
  403. apt_install_host() {
  404. local pkgs=$(_print_pkgs_to_install 'host')
  405. [ "$pkgs" ] && {
  406. _apt_update
  407. apt --yes install $pkgs
  408. }
  409. true
  410. }
  411. create_build_dir() {
  412. mkdir -p $BUILD_DIR
  413. mkdir -p $SRC_ROOT
  414. mkdir -p $BOOT_ROOT
  415. mkdir -p $TARGET_ROOT
  416. }
  417. umount_target() {
  418. for i in $BOOT_ROOT $TARGET_ROOT; do
  419. while mountpoint -q $i; do
  420. umount -Rl $i
  421. done
  422. done
  423. }
  424. remove_build_dir() {
  425. [ -d $TARGET_ROOT ] && rmdir $TARGET_ROOT
  426. [ -d $BOOT_ROOT ] && rmdir $BOOT_ROOT
  427. [ -d $SRC_ROOT ] && rmdir $SRC_ROOT
  428. [ -d $BUILD_DIR ] && rmdir $BUILD_DIR
  429. true
  430. }
  431. _get_device_maps() {
  432. local dm_type=$1 varname="device_maps_$1" dm_name ls mp
  433. eval "$varname="
  434. for dm_name in $(dmsetup ls | awk '{print $1}'); do
  435. [ "$(lsblk --noheadings --nodeps -o fstype /dev/mapper/$dm_name)" == 'ext4' ] || continue
  436. ls=$(findmnt -n --source /dev/mapper/$dm_name | awk '{print $1}')
  437. if [ "$ls" -a "$dm_type" == 'mounted_on_target' ]; then
  438. for mp in $ls; do
  439. if [ "${mp: -${#TARGET_ROOT}}" == "$TARGET_ROOT" ]; then
  440. eval "$varname+=$dm_name "
  441. fi
  442. done
  443. elif [ -z "$ls" -a "$dm_type" == 'unmounted' ]; then
  444. eval "$varname+=$dm_name "
  445. fi
  446. done
  447. tmsg "$varname=[${!varname}]"
  448. }
  449. _close_device_maps() {
  450. local dm_type=$1
  451. local varname="device_maps_${dm_type}"
  452. for i in ${!varname}; do
  453. tmsg "closing $i"
  454. cryptsetup status $i > '/dev/null' && cryptsetup luksClose $i
  455. done
  456. true
  457. }
  458. _preclean() {
  459. close_loopmount
  460. _get_device_maps 'unmounted'
  461. _close_device_maps 'unmounted'
  462. _get_device_maps 'mounted_on_target'
  463. umount_target
  464. _close_device_maps 'mounted_on_target'
  465. remove_build_dir
  466. }
  467. _clean() {
  468. pu_msg "Cleaning up, please wait..."
  469. _show_output
  470. close_loopmount
  471. _get_device_maps 'mounted_on_target'
  472. umount_target
  473. update_config_vars_file
  474. _close_device_maps 'mounted_on_target'
  475. [ -e 'authorized_keys' -a -z "$USE_LOCAL_AUTHORIZED_KEYS" ] && shred -u 'authorized_keys'
  476. remove_build_dir
  477. }
  478. get_armbian_image() {
  479. ARMBIAN_IMAGE="$(ls *.img)"
  480. [ "$ARMBIAN_IMAGE" ] || die 'You must place an Armbian image in the current directory!'
  481. local count=$(echo "$ARMBIAN_IMAGE" | wc -l)
  482. [ "$count" == 1 ] || die "More than one image file present!:\n$ARMBIAN_IMAGE"
  483. }
  484. _confirm_user_vars() {
  485. echo
  486. echo " Armbian image: $ARMBIAN_IMAGE"
  487. echo " Target device: /dev/$SDCARD_DEVNAME ($SD_INFO)"
  488. echo " Root filesystem device name: /dev/mapper/$ROOTFS_NAME"
  489. echo " Target IP address: $IP_ADDRESS"
  490. [ "$NETMASK" ] && echo " Target netmask: $NETMASK"
  491. echo " Boot partition label: $BOOTPART_LABEL"
  492. echo " Disk password: $DISK_PASSWD"
  493. [ "$UNLOCKING_USERHOST" ] && echo " user@host of unlocking host: $UNLOCKING_USERHOST"
  494. echo " Serial console unlocking: ${SERIAL_CONSOLE:-no}"
  495. echo " SSH over USB unlocking: ${USB_GADGET:-no}"
  496. echo
  497. _user_confirm ' Are these settings correct?' 'yes'
  498. }
  499. setup_loopmount() {
  500. LOOP_DEV=$(losetup -f)
  501. losetup -P $LOOP_DEV $ARMBIAN_IMAGE
  502. mount ${LOOP_DEV}p1 $SRC_ROOT
  503. START_SECTOR=$(fdisk -l $LOOP_DEV -o Start | tail -n1 | tr -d ' ') # usually 32768
  504. BOOT_SECTORS=409600 # 200MB
  505. }
  506. _umount_with_check() {
  507. mountpoint -q $1 && umount $1
  508. }
  509. update_config_vars_file() {
  510. mount "/dev/$BOOT_DEVNAME" $BOOT_ROOT
  511. _print_config_vars $CONFIG_VARS_FILE
  512. umount $BOOT_ROOT
  513. }
  514. _print_states() {
  515. for i in $STATES; do
  516. echo $i: ${!i}
  517. done
  518. }
  519. _update_state_from_config_vars() {
  520. [ -e $CONFIG_VARS_FILE ] || return 0
  521. local reply
  522. while read reply; do
  523. eval "c$reply"
  524. done <<<$(cat $CONFIG_VARS_FILE)
  525. local saved_states cfgvar_changed
  526. saved_states="$(_print_states)"
  527. cfgvar_changed=
  528. [ $cARMBIAN_IMAGE != $ARMBIAN_IMAGE ] && cfgvar_changed+=' ARMBIAN_IMAGE' card_partitioned='n'
  529. [ $cBOOTPART_LABEL != $BOOTPART_LABEL ] && cfgvar_changed+=' BOOTPART_LABEL' bootpart_label_created='n'
  530. [ $cROOTFS_NAME != $ROOTFS_NAME ] && cfgvar_changed+=' ROOTFS_NAME' target_configured='n'
  531. [ $cDISK_PASSWD != $DISK_PASSWD ] && cfgvar_changed+=' DISK_PASSWD' rootpart_copied='n'
  532. [ "$UNLOCKING_USERHOST" -a "$cUNLOCKING_USERHOST" != "$UNLOCKING_USERHOST" ] && {
  533. cfgvar_changed+=' UNLOCKING_USERHOST' target_configured='n'
  534. }
  535. [ "$cSERIAL_CONSOLE" != "$SERIAL_CONSOLE" ] && {
  536. cfgvar_changed+=' SERIAL_CONSOLE' target_configured='n'
  537. }
  538. [ $cIP_ADDRESS != $IP_ADDRESS ] && cfgvar_changed+=' IP_ADDRESS' target_configured='n'
  539. [ "$cNETMASK" != "$NETMASK" ] && cfgvar_changed+=' NETMASK' target_configured='n'
  540. [ "$cADD_ALL_MODS" != "$ADD_ALL_MODS" ] && cfgvar_changed+=' ADD_ALL_MODS' target_configured='n'
  541. [ "$cADD_MODS" != "$ADD_MODS" ] && cfgvar_changed+=' ADD_MODS' target_configured='n'
  542. [ "$cUSB_GADGET" != "$USB_GADGET" ] && cfgvar_changed+=' USB_GADGET' target_configured='n'
  543. [ "$IP_ADDRESS" -a "$cUSE_LOCAL_AUTHORIZED_KEYS" != "$USE_LOCAL_AUTHORIZED_KEYS" ] && {
  544. cfgvar_changed+=' USE_LOCAL_AUTHORIZED_KEYS' target_configured='n'
  545. }
  546. [ $card_partitioned == 'n' ] && {
  547. bootpart_copied='n'
  548. bootpart_label_created='n'
  549. rootpart_copied='n'
  550. target_configured='n'
  551. }
  552. [ $bootpart_copied == 'n' ] && bootpart_label_created='n'
  553. [ $rootpart_copied == 'n' ] && target_configured='n'
  554. [ "$saved_states" != "$(_print_states)" ] && {
  555. warn "Install state altered due to changed config vars:$cfgvar_changed"
  556. for i in $STATES; do
  557. if [ "${!i}" == 'n' ]; then
  558. imsg " $i: ${RED}no$RESET"
  559. else
  560. imsg " $i: ${GREEN}yes$RESET"
  561. fi
  562. done
  563. _delete_state_files
  564. }
  565. true
  566. }
  567. _add_state_file() {
  568. local state=$1 cmd=$2
  569. if [ "$cmd" == 'target' ]; then
  570. touch "$TARGET_ROOT/boot/.rootenc_install_state/$state"
  571. else
  572. [ "$cmd" == 'mount' ] && mount "/dev/$BOOT_DEVNAME" $BOOT_ROOT
  573. mkdir -p "$BOOT_ROOT/.rootenc_install_state"
  574. touch "$BOOT_ROOT/.rootenc_install_state/$state"
  575. [ "$cmd" == 'mount' ] && umount $BOOT_ROOT
  576. fi
  577. eval "$state='y'"
  578. tmsg "added state file '$state'"
  579. }
  580. _delete_state_files() {
  581. for i in $STATES; do
  582. local fn="$BOOT_ROOT/.rootenc_install_state/$i"
  583. [ ${!i} == 'n' -a -e $fn ] && /bin/rm $fn
  584. done
  585. true
  586. }
  587. _get_state_from_state_files() {
  588. for i in $STATES; do
  589. if [ -e "$BOOT_ROOT/.rootenc_install_state/$i" ]; then
  590. eval "$i=y"
  591. else
  592. eval "$i=n"
  593. fi
  594. done
  595. }
  596. _print_state_from_state_files() {
  597. imsg 'Install state:'
  598. for i in $STATES; do
  599. if [ -e "$BOOT_ROOT/.rootenc_install_state/$i" ]; then
  600. imsg " $i: ${GREEN}yes$RESET"
  601. else
  602. imsg " $i: ${RED}no$RESET"
  603. fi
  604. done
  605. }
  606. check_install_state() {
  607. for i in $STATES; do eval "$i=n"; done
  608. if [ "$FORCE_REBUILD" ]; then
  609. return
  610. else
  611. do_partprobe
  612. lsblk --noheadings --list /dev/$SDCARD_DEVNAME -o 'NAME' | grep -q $BOOT_DEVNAME || return 0
  613. lsblk --noheadings --list /dev/$BOOT_DEVNAME -o 'FSTYPE' | grep -q 'ext4' || return 0
  614. mount "/dev/$BOOT_DEVNAME" $BOOT_ROOT
  615. _get_state_from_state_files
  616. if [ "$target_configured" == 'y' -a "$FORCE_RECONFIGURE" ]; then
  617. target_configured='n'
  618. _delete_state_files
  619. fi
  620. _print_state_from_state_files
  621. _update_state_from_config_vars
  622. _umount_with_check $BOOT_ROOT
  623. fi
  624. }
  625. close_loopmount() {
  626. while mountpoint -q $SRC_ROOT; do
  627. umount $SRC_ROOT
  628. done
  629. for i in $(losetup --noheadings --raw --list -j $ARMBIAN_IMAGE | awk '{print $1}'); do
  630. losetup -d $i
  631. done
  632. }
  633. _user_confirm() {
  634. local prompt1 prompt2 dfl_action reply
  635. prompt1=$1 dfl_action=$2
  636. if [ "$dfl_action" == 'yes' ]; then
  637. prompt2='(Y/n)'
  638. else
  639. prompt2='(y/N)'
  640. fi
  641. imsg_nonl "$prompt1 $prompt2 "
  642. read -n1 reply
  643. [ "$reply" ] && imsg ''
  644. [ "$dfl_action" == 'yes' -a -z "$reply" ] && return
  645. [ "$reply" == 'y' -o "$reply" == 'Y' ] && return
  646. warn "Exiting at user request"
  647. exit 1
  648. }
  649. erase_boot_sector_and_first_partition() {
  650. local sectors count
  651. sectors=$((START_SECTOR+BOOT_SECTORS+100))
  652. count=$(((sectors/8192)+1))
  653. pu_msg "Erasing up to beginning of second partition ($sectors sectors, ${count}M):"
  654. _show_output
  655. dd if=/dev/zero \
  656. of=/dev/$SDCARD_DEVNAME \
  657. status=progress \
  658. bs=$((512*8192)) \
  659. count=$count
  660. _hide_output
  661. }
  662. create_partition_label() {
  663. pu_msg "Creating new partition label on /dev/$SDCARD_DEVNAME"
  664. local fdisk_cmds="o\nw\n"
  665. set +e
  666. echo -e "$fdisk_cmds" | fdisk "/dev/$SDCARD_DEVNAME"
  667. set -e
  668. do_partprobe
  669. }
  670. copy_boot_loader() {
  671. local count
  672. count=$((START_SECTOR/2048))
  673. pu_msg "Copying boot loader ($START_SECTOR sectors, ${count}M):"
  674. _show_output
  675. dd if=$ARMBIAN_IMAGE \
  676. of=/dev/$SDCARD_DEVNAME \
  677. status=progress \
  678. bs=$((512*2048)) \
  679. count=$count
  680. _hide_output
  681. do_partprobe
  682. }
  683. _print_config_vars() {
  684. local outfile=$1
  685. local data="$(for i in $CONFIG_VARS; do echo "$i='${!i}'"; done)"
  686. if [ "$outfile" ]; then echo "$data" > $outfile; else echo "$data"; fi
  687. }
  688. partition_sd_card() {
  689. local p1_end p2_start fdisk_cmds bname rname fstype
  690. p1_end=$((START_SECTOR+BOOT_SECTORS-1))
  691. p2_start=$((p1_end+1))
  692. fdisk_cmds="o\nn\np\n1\n$START_SECTOR\n$p1_end\nn\np\n2\n$p2_start\n\nw\n"
  693. set +e
  694. echo -e "$fdisk_cmds" | fdisk "/dev/$SDCARD_DEVNAME"
  695. set -e
  696. do_partprobe
  697. bname="$(lsblk --noheadings --list --output=NAME /dev/$BOOT_DEVNAME)"
  698. [ "$bname" == $BOOT_DEVNAME ] || die 'Partitioning failed!'
  699. rname="$(lsblk --noheadings --list --output=NAME /dev/$ROOT_DEVNAME)"
  700. [ "$rname" == $ROOT_DEVNAME ] || die 'Partitioning failed!'
  701. # filesystem is required by call to _add_state_file(), so we must create it here
  702. fstype=$(lsblk --noheadings --list --output=FSTYPE "/dev/$BOOT_DEVNAME")
  703. [ "$fstype" == 'ext4' -a "$ROOTENC_REUSE_FS" ] || mkfs.ext4 -F "/dev/$BOOT_DEVNAME"
  704. do_partprobe
  705. _add_state_file 'card_partitioned' 'mount'
  706. }
  707. _do_partition() {
  708. imsg "All data on /dev/$SDCARD_DEVNAME ($SD_INFO) will be destroyed!!!"
  709. _user_confirm 'Are you sure you want to continue?' 'no'
  710. if [ "$ERASE" ]; then
  711. erase_boot_sector_and_first_partition
  712. else
  713. create_partition_label
  714. fi
  715. copy_boot_loader
  716. partition_sd_card
  717. }
  718. copy_system_boot() {
  719. [ "$PARTITION_ONLY" ] && {
  720. _add_state_file 'bootpart_copied' 'mount'
  721. return
  722. }
  723. mount "/dev/$BOOT_DEVNAME" $BOOT_ROOT
  724. pu_msg "Copying files to boot partition:"
  725. _show_output
  726. rsync $RSYNC_VERBOSITY --archive $SRC_ROOT/boot/* $BOOT_ROOT
  727. _hide_output
  728. [ -e "$BOOT_ROOT/boot" ] || (cd $BOOT_ROOT && ln -s . 'boot')
  729. _add_state_file 'bootpart_copied'
  730. umount $BOOT_ROOT
  731. }
  732. create_bootpart_label() {
  733. e2label "/dev/$BOOT_DEVNAME" "$BOOTPART_LABEL"
  734. do_partprobe
  735. _add_state_file 'bootpart_label_created' 'mount'
  736. }
  737. copy_system_root() {
  738. if [ "$FORCE_REFORMAT_ROOT" ] || ! cryptsetup isLuks "/dev/$ROOT_DEVNAME"; then
  739. pu_msg "Formatting encrypted root partition:"
  740. echo -n $DISK_PASSWD | cryptsetup luksFormat "/dev/$ROOT_DEVNAME" '-'
  741. fi
  742. echo $DISK_PASSWD | cryptsetup luksOpen "/dev/$ROOT_DEVNAME" $ROOTFS_NAME
  743. local fstype=$(lsblk --noheadings --list --output=FSTYPE "/dev/mapper/$ROOTFS_NAME")
  744. [ "$fstype" == 'ext4' -a "$ROOTENC_REUSE_FS" ] || mkfs.ext4 -F "/dev/mapper/$ROOTFS_NAME"
  745. [ "$PARTITION_ONLY" ] || {
  746. mount "/dev/mapper/$ROOTFS_NAME" $TARGET_ROOT
  747. pu_msg "Copying system to encrypted root partition:"
  748. _show_output
  749. rsync $RSYNC_VERBOSITY --archive --exclude=boot $SRC_ROOT/* $TARGET_ROOT
  750. _hide_output
  751. sync
  752. mkdir -p "$TARGET_ROOT/boot"
  753. touch "$TARGET_ROOT/root/.no_rootfs_resize"
  754. umount $TARGET_ROOT
  755. }
  756. cryptsetup luksClose $ROOTFS_NAME
  757. do_partprobe
  758. _add_state_file 'rootpart_copied' 'mount'
  759. }
  760. mount_target() {
  761. echo $DISK_PASSWD | cryptsetup luksOpen "/dev/$ROOT_DEVNAME" $ROOTFS_NAME
  762. mount "/dev/mapper/$ROOTFS_NAME" $TARGET_ROOT
  763. mount "/dev/$BOOT_DEVNAME" "$TARGET_ROOT/boot"
  764. local src dest args
  765. while read src dest args; do
  766. mount $args $src $TARGET_ROOT/$dest
  767. done <<-EOF
  768. udev dev -t devtmpfs -o rw,relatime,nosuid,mode=0755
  769. devpts dev/pts -t devpts
  770. tmpfs dev/shm -t tmpfs -o rw,nosuid,nodev,relatime
  771. proc proc -t proc
  772. sys sys -t sysfs
  773. EOF
  774. }
  775. _copy_to_target() {
  776. local fn=$1
  777. if [ -e $fn ]; then
  778. echo "Copying '$fn'"
  779. cat $fn > $TARGET_ROOT/$fn
  780. else
  781. imsg "Unable to copy '$fn' to target (file does not exist)"
  782. false
  783. fi
  784. }
  785. create_etc_crypttab() {
  786. local root_uuid="$(lsblk --noheadings --list --nodeps --output=UUID /dev/$ROOT_DEVNAME)"
  787. echo "$ROOTFS_NAME UUID=$root_uuid none initramfs,luks" > "$TARGET_ROOT/etc/crypttab"
  788. _display_file "$TARGET_ROOT/etc/crypttab"
  789. }
  790. copy_etc_files() {
  791. _copy_to_target '/etc/resolv.conf'
  792. _copy_to_target '/etc/hosts'
  793. set +e
  794. _copy_to_target /etc/apt/apt.conf.d/*proxy
  795. set -e
  796. }
  797. _set_target_vars() {
  798. target_distro=$(chroot $TARGET_ROOT 'lsb_release' '--short' '--codename')
  799. target_kernel=$(chroot $TARGET_ROOT 'ls' '/boot' | egrep '^vmlinu[xz]')
  800. imsg "$(printf '%-8s %-28s %s' '' 'Host' 'Target')"
  801. imsg "$(printf '%-8s %-28s %s' '' '----' '------')"
  802. imsg "$(printf '%-8s %-28s %s' 'distro:' $host_distro $target_distro)"
  803. imsg "$(printf '%-8s %-28s %s' 'kernel:' $host_kernel $target_kernel)"
  804. }
  805. _distros_match() {
  806. [ $host_distro == $target_distro ]
  807. }
  808. _kernels_match() {
  809. [ ${host_kernel%.*} == ${target_kernel%.*} ] || return 1
  810. [ ${host_kernel##*-} == ${target_kernel##*-} ]
  811. }
  812. copy_etc_files_distro_specific() {
  813. local files='/etc/apt/sources.list /etc/apt/sources.list.d/armbian.list'
  814. if _distros_match; then
  815. for i in $files; do _copy_to_target $i; done
  816. else
  817. warn 'Warning: host and target distros do not match:'
  818. for i in $files; do imsg " not copying $i"; done
  819. fi
  820. }
  821. _display_file() {
  822. local name text reply
  823. if [ "$2" ]; then
  824. name="$1"
  825. text="$2"
  826. else
  827. name=${1#$TARGET_ROOT}
  828. text="$(cat $1)"
  829. fi
  830. hl='────────────────────────────────────────'
  831. hl="$hl$hl$hl"
  832. hls=${hl:0:${#name}+1}
  833. echo "┌─$hls─┐"
  834. echo "│ $name: │"
  835. echo "├─$hls─┘"
  836. echo "$text" | sed 's/^/│ /'
  837. }
  838. edit_armbianEnv() {
  839. local file text console_arg
  840. file="$TARGET_ROOT/boot/armbianEnv.txt"
  841. ed $file <<-'EOF'
  842. g/^\s*rootdev=/d
  843. g/^\s*console=/d
  844. g/^\s*bootlogo=/d
  845. wq
  846. EOF
  847. case $SERIAL_CONSOLE in
  848. 'yes') console_arg='serial' ;;
  849. *) console_arg='display' ;;
  850. esac
  851. text="rootdev=/dev/mapper/$ROOTFS_NAME
  852. console=$console_arg
  853. bootlogo=false"
  854. echo "$text" >> $file
  855. _display_file $file
  856. }
  857. # Add the following lines to '/etc/initramfs-tools/initramfs.conf'. If
  858. # your board’s IP address will be statically configured, substitute the
  859. # correct static IP address after 'IP='. If it will be configured via
  860. # DHCP, omit the IP line entirely:
  861. edit_initramfs_conf() {
  862. local file="$TARGET_ROOT/etc/initramfs-tools/initramfs.conf" dev='eth0'
  863. [ "$USB_GADGET" ] && dev='usb0'
  864. ed $file <<-'EOF'
  865. g/^\s*IP=/s/^/# /
  866. g/^\s*DEVICE=/d
  867. wq
  868. EOF
  869. [ "$IP_ADDRESS" == 'dhcp' -o "$IP_ADDRESS" == 'none' ] || {
  870. echo "IP=$IP_ADDRESS:::$NETMASK::$dev:off" >> $file
  871. }
  872. [ "$IP_ADDRESS" == 'none' ] || echo "DEVICE=$dev" >> $file
  873. _display_file $file
  874. }
  875. edit_initramfs_modules() {
  876. local modlist file hdr
  877. [ "$ADD_ALL_MODS" -o "$ADD_MODS" -o "$USB_GADGET" ] && {
  878. if ! _kernels_match; then
  879. warn 'Host and target kernels do not match. Not adding modules to initramfs'
  880. elif ! _distros_match; then
  881. warn 'Host and target distros do not match. Not adding modules to initramfs'
  882. else
  883. local g_mods='libcomposite u_ether usb_f_rndis g_ether usb_f_eem'
  884. [ "$ADD_ALL_MODS" ] && modlist=$(lsmod | cut -d ' ' -f1 | tail -n+2)
  885. [ "$ADD_MODS" ] && modlist+=$(echo; for m in ${ADD_MODS//,/ }; do echo $m; done)
  886. [ "$USB_GADGET" ] && modlist+=$(echo; for m in $g_mods; do echo $m; done)
  887. fi
  888. }
  889. file="$TARGET_ROOT/etc/initramfs-tools/modules"
  890. hdr="# List of modules that you want to include in your initramfs.
  891. # They will be loaded at boot time in the order below.
  892. #
  893. # Syntax: module_name [args ...]
  894. #
  895. # You must run update-initramfs(8) to effect this change.
  896. #
  897. "
  898. echo "$hdr$modlist" > $file
  899. _display_file $file
  900. }
  901. copy_authorized_keys() {
  902. local dest="$TARGET_ROOT/etc/dropbear-initramfs"
  903. mkdir -p $dest
  904. /bin/cp 'authorized_keys' $dest
  905. _display_file "$dest/authorized_keys"
  906. }
  907. create_fstab() {
  908. local boot_uuid file text
  909. boot_uuid="$(lsblk --noheadings --list --output=UUID /dev/$BOOT_DEVNAME)"
  910. file="$TARGET_ROOT/etc/fstab"
  911. text="/dev/mapper/$ROOTFS_NAME / ext4 defaults,noatime,nodiratime,commit=600,errors=remount-ro 0 1
  912. UUID=$boot_uuid /boot ext4 defaults,noatime,nodiratime,commit=600,errors=remount-ro 0 2
  913. tmpfs /tmp tmpfs defaults,nosuid 0 0"
  914. echo "$text" > $file
  915. _display_file $file
  916. }
  917. edit_dropbear_cfg() {
  918. local dest file text
  919. dest="$TARGET_ROOT/etc/dropbear-initramfs"
  920. file="$dest/config"
  921. text='DROPBEAR_OPTIONS="-p 2222"
  922. DROPBEAR=y'
  923. if [ "$IP_ADDRESS" == 'none' ]; then
  924. [ -e $file ] && rm -v $file
  925. true
  926. else
  927. mkdir -p $dest
  928. [ -e $file ] && grep -q '^DROPBEAR_OPTIONS="-p 2222"' $file || echo "$text" >> $file
  929. _display_file $file
  930. fi
  931. }
  932. netman_manage_usb0() {
  933. local file bu_file text
  934. file="$TARGET_ROOT/etc/NetworkManager/NetworkManager.conf"
  935. bu_file="$file.rootenc.orig"
  936. text='
  937. [device]
  938. match-device=interface-name:eth0
  939. managed=0
  940. match-device=interface-name:usb0
  941. managed=1'
  942. if [ -e $file ]; then
  943. if [ "$USB_GADGET" ]; then
  944. grep -q '^match-device=interface-name:usb0' $file || {
  945. /bin/cp $file $bu_file
  946. echo "$text" >> $file
  947. }
  948. else
  949. [ -e $bu_file ] && /bin/mv $bu_file $file
  950. fi
  951. _display_file $file
  952. else
  953. warn "$file does not exist, not enabling managed usb0"
  954. fi
  955. }
  956. ifupdown_config_usb0() {
  957. local file bu_file text
  958. file="$TARGET_ROOT/etc/network/interfaces"
  959. bu_file="$file.rootenc.orig"
  960. text="
  961. auto usb0
  962. iface usb0 inet static
  963. address $IP_ADDRESS
  964. netmask $NETMASK
  965. "
  966. if [ -e $file ]; then
  967. if [ "$USB_GADGET" -a "$IP_ADDRESS" != 'dhcp' ]; then
  968. grep -q '^auto usb0' $file || {
  969. /bin/cp $file $bu_file
  970. echo "$text" >> $file
  971. }
  972. systemctl mask network-manager
  973. else
  974. [ -e $bu_file ] && /bin/mv $bu_file $file
  975. systemctl unmask network-manager
  976. fi
  977. _display_file $file
  978. else
  979. warn "$file does not exist, not configuring static usb0"
  980. fi
  981. }
  982. create_cryptroot_unlock_sh() {
  983. local dest file text
  984. dest="$TARGET_ROOT/etc/initramfs-tools/hooks"
  985. file="$dest/cryptroot-unlock.sh"
  986. text='#!/bin/sh
  987. if [ "$1" = "prereqs" ]; then echo "dropbear-initramfs"; exit 0; fi
  988. . /usr/share/initramfs-tools/hook-functions
  989. source="/tmp/cryptroot-unlock-profile"
  990. root_home=$(echo $DESTDIR/root-*)
  991. root_home=${root_home#$DESTDIR}
  992. echo "if [ \"\$SSH_CLIENT\" ]; then /usr/bin/cryptroot-unlock; fi" > $source
  993. copy_file ssh_login_profile $source $root_home/.profile
  994. exit 0'
  995. mkdir -p $dest
  996. echo "$text" > $file
  997. chmod 755 $file
  998. _display_file $file
  999. }
  1000. # begin chroot functions:
  1001. apt_install_target() {
  1002. local pkgs=$(_print_pkgs_to_install 'target')
  1003. [ "$pkgs" ] && {
  1004. echo "target packages to install: $pkgs"
  1005. local ls1 ls2
  1006. _show_output
  1007. ls1=$(ls -l /boot/initrd.img-*)
  1008. # DEBUG:
  1009. # dpkg-reconfigure $pkgs # doesn't work in chroot
  1010. # apt --yes purge $pkgs
  1011. # apt-get --yes --purge autoremove
  1012. dpkg --configure --pending --force-confdef
  1013. set +e
  1014. apt --yes purge 'bash-completion'
  1015. apt --yes purge 'command-not-found'
  1016. set -e
  1017. _apt_update
  1018. echo 'force-confdef' > /root/.dpkg.cfg
  1019. apt --yes install $pkgs
  1020. rm /root/.dpkg.cfg
  1021. apt --yes autoremove
  1022. ls2=$(ls -l /boot/initrd.img-*)
  1023. [ "$ls1" != "$ls2" ] && initramfs_updated='y'
  1024. _hide_output
  1025. }
  1026. true
  1027. }
  1028. update_initramfs() {
  1029. [ "$ROOTENC_TESTING" ] && return 0
  1030. _show_output
  1031. local ver=$(echo /boot/vmlinu?-* | sed 's/.boot.vmlinu.-//')
  1032. update-initramfs -k $ver -u
  1033. _hide_output
  1034. }
  1035. check_initramfs() {
  1036. local text chk count
  1037. text="$(lsinitramfs /boot/initrd.img*)"
  1038. set +e
  1039. chk=$(echo "$text" | grep 'cryptsetup')
  1040. count=$(echo "$chk" | wc -l)
  1041. [ "$count" -gt 5 ] || { echo "$text"; die 'Cryptsetup scripts missing in initramfs image'; }
  1042. _display_file "lsinitramfs /boot/initrd.img* | grep 'cryptsetup'" "$chk"
  1043. [ "$IP_ADDRESS" == 'none' ] || {
  1044. chk=$(echo "$text" | grep 'dropbear')
  1045. count=$(echo "$chk" | wc -l)
  1046. [ "$count" -gt 5 ] || { echo "$text"; die 'Dropbear scripts missing in initramfs image'; }
  1047. _display_file "lsinitramfs /boot/initrd.img* | grep 'dropbear'" "$chk"
  1048. chk=$(echo "$text" | grep 'authorized_keys')
  1049. count=$(echo "$chk" | wc -l)
  1050. [ "$count" -eq 1 ] || { echo "$text"; die 'authorized_keys missing in initramfs image'; }
  1051. _display_file "lsinitramfs /boot/initrd.img* | grep 'authorized_keys'" "$chk"
  1052. }
  1053. set -e
  1054. }
  1055. configure_target() {
  1056. [ "$PARTITION_ONLY" ] && return
  1057. mount_target
  1058. _set_target_vars
  1059. copy_etc_files
  1060. copy_etc_files_distro_specific
  1061. edit_initramfs_conf
  1062. edit_initramfs_modules
  1063. [ "$IP_ADDRESS" == 'none' ] || copy_authorized_keys
  1064. create_etc_crypttab
  1065. create_fstab
  1066. edit_dropbear_cfg
  1067. netman_manage_usb0
  1068. ifupdown_config_usb0
  1069. [ "$IP_ADDRESS" == 'none' ] || create_cryptroot_unlock_sh
  1070. edit_armbianEnv
  1071. _debug_pause
  1072. _show_output # this must be done before entering chroot
  1073. /bin/cp $0 $TARGET_ROOT
  1074. export 'ROOTFS_NAME' 'IP_ADDRESS' 'target_distro' 'ROOTENC_TESTING' 'ROOTENC_PAUSE' 'ROOTENC_IGNORE_APT_ERRORS' 'APT_UPGRADE'
  1075. chroot $TARGET_ROOT "./$PROGNAME" $ORIG_OPTS 'in_target'
  1076. /bin/cp -a '/etc/resolv.conf' "$TARGET_ROOT/etc" # this could be a symlink
  1077. /bin/rm "$TARGET_ROOT/$PROGNAME"
  1078. _add_state_file 'target_configured' 'target'
  1079. }
  1080. _set_env_vars() {
  1081. shopt -s extglob
  1082. local name val
  1083. while [ $# -gt 0 ]; do
  1084. name=${1%=?*} val=${1#+([A-Z_])=}
  1085. [ "$name" == "$1" -o "$val" == "$1" ] && die "$1: illegal argument (must be in format 'NAME=value')"
  1086. eval "$name=$val"
  1087. shift
  1088. done
  1089. shopt -u extglob
  1090. }
  1091. _mount_target_and_exit() {
  1092. setup_loopmount
  1093. mount_target
  1094. _set_target_vars
  1095. rmdir $BOOT_ROOT
  1096. exit
  1097. }
  1098. # begin execution
  1099. set -e
  1100. while getopts hCfFmo:MUpRsudvz OPT
  1101. do
  1102. case "$OPT" in
  1103. h) print_help; exit ;;
  1104. C) NO_CLEANUP='y' ;;
  1105. f) FORCE_RECONFIGURE='y' ;;
  1106. F) FORCE_REBUILD='y' ;;
  1107. m) ADD_ALL_MODS='y' ;;
  1108. o) ADD_MODS=$OPTARG ;;
  1109. M) MOUNT_TARGET_ONLY='y' ;;
  1110. U) UMOUNT_TARGET_ONLY='y' ;;
  1111. p) PARTITION_ONLY='y' ;;
  1112. R) FORCE_REFORMAT_ROOT='y' ;;
  1113. s) USE_LOCAL_AUTHORIZED_KEYS='y' ;;
  1114. u) APT_UPGRADE='y' ;;
  1115. d) DEBUG='y' ;&
  1116. v) VERBOSE='y' RSYNC_VERBOSITY='--verbose' ;;
  1117. z) ERASE='y' ;;
  1118. *) exit ;;
  1119. esac
  1120. ORIG_OPTS+="-$OPT $OPTARG "
  1121. done
  1122. shift $((OPTIND-1))
  1123. trap '_return_handler' RETURN
  1124. trap '_sigint_handler' INT
  1125. trap '_error_handler' ERR
  1126. set -o functrace
  1127. set -o errtrace
  1128. exec {stdout_dup}>&1
  1129. exec {stderr_dup}>&2
  1130. [ $UID == 0 -o $EUID == 0 ] || die 'This program must be run as root!'
  1131. export HOME='/root'
  1132. [ "$DEBUG" ] && set -x
  1133. ARG1=$1; shift
  1134. _set_env_vars $@
  1135. if [ "$ARG1" == 'in_target' ]; then
  1136. SCRIPT_DESC='Target script'
  1137. _hide_output
  1138. [ "$target_distro" == 'bionic' ] && {
  1139. echo 'export CRYPTSETUP=y' > '/etc/initramfs-tools/conf.d/cryptsetup'
  1140. }
  1141. apt_install_target
  1142. [ "$initramfs_updated" ] || update_initramfs
  1143. check_initramfs
  1144. else
  1145. SCRIPT_DESC='Host script'
  1146. _do_header
  1147. _set_host_vars
  1148. get_armbian_image
  1149. apt_install_host # _preclean requires cryptsetup
  1150. _preclean
  1151. [ "$UMOUNT_TARGET_ONLY" ] && exit
  1152. check_sdcard_name_and_params $ARG1
  1153. create_build_dir
  1154. [ "$MOUNT_TARGET_ONLY" ] && warn 'Mounting source and target and exiting at user request'
  1155. _get_user_vars
  1156. _test_sdcard_mounted
  1157. [ "$MOUNT_TARGET_ONLY" ] && _mount_target_and_exit
  1158. _warn_user_opts
  1159. _confirm_user_vars
  1160. [ "$IP_ADDRESS" == 'none' ] || get_authorized_keys
  1161. [ "$NO_CLEANUP" ] || trap '_clean' EXIT
  1162. setup_loopmount
  1163. _debug_pause
  1164. check_install_state
  1165. _hide_output
  1166. [ "$card_partitioned" == 'n' ] && _do_partition
  1167. _debug_pause
  1168. [ "$bootpart_copied" == 'n' ] && copy_system_boot
  1169. [ "$bootpart_label_created" == 'n' ] && create_bootpart_label
  1170. [ "$rootpart_copied" == 'n' ] && copy_system_root
  1171. [ "$target_configured" == 'n' ] && configure_target
  1172. sync
  1173. gmsg 'All done!'
  1174. if [ "$IP_ADDRESS" != 'none' ]; then
  1175. imsg "To unlock the target disk, execute the following from the unlocking host:"
  1176. imsg " ssh -p 2222 root@${IP_ADDRESS/dhcp/TARGET_IP}"
  1177. fi
  1178. fi