armbian_rootenc_setup.sh 42 KB

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