armbian_rootenc_setup.sh 41 KB

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