armbian_rootenc_setup.sh 38 KB

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