armbian_rootenc_setup.sh 45 KB

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