armbian_rootenc_setup.sh 39 KB

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