armbian_rootenc_setup.sh 40 KB

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