armbian_rootenc_setup.sh 40 KB

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