armbian_rootenc_setup.sh 41 KB

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