├── diff.sh ├── lft.sh ├── lib ├── block-dev.sh ├── board.sh ├── bootloader.sh ├── distro.sh ├── dmi.sh ├── left.sh ├── toolkit.sh ├── traps.sh └── wget.sh ├── readme.md └── vendor ├── allwinner └── bin │ └── sunxi-fel-x86_64 └── rockchip └── bin ├── rockusb-aarch64 └── rockusb-x86_64 /diff.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | if [ -z "$1" ] || [ "$1" = "-h" ] || [ "$1" = "--help" ]; then 3 | echo "$0 dt|config board[/master|linux-rolling-lts|linux-rolling-stable]" >&2 4 | exit 1 5 | fi 6 | 7 | case "$1" in 8 | "dt") 9 | target=dts 10 | ;; 11 | "config") 12 | target=config 13 | ;; 14 | *) 15 | echo "?" >&2 16 | exit 1 17 | ;; 18 | esac 19 | 20 | getURL(){ 21 | board="${1%%/*}" 22 | if [ "$board" != "$1" ]; then 23 | release="${1#*/}" 24 | echo "http://boot.libre.computer/vanilla/$board/$release/$board-$release.$target" 25 | else 26 | echo "http://boot.libre.computer/ci/$1.$target" 27 | fi 28 | } 29 | 30 | url1=$(getURL "$2") 31 | url2=$(getURL "$3") 32 | echo "$url1" 33 | echo "$url2" 34 | diff -y --color=always -W $(tput cols) <(curl "$url1") <(curl "$url2") | grep -v "phandle = " 35 | -------------------------------------------------------------------------------- /lft.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # SPDX-License-Identifier: GPL-2.0 3 | # Copyright (C) 2022 Da Xue 4 | 5 | set -e 6 | 7 | cd $(dirname $(readlink -f "${BASH_SOURCE[0]}")) 8 | 9 | . lib/traps.sh 10 | . lib/toolkit.sh 11 | . lib/wget.sh 12 | . lib/dmi.sh 13 | . lib/board.sh 14 | . lib/block-dev.sh 15 | 16 | #1 command {flash} 17 | #2 board 18 | #3 block device 19 | #4+ parameter 20 | 21 | main(){ 22 | local cmd="help" 23 | if [ ! -z "$1" ]; then 24 | local cmd=$1 25 | shift 26 | fi 27 | if [ "${cmd%%-*}" = "b" -o "${cmd%%-*}" = "board" ]; then 28 | local action 29 | if [ ! -z "$1" ]; then 30 | local action=${1,,} 31 | shift 32 | fi 33 | elif [ "${cmd%%-*}" = "bl" -o "${cmd%%-*}" = "bootloader" ]; then 34 | . lib/bootloader.sh 35 | local board 36 | if [ ! -z "$1" ]; then 37 | local board=${1,,} 38 | shift 39 | fi 40 | local dev="null" 41 | if [ ! -z "$1" ]; then 42 | local dev=$1 43 | shift 44 | fi 45 | elif [ "${cmd%%-*}" = "dist" -o "${cmd%%-*}" = "distro" ]; then 46 | . lib/distro.sh 47 | . lib/left.sh 48 | local distro 49 | if [ ! -z "$1" ]; then 50 | local distro=${1,,} 51 | shift 52 | fi 53 | local release 54 | if [ ! -z "$1" ]; then 55 | local release=${1,,} 56 | shift 57 | fi 58 | local variant 59 | if [ ! -z "$1" ]; then 60 | local variant=${1,,} 61 | shift 62 | fi 63 | local board 64 | if [ ! -z "$1" ]; then 65 | local board=${1,,} 66 | shift 67 | fi 68 | local dev="null" 69 | if [ ! -z "$1" ]; then 70 | local dev=$1 71 | shift 72 | fi 73 | elif [ "${cmd%%-*}" = "left" ]; then 74 | . lib/distro.sh 75 | . lib/left.sh 76 | local dev="null" 77 | if [ ! -z "$1" ]; then 78 | local dev=$1 79 | shift 80 | fi 81 | local image="" 82 | if [ ! -z "$1" ]; then 83 | local image="$1" 84 | shift 85 | fi 86 | fi 87 | local param 88 | if [ ! -z "$1" ]; then 89 | local param=("$@") 90 | fi 91 | case ${cmd,,} in 92 | help) 93 | echo "COMMAND device-list board-help bootloader-help distro-help left-help" >&2 94 | return 1 95 | ;; 96 | dev-list|device-list) 97 | BLOCK_DEV_get 98 | ;; 99 | dev-list-all|device-list-all) 100 | BLOCK_DEV_get 1 101 | ;; 102 | b-help|board-help) 103 | echo "COMMAND BOARD [DEVICE] [PARAMETERS]" >&2 104 | echo "b-list|board-list" >&2 105 | echo "b-emmc|board-emmc status" >&2 106 | echo "b-emmc|board-emmc bind|unbind|rebind" >&2 107 | echo "b-emmc|board-emmc show" >&2 108 | echo "b-emmc|board-emmc test read|write" >&2 109 | echo "b-bootrom|board-bootrom usb-drive BOARD [DEVICE] [INDEX]" >&2 110 | ;; 111 | b-list|board-list) 112 | BOARD_list 113 | ;; 114 | b-emmc|board-emmc) 115 | case ${action,,} in 116 | "status") 117 | BOARD_EMMC_isBound 118 | ;; 119 | "bind"|"unbind"|"rebind"|"test") 120 | BOARD_EMMC_${action} "${param[@]}" 121 | ;; 122 | esac 123 | ;; 124 | b-bootrom|board-bootrom) 125 | . lib/bootloader.sh 126 | case "${action,,}" in 127 | "usb-drive") 128 | BOARD_BOOTROM_USB_drive "${param[@]}" 129 | ;; 130 | # "usb-dfu") 131 | # BOARD_BOOTROM_USB_dfu "${param[@]}" 132 | # ;; 133 | *) 134 | echo "BOARD BootROM: ${action,,} not supported or implemented." 135 | ;; 136 | esac 137 | ;; 138 | bl-help|bootloader-help) 139 | echo "COMMAND BOARD [DEVICE] [PARAMETERS]" >&2 140 | echo "bl-offset|bootloader-offset BOARD" >&2 141 | echo "bl-url|bootloader-url BOARD" >&2 142 | echo "bl-flash|bootloader-flash BOARD DEVICE force|verify" >&2 143 | echo "bl-wipe|bootloader-wipe DEVICE force|verify" >&2 144 | return 1 145 | ;; 146 | bl-offset|bootloader-offset) 147 | if [ -z "$board" ]; then 148 | echo "$0 ${cmd^^} BOARD" >&2 149 | return 1 150 | fi 151 | if ! BOOTLOADER_isValid $board; then 152 | echo "$FUNCNAME: BOARD $board is not valid." >&2 153 | return 1 154 | fi 155 | echo $(BOOTLOADER_getOffset $board) 156 | ;; 157 | bl-url|bootloader-url) 158 | if [ -z "$board" ]; then 159 | echo "$0 ${cmd^^} BOARD" >&2 160 | return 1 161 | fi 162 | echo $(BOOTLOADER_getURL $board) 163 | ;; 164 | bl-flash|bootloader-flash) 165 | if [ -z "$board" ]; then 166 | echo "$0 ${cmd^^} BOARD [DEVICE]" >&2 167 | return 1 168 | fi 169 | BOOTLOADER_flash "$board" "$dev" "${param[@]}" 170 | ;; 171 | bl-wipe|bootloader-wipe) 172 | if [ -z "$board" ]; then 173 | echo "$0 ${cmd^^} BOARD [DEVICE]" >&2 174 | return 1 175 | fi 176 | BOOTLOADER_wipe "$board" "$dev" "${param[@]}" 177 | ;; 178 | dist-help|distro-help) 179 | echo "COMMAND [DISTRO] [RELEASE] [VARIANT] [BOARD] [DEVICE] [PARAMETERS]" >&2 180 | echo "dist-list|distro-list [DISTRO] [RELEASE] [VARIANT] [BOARD]" >&2 181 | echo "dist-flash|distro-flash|distro-flash-left [DISTRO] [RELEASE] [VARIANT] [BOARD] [DEVICE] [PARAMETERS]" >&2 182 | return 1 183 | ;; 184 | dist-list|distro-list) 185 | DISTRO_list "$distro" "$release" "$variant" "$board" 186 | ;; 187 | dist-flash|distro-flash) 188 | DISTRO_flash "$distro" "$release" "$variant" "$board" "$dev" "${param[@]}" 189 | ;; 190 | dist-flash-left|distro-flash-left) 191 | DISTRO_LEFT_flash "$distro" "$release" "$variant" "$board" "$dev" "${param[@]}" 192 | ;; 193 | left-help) 194 | echo "COMMAND [DEVICE] [IMAGE] [PARAMETERS]" >&2 195 | echo "left-flash [DEVICE] [IMAGE] [PARAMETERS]" >&2 196 | return 1 197 | ;; 198 | left-flash) 199 | LEFT_flash "$dev" "$image" "${param[@]}" 200 | ;; 201 | *) 202 | echo "$FUNCNAME: COMMAND $cmd is not valid." >&2 203 | exit 1 204 | ;; 205 | esac 206 | } 207 | 208 | main "$@" 209 | -------------------------------------------------------------------------------- /lib/block-dev.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # SPDX-License-Identifier: GPL-2.0 3 | # Copyright (C) 2022 Da Xue 4 | 5 | BLOCK_DEV_get(){ 6 | local blk_show_all=${1:-0} 7 | local blk_devs=$(lsblk -dn | cut -f 1 -d " ") 8 | local blk_dev 9 | local blk_part 10 | for blk_dev in $blk_devs; do 11 | local blk_show=1 12 | for blk_part in $(ls /dev/$blk_dev*); do 13 | case "$(findmnt -no TARGET $blk_part)" in 14 | "") 15 | : 16 | ;; 17 | /media/*) 18 | : 19 | ;; 20 | *) 21 | local blk_show=0 22 | break 23 | ;; 24 | esac 25 | done 26 | if [ "$blk_show" -eq 1 -o "$blk_show_all" -eq 1 ]; then 27 | echo $blk_dev 28 | fi 29 | done 30 | } 31 | 32 | BLOCK_DEV_isValid(){ 33 | local dev=$1 34 | local blk_show_all=${2:-0} 35 | if [ "$dev" = "null" ]; then 36 | return 0 37 | fi 38 | for _dev in $(BLOCK_DEV_get $blk_show_all); do 39 | if [ "$dev" = "$_dev" ]; then 40 | return 0 41 | fi 42 | done 43 | return 1 44 | } 45 | 46 | BLOCK_DEV_isMounted(){ 47 | local dev=$1 48 | if [ "$dev" = "null" ]; then 49 | return 1 50 | fi 51 | for blk_part in $(ls /dev/$dev*); do 52 | local blk_mnt=$(findmnt -no TARGET $blk_part) 53 | if [ ! -z "$blk_mnt" ]; then 54 | return 0 55 | fi 56 | done 57 | return 1 58 | } 59 | 60 | BLOCK_DEV_getInfo(){ 61 | local dev=$1 62 | lsblk -dnyo TRAN,SIZE,VENDOR,MODEL,SERIAL /dev/$dev 63 | } 64 | 65 | BLOCK_DEV_getPartPrefix(){ 66 | if [ "${1/\/dev\/mmcblk/}" != "$1" ]; then 67 | echo -n "p" 68 | elif [ "${1/\/dev\/nvme/}" != "$1" ]; then 69 | echo -n "p" 70 | elif [ "${1/\/dev\/loop/}" != "$1" ]; then 71 | echo -n "p" 72 | fi 73 | } 74 | 75 | BLOCK_DEV_mkfs(){ 76 | local type="$1" 77 | local target="$2" 78 | } 79 | -------------------------------------------------------------------------------- /lib/board.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # SPDX-License-Identifier: GPL-2.0 3 | # Copyright (C) 2021 Da Xue 4 | 5 | declare -a BOARD_LIST=( 6 | "all-h3-cc-h3" 7 | "all-h3-cc-h5" 8 | "aml-a311d-cc-v01" 9 | "aml-a311d-cc" 10 | "aml-s805x-ac" 11 | "aml-s905x-cc" 12 | "aml-s905x-cc-v2" 13 | "aml-s905d-pc" 14 | "aml-s905d3-cc-v01" 15 | "aml-s905d3-cc" 16 | "roc-rk3328-cc" 17 | "roc-rk3328-cc-v2" 18 | "roc-rk3399-pc" 19 | ) 20 | 21 | BOARD_list(){ 22 | local _board 23 | for _board in ${BOARD_LIST[@]}; do 24 | echo $_board 25 | done 26 | } 27 | 28 | declare -A BOARD_EMMC_DT_NODE=( 29 | [all-h3-cc-h3]=1c11000.mmc 30 | [all-h3-cc-h5]=1c11000.mmc 31 | [aml-a311d-cc-v01]=ffe07000.mmc 32 | [aml-a311d-cc]=ffe07000.mmc 33 | [aml-s805x-ac]=d0074000.mmc 34 | [aml-s905x-cc]=d0074000.mmc 35 | [aml-s905x-cc-v2]=d0074000.mmc 36 | [aml-s905d-pc]=d0074000.mmc 37 | [aml-s905d3-cc-v01]=ffe07000.mmc 38 | [aml-s905d3-cc]=ffe07000.mmc 39 | [roc-rk3328-cc]=ff520000.mmc 40 | [roc-rk3328-cc-v2]=ff520000.mmc 41 | [roc-rk3399-pc]=fe320000.mmc 42 | ) 43 | 44 | declare -A BOARD_EMMC_DRIVER=( 45 | [all-h3-cc-h3]=sunxi-mmc 46 | [all-h3-cc-h5]=sunxi-mmc 47 | [aml-a311d-cc-v01]=meson-gx-mmc 48 | [aml-a311d-cc]=meson-gx-mmc 49 | [aml-s805x-ac]=meson-gx-mmc 50 | [aml-s905x-cc]=meson-gx-mmc 51 | [aml-s905x-cc-v2]=meson-gx-mmc 52 | [aml-s905d-pc]=meson-gx-mmc 53 | [aml-s905d3-cc-v01]=meson-gx-mmc 54 | [aml-s905d3-cc]=meson-gx-mmc 55 | [roc-rk3328-cc]=dwmmc_rockchip 56 | [roc-rk3328-cc-v2]=dwmmc_rockchip 57 | [roc-rk3399-pc]=dwmmc_rockchip 58 | ) 59 | 60 | BOARD_NAME_get(){ 61 | if [ "$(DMI_BOARD_VENDOR_get)" != "libre-computer" ]; then 62 | echo "This command is designed for Libre Computer products." >&2 63 | exit 2 64 | fi 65 | local board=$(echo -n $(DMI_BOARD_NAME_get | tr "[:punct:]" " ") | tr -s ' ' '-' | tr '[:upper:]' '[:lower:]') 66 | 67 | while [ -z "${BOARD_EMMC_DRIVER[$board]}" ]; do 68 | local board_new="${board%-*}" 69 | if [ -z "$board_new" ] || [ "$board_new" = "$board" ]; then 70 | echo "$FUNCNAME: BOARD $1 is not supported" >&2 71 | return 1 72 | fi 73 | local board="$board_new" 74 | done 75 | echo -n $board 76 | } 77 | 78 | BOARD_DRIVER_PATH=/sys/bus/platform/drivers 79 | 80 | BOARD_EMMC_isBound(){ 81 | local board=${1:-$(BOARD_NAME_get)} 82 | if [ -z "$board" ]; then 83 | return 2 84 | fi 85 | [ -e "$BOARD_DRIVER_PATH/${BOARD_EMMC_DRIVER[$board]}/${BOARD_EMMC_DT_NODE[$board]}" ] 86 | } 87 | 88 | BOARD_EMMC_bind(){ 89 | local board=${1:-$(BOARD_NAME_get)} 90 | if [ -z "$board" ]; then 91 | return 2 92 | fi 93 | if BOARD_EMMC_isBound $board; then 94 | echo "$FUNCNAME: eMMC already bound." >&2 95 | return 1 96 | fi 97 | local driver_bind=$BOARD_DRIVER_PATH/${BOARD_EMMC_DRIVER[$board]}/bind 98 | if [ ! -w "$driver_bind" ]; then 99 | echo "$FUNCNAME: eMMC write permission denied." >&2 100 | return 1 101 | fi 102 | echo -n ${BOARD_EMMC_DT_NODE[$board]} > $driver_bind 103 | } 104 | 105 | BOARD_EMMC_unbind(){ 106 | local board=$(BOARD_NAME_get) 107 | if [ -z "$board" ]; then 108 | return 2 109 | fi 110 | if ! BOARD_EMMC_isBound $board; then 111 | echo "$FUNCNAME: eMMC not bound." >&2 112 | return 1 113 | fi 114 | local driver_unbind=$BOARD_DRIVER_PATH/${BOARD_EMMC_DRIVER[$board]}/unbind 115 | if [ ! -w "$driver_unbind" ]; then 116 | echo "$FUNCNAME: eMMC write permission denied." >&2 117 | return 1 118 | fi 119 | echo -n ${BOARD_EMMC_DT_NODE[$board]} > $driver_unbind 120 | } 121 | 122 | BOARD_EMMC_rebind(){ 123 | local board=$(BOARD_NAME_get) 124 | if [ -z "$board" ]; then 125 | return 2 126 | fi 127 | if BOARD_EMMC_isBound $board; then 128 | BOARD_EMMC_unbind $board 129 | fi 130 | sleep 1 131 | BOARD_EMMC_bind $board 132 | } 133 | 134 | BOARD_EMMC_show(){ 135 | echo "Not Implemented." >&2 136 | } 137 | 138 | BOARD_EMMC_test(){ 139 | echo "Not Implemented." >&2 140 | } 141 | 142 | BOARD_BOOTROM_USB_drive(){ 143 | if [ -z "$1" ]; then 144 | echo "$FUNCNAME: Board required." >&2 145 | return 2 146 | fi 147 | if [ ! -z "$2" ]; then 148 | if [ "${2,,}" != "emmc" ]; then 149 | echo "$FUNCNAME: Only eMMC drive mode is implemented." >&2 150 | return 2 151 | fi 152 | fi 153 | local board=$1 154 | case $board in 155 | all-*-h5) 156 | local usb_device=1f3a:efe8 157 | local soc_vendor=allwinner 158 | local soc_tool="bin/sunxi-fel-$(uname -m) uboot" 159 | local soc_tool_canfail=0 160 | ;; 161 | roc-rk3328-*) 162 | local usb_device=2207:320c 163 | local soc_vendor=rockchip 164 | local soc_tool="bin/rockusb-$(uname -m) download-boot" 165 | local soc_tool_canfail=1 166 | ;; 167 | roc-rk3399-*) 168 | local soc_vendor=rockchip 169 | local usb_device=2207:330c 170 | local soc_tool="bin/rockusb-$(uname -m) download-boot" 171 | local soc_tool_canfail=0 172 | ;; 173 | *) 174 | echo "$FUNCNAME: Board $board is not supported." >&2 175 | return 2 176 | ;; 177 | esac 178 | local usb_device_list=$(lsusb -d $usb_device) 179 | if [ -z "$usb_device_list" ]; then 180 | echo "$FUNCNAME: No USB devices found matching $usb_device." >&2 181 | return 1 182 | fi 183 | 184 | traps_start 185 | local bl=$(mktemp) 186 | local bl_wget_log=$(mktemp) 187 | traps_push rm "$bl" "$bl_wget_log" 188 | 189 | if ! wget -O "$bl" "$BOOTLOADER_URL/$board-ums-emmc" 2> "$bl_wget_log"; then 190 | cat $bl_wget_log 191 | if grep -io "404\sNot\sFound" $bl_wget_log > /dev/null; then 192 | echo "$FUNCNAME: BOARD $board bootloader could not be found." >&2 193 | else 194 | echo "$FUNCNAME: BOARD $board bootloader server could not be reached." >&2 195 | fi 196 | return 1 197 | fi 198 | vendor/$soc_vendor/$soc_tool "$bl" || [ "$soc_tool_canfail" -eq 1 ] 199 | traps_popUntilLength 0 200 | traps_stop 201 | echo "Please wait a minute for the board to enumerate the ${2,,} as a USB drive or an ACM debug device if the ${2,,} cannot be enumerated for any reason." 202 | } 203 | 204 | BOARD_BOOTROM_USB_dfu(){ 205 | if [ -z "$1" ]; then 206 | echo "$FUNCNAME: Board required." >&2 207 | return 2 208 | fi 209 | local board=$1 210 | case $board in 211 | all-*-h5) 212 | local usb_device=1f3a:efe8 213 | local soc_vendor=allwinner 214 | local soc_tool="bin/sunxi-fel-$(uname -m) uboot" 215 | local soc_tool_canfail=0 216 | ;; 217 | roc-rk3328-*) 218 | local usb_device=2207:320c 219 | local soc_vendor=rockchip 220 | local soc_tool="bin/rockusb-$(uname -m) download-boot" 221 | local soc_tool_canfail=1 222 | ;; 223 | roc-rk3399-*) 224 | local soc_vendor=rockchip 225 | local usb_device=2207:330c 226 | local soc_tool="bin/rockusb-$(uname -m) download-boot" 227 | local soc_tool_canfail=0 228 | ;; 229 | *) 230 | echo "$FUNCNAME: Board $board is not supported." >&2 231 | return 2 232 | ;; 233 | esac 234 | set -x 235 | local usb_device_list=$(lsusb -d $usb_device) 236 | if [ -z "$usb_device_list" ]; then 237 | echo "$FUNCNAME: No USB devices found matching $usb_device." >&2 238 | return 1 239 | fi 240 | 241 | traps_start 242 | local bl=$(mktemp) 243 | local bl_wget_log=$(mktemp) 244 | traps_push rm "$bl" "$bl_wget_log" 245 | 246 | if ! wget -O "$bl" "$BOOTLOADER_URL/$board" 2> "$bl_wget_log"; then 247 | cat $bl_wget_log 248 | if grep -io "404\sNot\sFound" $bl_wget_log > /dev/null; then 249 | echo "$FUNCNAME: BOARD $board bootloader could not be found." >&2 250 | else 251 | echo "$FUNCNAME: BOARD $board bootloader server could not be reached." >&2 252 | fi 253 | return 1 254 | fi 255 | vendor/$soc_vendor/$soc_tool "$bl" || [ "$soc_tool_canfail" -eq 1 ] 256 | traps_popUntilLength 0 257 | traps_stop 258 | echo "Please wait a minute for the board to enumerate the ${2,,} as a USB DFU device or an ACM debug device if the ${2,,} cannot be enumerated." 259 | } 260 | -------------------------------------------------------------------------------- /lib/bootloader.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # SPDX-License-Identifier: GPL-2.0 3 | # Copyright (C) 2021 Da Xue 4 | 5 | declare -A BOOTLOADER_OFFSET=( 6 | [all-h3-cc-h3]=16 7 | [all-h3-cc-h5]=16 8 | [aml-a311d-cc-v01]=1 9 | [aml-a311d-cc]=1 10 | [aml-s805x-ac]=1 11 | [aml-s905x-cc]=1 12 | [aml-s905x-cc-v2]=1 13 | [aml-s905d-pc]=1 14 | [aml-s905d3-cc-v01]=1 15 | [aml-s905d3-cc]=1 16 | [roc-rk3328-cc-v2]=64 17 | [roc-rk3328-cc]=64 18 | [roc-rk3399-pc]=64 19 | ) 20 | 21 | BOOTLOADER_URL="https://boot.libre.computer/ci" 22 | BOOTLOADER_BLK_SIZE=512 23 | BOOTLOADER_isValid(){ 24 | local board=$1 25 | local _board 26 | for _board in ${!BOOTLOADER_OFFSET[@]}; do 27 | if [ "$board" = "$_board" ]; then 28 | return 0 29 | fi 30 | done 31 | return 1 32 | } 33 | 34 | BOOTLOADER_getOffset(){ 35 | local board=$1 36 | if [ "${board##*-}" = "spiflash" ]; then 37 | echo -n 0 38 | return 39 | elif [ "${board##*-}" = "nfs" ]; then 40 | echo -n 0 41 | return 42 | elif [ "${board##*-}" = "test" ]; then 43 | echo -n 0 44 | return 45 | fi 46 | while [ -z "${BOOTLOADER_OFFSET[$board]}" ]; do 47 | local board_new="${board%-*}" 48 | if [ -z "$board_new" ] || [ "$board_new" = "$board" ]; then 49 | echo "$FUNCNAME: BOARD $1 is not supported" >&2 50 | return 1 51 | fi 52 | local board="$board_new" 53 | done 54 | echo -n ${BOOTLOADER_OFFSET[$board]} 55 | } 56 | 57 | BOOTLOADER_getURL(){ 58 | echo -n "${BOOTLOADER_URL}/${1}" 59 | } 60 | 61 | BOOTLOADER_getHeaders(){ 62 | WGET_getHeaders "$BOOTLOADER_URL/$1" 63 | } 64 | 65 | BOOTLOADER_get(){ 66 | local board=$1 67 | local bl=$2 68 | echo "$FUNCNAME: downloading $board bootloader to $bl." 69 | echo 70 | wget -O $bl "$BOOTLOADER_URL/$board" 2>&1 71 | echo "$FUNCNAME: downloaded $board bootloader to $bl." 72 | } 73 | 74 | BOOTLOADER_flash(){ 75 | local board=$1 76 | local dev=$2 77 | shift 2 78 | 79 | traps_start 80 | local bl=$(mktemp) 81 | traps_push rm "$bl" 82 | 83 | local force=0 84 | if TOOLKIT_isInCaseInsensitive "force" "$@"; then 85 | local force=1 86 | fi 87 | local verify=0 88 | if TOOLKIT_isInCaseInsensitive "verify" "$@"; then 89 | local verify=1 90 | fi 91 | 92 | local dev_path=/dev/$dev 93 | 94 | if ! BLOCK_DEV_isValid $dev $force; then 95 | echo "$FUNCNAME: DEVICE $dev is not a valid target." >&2 96 | return 1 97 | fi 98 | 99 | if BLOCK_DEV_isMounted $dev; then 100 | echo "$FUNCNAME: !!!WARNING!!! DEVICE $dev is mounted." >&2 101 | fi 102 | 103 | if [ ! -w "$dev_path" ]; then 104 | echo "$FUNCNAME: DEVICE $dev is not writable by current user $USER." >&2 105 | return 1 106 | fi 107 | 108 | if ! BOOTLOADER_getHeaders $board > $bl; then 109 | if grep -io "HTTP/1.1\s404\sNot\sFound" $bl > /dev/null; then 110 | echo "$FUNCNAME: BOARD $board bootloader could not be found." >&2 111 | else 112 | echo "$FUNCNAME: BOARD $board bootloader server could not be reached." >&2 113 | fi 114 | return 1 115 | fi 116 | local bl_size=$(grep -o "Content-Length:\s\+[0-9]*" $bl | tr -s " " | cut -f 2 -d " ") 117 | if [ $bl_size -lt $((100*1024)) ]; then 118 | echo "$FUNCNAME: BOARD $board bootloader size is unexpectedly small." >&2 119 | return 1 120 | fi 121 | if ! BOOTLOADER_get $board $bl; then 122 | echo "$FUNCNAME: BOARD $board bootloader could not be downloaded." >&2 123 | return 1 124 | fi 125 | 126 | if [ $(stat -c %s $bl) -ne $bl_size ]; then 127 | echo "$FUNCNAME: BOARD $board bootloader does not match expected size." >&2 128 | return 1 129 | fi 130 | 131 | if BLOCK_DEV_isMounted $dev; then 132 | echo "$FUNCNAME: !!!WARNING!!! DEVICE $dev is mounted." >&2 133 | fi 134 | 135 | local bl_dd_seek="" 136 | local bl_offset=$(BOOTLOADER_getOffset $board) 137 | if [ $bl_offset -eq 0 ]; then 138 | local bl_block_size=1M 139 | else 140 | local bl_block_size=$BOOTLOADER_BLK_SIZE 141 | local bl_dd_seek="seek=$bl_offset" 142 | fi 143 | local bl_flash_cmd="dd if=$bl of=$dev_path bs=$bl_block_size $bl_dd_seek conv=notrunc status=progress" 144 | 145 | if [ "$force" -eq 0 ]; then 146 | echo "$FUNCNAME: COMMAND: $bl_flash_cmd" >&2 147 | echo "$FUNCNAME: DEVICE $dev: $(BLOCK_DEV_getInfo $dev)" >&2 148 | echo "$FUNCNAME: Run the COMMAND above to flash the target DEVICE?" >&2 149 | if TOOLKIT_promptYesNo; then 150 | echo "$bl_flash_cmd" 151 | else 152 | echo "$FUNCNAME: operation cancelled." >&2 153 | return 1 154 | fi 155 | fi 156 | 157 | if $bl_flash_cmd; then 158 | sync $dev_path 159 | echo "$FUNCNAME: bootloader written to $dev successfully." >&2 160 | if [ "$verify" -eq 1 ]; then 161 | local bl_sector_count=$(((bl_size+$BOOTLOADER_BLK_SIZE-1)/$BOOTLOADER_BLK_SIZE)) 162 | if cmp <(dd if=$bl bs=$BOOTLOADER_BLK_SIZE count=$bl_sector_count 2> /dev/null) \ 163 | <(dd if=$dev_path bs=$BOOTLOADER_BLK_SIZE count=$bl_sector_count skip=$bl_offset 2> /dev/null) > /dev/null; then 164 | echo "$FUNCNAME: bootloader written to $dev verified." >&2 165 | else 166 | echo "$FUNCNAME: bootloader written to $dev failed verification!" >&2 167 | fi 168 | fi 169 | traps_pop 170 | traps_stop 171 | else 172 | echo "$FUNCNAME: bootloader write to $dev failed!" >&2 173 | return 1 174 | fi 175 | } 176 | 177 | BOOTLOADER_wipe(){ 178 | local board=$1 179 | local dev=$2 180 | shift 2 181 | local force=0 182 | if TOOLKIT_isInCaseInsensitive "force" "$@"; then 183 | local force=1 184 | fi 185 | 186 | local dev_path=/dev/$dev 187 | 188 | if ! BLOCK_DEV_isValid $dev $force; then 189 | echo "$FUNCNAME: DEVICE $dev is not a valid target." >&2 190 | return 1 191 | fi 192 | 193 | if BLOCK_DEV_isMounted $dev; then 194 | echo "$FUNCNAME: !!!WARNING!!! DEVICE $dev is mounted." >&2 195 | fi 196 | 197 | if [ ! -w "$dev_path" ]; then 198 | echo "$FUNCNAME: DEVICE $dev is not writable by current user $USER." >&2 199 | return 1 200 | fi 201 | 202 | if BLOCK_DEV_isMounted $dev; then 203 | echo "$FUNCNAME: !!!WARNING!!! DEVICE $dev is mounted." >&2 204 | fi 205 | 206 | local bl_dd_seek="" 207 | local bl_offset=$(BOOTLOADER_getOffset $board) 208 | local bl_block_size=$BOOTLOADER_BLK_SIZE 209 | local bl_dd_seek="seek=$bl_offset" 210 | local bl_count="count=$((2048-bl_offset))" 211 | local bl_flash_cmd="dd if=/dev/zero of=$dev_path bs=$bl_block_size $bl_dd_seek $bl_count conv=notrunc status=progress" 212 | 213 | if [ "$force" -eq 0 ]; then 214 | echo "$FUNCNAME: $bl_flash_cmd" >&2 215 | echo "$FUNCNAME: run the above command to flash the target device?" >&2 216 | while true; do 217 | read -s -n 1 -p "(y/n)" confirm 218 | echo 219 | case "${confirm,,}" in 220 | y|yes) 221 | echo "$bl_flash_cmd" 222 | break 223 | ;; 224 | n|no) 225 | echo "$FUNCNAME: operation cancelled." >&2 226 | return 1 227 | ;; 228 | esac 229 | done 230 | fi 231 | 232 | if $bl_flash_cmd; then 233 | sync $dev_path 234 | echo "$FUNCNAME: bootloader wiped from $dev successfully." >&2 235 | else 236 | echo "$FUNCNAME: bootloader wipe from $dev failed!" >&2 237 | return 1 238 | fi 239 | } 240 | -------------------------------------------------------------------------------- /lib/distro.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # SPDX-License-Identifier: GPL-2.0 3 | # Copyright (C) 2024 Da Xue 4 | 5 | declare -A DISTRO_NAME=( 6 | [debian]="Debian" 7 | [raspbian]="Raspbian" 8 | [ubuntu]="Ubuntu" 9 | ) 10 | 11 | declare -A DISTRO_DEBIAN_RELEASE=( 12 | [11]="Bullseye" 13 | [12]="Bookworm" 14 | ) 15 | 16 | declare -A DISTRO_DEBIAN_RELEASE_PREFIX=( 17 | [11]="debian-11-" 18 | [12]="debian-12-" 19 | ) 20 | 21 | declare -A DISTRO_RASPBIAN_RELEASE=( 22 | [10]="Buster" 23 | [11]="Bullseye" 24 | [12]="Bookworm" 25 | ) 26 | 27 | declare -A DISTRO_RASPBIAN_RELEASE_PREFIX=( 28 | [10]="2023-05-03-raspbian-buster-" 29 | [11]="2023-05-03-raspbian-bullseye-" 30 | [12]="2023-10-10-raspbian-bookworm-" 31 | ) 32 | 33 | declare -A DISTRO_UBUNTU_RELEASE=( 34 | [20.04]="Focal Fossa" 35 | [22.04]="Jammy Jellyfish" 36 | [24.04]="Noble Numbat" 37 | ) 38 | 39 | declare -A DISTRO_UBUNTU_RELEASE_PREFIX=( 40 | [20.04]="ubuntu-20.04.5-preinstalled-" 41 | [22.04]="ubuntu-22.04.3-preinstalled-" 42 | [24.04]="ubuntu-24.04-preinstalled-" 43 | ) 44 | 45 | DISTRO_URL="https://distro.libre.computer/ci" 46 | DISTRO_SHA256SUM=SHA256SUMS 47 | 48 | DISTRO_getURL(){ 49 | local distro=$1 50 | local release=$2 51 | shift 2 52 | echo -n $DISTRO_URL/$distro/$release/$@ 53 | } 54 | 55 | DISTRO_getSHA256SUMS(){ 56 | wget -O - $(DISTRO_getURL $1 $2 $DISTRO_SHA256SUM) 2> /dev/null 57 | } 58 | 59 | DISTRO_get(){ 60 | local url=$1 61 | local dist=$2 62 | echo "$FUNCNAME: downloading $url to $dist" 63 | echo 64 | #TODO: check download size vs mountpoint free space 65 | #TODO: direct write to disk with checksum verify if low space 66 | wget -O $dist "$url" 67 | echo "$FUNCNAME: downloaded $url to $dist." 68 | local checksum=$(sha256sum $dist | cut -f 1 -d ' ') 69 | if [ ! -z "$3" ]; then 70 | if [ "$checksum" != "$3" ]; then 71 | echo "$FUNCNAME: checksum $checksum does not match expected $3" 72 | return 1 73 | fi 74 | echo "$FUNCNAME: checksum verified." 75 | fi 76 | } 77 | 78 | DISTRO_list(){ 79 | if [ -z "$1" ]; then 80 | for distro_name in "${DISTRO_NAME[@]}"; do 81 | echo $distro_name 82 | done 83 | return 84 | elif [ ! -v "DISTRO_NAME[$1]" ]; then 85 | echo "$FUNCNAME: DISTRO $distro is not supported." >&2 86 | return 1 87 | fi 88 | local distro="$1" 89 | shift 90 | local distro_release="DISTRO_${distro^^}_RELEASE" 91 | declare -n distro_release="$distro_release" 92 | if [ -z "$1" ]; then 93 | for release in "${!distro_release[@]}"; do 94 | echo $release ${distro_release[$release]} 95 | done 96 | return 97 | elif [ ! -v "distro_release[$1]" ]; then 98 | echo "$FUNCNAME: DISTRO RELEASE $distro $release is not supported." >&2 99 | return 1 100 | fi 101 | local release="$1" 102 | local distro_release_prefix="DISTRO_${distro^^}_RELEASE_PREFIX" 103 | declare -n distro_release_prefix="$distro_release_prefix" 104 | local release_prefix="${distro_release_prefix[$release]}" 105 | shift 106 | local sha256sums=$(DISTRO_getSHA256SUMS $distro $release) 107 | if [ $? -eq 1 ]; then 108 | echo "$FUNCNAME: DISTRO RELEASE $distro $release manifest retreival failed." >&2 109 | return 1 110 | fi 111 | local variants=$(echo "$sha256sums" | sed "s/^.*$release_prefix//" | sed -E "s/-?arm(hf|64)-?//" | sed "s/+.*.img.[gx]z//" | sed "s/^$/desktop/" | sort | uniq) 112 | if [ -z "$variants" ]; then 113 | echo "$FUNCNAME: DISTRO RELEASE $distro $release variants are not available." >&2 114 | return 1 115 | fi 116 | if [ -z "$1" ]; then 117 | echo "$variants" 118 | return 119 | fi 120 | local variant_found=0 121 | for variant_name in $variants; do 122 | if [ "$variant_name" = "$1" ]; then 123 | local variant_found=1 124 | local variant="$1" 125 | shift 126 | break 127 | fi 128 | done 129 | if [ "$variant_found" -eq 0 ]; then 130 | echo "$FUNCNAME: DISTRO RELEASE VARIANT $distro $release $variant is not available." >&2 131 | return 1 132 | fi 133 | local boards=$(echo "$sha256sums" | sed "s/^.*$release_prefix//" | sed -E "s/-?arm(hf|64)-?//" | sed "s/.img.[gx]z//" | sed "s/^+/desktop+/" | grep "$variant" | sed "s/$variant//" | sed "s/+//" ) 134 | if [ -z "$1" ]; then 135 | echo "$boards" 136 | return 137 | fi 138 | local board_found=0 139 | for board_name in $boards; do 140 | if [ "$board_name" = "$1" ]; then 141 | local board_found=1 142 | local board="$1" 143 | shift 144 | break 145 | fi 146 | done 147 | if [ "$board_found" -eq 0 ]; then 148 | echo "$FUNCNAME: DISTRO RELEASE VARIANT BOARD $distro $release $variant $board is not available." >&2 149 | return 1 150 | fi 151 | if [ "$distro" = "raspbian" -a "$variant" = "desktop" ]; then 152 | local row=$(echo "$sha256sums" | grep "$release_prefix" | grep -v "\\-lite" | grep "+$board.img.[gx]z" | tac -s ' ') 153 | else 154 | local row=$(echo "$sha256sums" | grep "$release_prefix" | grep "$variant" | grep "+$board.img.[gx]z" | tac -s ' ') 155 | fi 156 | if [ $? -ne 0 ]; then 157 | echo "$FUNCNAME: Internal error. Please submit a bug report!" >&2 158 | return 1 159 | fi 160 | echo -n $(DISTRO_getURL $distro $release $row) 161 | } 162 | 163 | DISTRO_flash(){ 164 | local url_checksum=($(DISTRO_list $@)) 165 | local url=${url_checksum[0]} 166 | local checksum=${url_checksum[1]} 167 | shift 4 168 | 169 | traps_start 170 | local dist=$(mktemp) 171 | traps_push rm $dist 172 | 173 | local dev=$1 174 | shift 175 | 176 | local dev_path=/dev/$dev 177 | 178 | if ! BLOCK_DEV_isValid $dev; then 179 | echo "$FUNCNAME: DEVICE $dev is not a valid target." >&2 180 | return 1 181 | fi 182 | 183 | if BLOCK_DEV_isMounted $dev; then 184 | echo "$FUNCNAME: !!!ERROR!!! DEVICE $dev is mounted." >&2 185 | return 1 186 | fi 187 | 188 | if [ ! -w "$dev_path" ]; then 189 | echo "$FUNCNAME: DEVICE $dev is not writable by current user $USER." >&2 190 | return 1 191 | fi 192 | 193 | if ! WGET_getHeaders "$url" > $dist; then 194 | if grep -io "HTTP/1.1\s404\sNot\sFound" $dist > /dev/null; then 195 | echo "$FUNCNAME: DISTRO could not be found at $url." >&2 196 | else 197 | echo "$FUNCNAME: DISTRO server could not be reached." >&2 198 | fi 199 | return 1 200 | fi 201 | 202 | #local dist_size=$(grep -oi "Content-Length:\s\+[0-9]*" $dist | tail -n 1 | tr -s " " | cut -f 2 -d " ") 203 | #if [ "$dist_size" -lt $((100*1024*1024)) ]; then 204 | # echo "$FUNCNAME: DISTRO size is unexpectedly small." >&2 205 | # return 1 206 | #fi 207 | 208 | if ! DISTRO_get "$url" $dist $checksum; then 209 | echo "$FUNCNAME: DISTRO could not be downloaded." >&2 210 | return 1 211 | fi 212 | 213 | #if [ $(stat -c %s $dist) -ne $dist_size ]; then 214 | # echo "$FUNCNAME: DISTRO does not match expected size." >&2 215 | # return 1 216 | #fi 217 | #local dist_size=$(stat -c %s $dist) 218 | 219 | if BLOCK_DEV_isMounted $dev; then 220 | echo "$FUNCNAME: !!!ERROR!!! DEVICE $dev is mounted." >&2 221 | return 1 222 | fi 223 | 224 | local dist_flash_cmd="xz -cd $dist | dd of=$dev_path bs=1M iflag=fullblock oflag=dsync conv=notrunc status=progress" 225 | 226 | if ! TOOLKIT_isInCaseInsensitive "force" "$@"; then 227 | echo "$FUNCNAME: COMMAND: $dist_flash_cmd" >&2 228 | echo "$FUNCNAME: DEVICE $dev: $(BLOCK_DEV_getInfo $dev)" >&2 229 | echo "$FUNCNAME: Run the COMMAND above to flash the target DEVICE?" >&2 230 | if TOOLKIT_promptYesNo; then 231 | echo "$dist_flash_cmd" 232 | else 233 | echo "$FUNCNAME: operation cancelled." >&2 234 | return 1 235 | fi 236 | fi 237 | 238 | local dist_flash_bytes=$(eval "$dist_flash_cmd 2>&1 | tee /dev/stderr | grep -oE '^[0-9]+ bytes' | tail -n 1 | cut -f 1 -d ' '") 239 | if [ $? -eq 0 ]; then 240 | [ "$dev" = "null" ] || sync $dev_path 241 | echo "$FUNCNAME: distro written to $dev successfully." >&2 242 | if [ -z "$dist_flash_bytes" ]; then 243 | echo "$FUNCNAME: unable to determine decompressed size." >&2 244 | return 1 245 | elif TOOLKIT_isInCaseInsensitive "verify" "$@"; then 246 | if [ "$dev" = "null" ]; then 247 | echo "$FUNCNAME: null device cannot be verified." >&2 248 | return 1 249 | fi 250 | if cmp -n $dist_flash_bytes <(xz -cd $dist 2> /dev/null) $dev_path > /dev/null; then 251 | echo "$FUNCNAME: distro written to $dev verified." >&2 252 | traps_popUntilLength 0 253 | traps_stop 254 | else 255 | echo "$FUNCNAME: distro written to $dev failed verification!" >&2 256 | return 1 257 | fi 258 | else 259 | traps_popUntilLength 0 260 | traps_stop 261 | fi 262 | else 263 | echo "$FUNCNAME: distro write to $dev failed!" >&2 264 | return 1 265 | fi 266 | } 267 | 268 | DISTRO_LEFT_flash(){ 269 | local url_checksum=($(DISTRO_list $@)) 270 | local url=${url_checksum[0]} 271 | local checksum=${url_checksum[1]} 272 | shift 4 273 | 274 | traps_start 275 | local left=$(mktemp) 276 | traps_push rm $left 277 | 278 | local dist=$(mktemp) 279 | traps_push rm $dist 280 | 281 | local dev=$1 282 | shift 283 | 284 | local dev_path=/dev/$dev 285 | 286 | if ! BLOCK_DEV_isValid $dev; then 287 | echo "$FUNCNAME: DEVICE $dev is not a valid target." >&2 288 | return 1 289 | fi 290 | 291 | if BLOCK_DEV_isMounted $dev; then 292 | echo "$FUNCNAME: !!!ERROR!!! DEVICE $dev is mounted." >&2 293 | return 1 294 | fi 295 | 296 | if [ ! -w "$dev_path" ]; then 297 | echo "$FUNCNAME: DEVICE $dev is not writable by current user $USER." >&2 298 | return 1 299 | fi 300 | 301 | if ! WGET_getHeaders "$LEFT_URL" > $left; then 302 | if grep -io "HTTP/1.1\s404\sNot\sFound" $dist > /dev/null; then 303 | echo "$FUNCNAME: LEFT could not be found at $url." >&2 304 | else 305 | echo "$FUNCNAME: LEFT server could not be reached." >&2 306 | fi 307 | fi 308 | 309 | if ! WGET_getHeaders "$url" > $dist; then 310 | if grep -io "HTTP/1.1\s404\sNot\sFound" $dist > /dev/null; then 311 | echo "$FUNCNAME: DISTRO could not be found at $url." >&2 312 | else 313 | echo "$FUNCNAME: DISTRO server could not be reached." >&2 314 | fi 315 | return 1 316 | fi 317 | 318 | #local dist_size=$(grep -oi "Content-Length:\s\+[0-9]*" $dist | tail -n 1 | tr -s " " | cut -f 2 -d " ") 319 | #if [ "$dist_size" -lt $((100*1024*1024)) ]; then 320 | # echo "$FUNCNAME: DISTRO size is unexpectedly small." >&2 321 | # return 1 322 | #fi 323 | 324 | #TODO left checksum 325 | if ! DISTRO_get "$LEFT_URL" $left; then 326 | echo "$FUNCNAME: DISTRO could not be downloaded." >&2 327 | return 1 328 | fi 329 | 330 | #if [ $(stat -c %s $dist) -ne $dist_size ]; then 331 | # echo "$FUNCNAME: DISTRO does not match expected size." >&2 332 | # return 1 333 | #fi 334 | #local dist_size=$(stat -c %s $dist) 335 | 336 | if BLOCK_DEV_isMounted $dev; then 337 | echo "$FUNCNAME: !!!ERROR!!! DEVICE $dev is mounted." >&2 338 | return 1 339 | fi 340 | 341 | local left_flash_cmd="xz -cd $left | dd of=$dev_path bs=1M iflag=fullblock oflag=dsync conv=notrunc status=progress" 342 | 343 | if ! TOOLKIT_isInCaseInsensitive "force" "$@"; then 344 | echo "$FUNCNAME: $left_flash_cmd" >&2 345 | echo "$FUNCNAME: run the above command to flash the target device?" >&2 346 | if TOOLKIT_promptYesNo; then 347 | echo "$left_flash_cmd" 348 | else 349 | echo "$FUNCNAME: operation cancelled." >&2 350 | return 1 351 | fi 352 | fi 353 | 354 | local left_flash_bytes=$(eval "$left_flash_cmd 2>&1 | tee /dev/stderr | grep -oE '^[0-9]+ bytes' | tail -n 1 | cut -f 1 -d ' '") 355 | if [ $? -eq 0 ]; then 356 | [ "$dev" = "null" ] || sync $dev_path 357 | echo "$FUNCNAME: LEFT written to $dev successfully." >&2 358 | if [ -z "$left_flash_bytes" ]; then 359 | echo "$FUNCNAME: unable to determine decompressed size." >&2 360 | return 1 361 | elif TOOLKIT_isInCaseInsensitive "verify" "$@"; then 362 | if [ "$dev" = "null" ]; then 363 | echo "$FUNCNAME: null device cannot be verified." >&2 364 | return 1 365 | fi 366 | if cmp -n $left_flash_bytes <(xz -cd $left 2> /dev/null) $dev_path > /dev/null; then 367 | echo "$FUNCNAME: LEFT written to $dev verified." >&2 368 | else 369 | echo "$FUNCNAME: LEFT written to $dev failed verification!" >&2 370 | return 1 371 | fi 372 | fi 373 | # DOWNLOAD 374 | partprobe "$dev_path" 375 | local left_end=$(parted -m "$dev_path" unit s print | tail -n 1 | cut -f 3 -d : | grep -oE [0-9]+) 376 | local left_parted_cmd="parted $dev_path mkpart primary ext4 $((left_end+1))s 100%" 377 | echo "$FUNCNAME: $left_parted_cmd" >&2 378 | echo "$FUNCNAME: run the above command to partition the target device?" >&2 379 | if TOOLKIT_promptYesNo; then 380 | echo "$left_parted_cmd" 381 | eval "$left_parted_cmd" 382 | else 383 | echo "$FUNCNAME: operation cancelled." >&2 384 | return 1 385 | fi 386 | partprobe "$dev_path" 387 | local left_part_num=$(parted -m "$dev_path" unit s print | tail -n 1 | cut -f 1 -d :) 388 | local left_part_path="${dev_path}$(BLOCK_DEV_getPartPrefix $dev_path)${left_part_num}" 389 | mkfs.ext4 -F "$left_part_path" 390 | local left_part_dir=$(mktemp -d) 391 | traps_push rmdir "$left_part_dir" 392 | mount "$left_part_path" "$left_part_dir" 393 | traps_push umount "$left_part_dir" 394 | local left_distro_filename="${url##*/}" 395 | if ! DISTRO_get "$url" "$left_part_dir/$left_distro_filename" $checksum; then 396 | echo "$FUNCNAME: DISTRO could not be downloaded." >&2 397 | return 1 398 | fi 399 | echo "IMAGE_FILE=$left_distro_filename" > "$left_part_dir/flash.ini" 400 | echo "$FUNCNAME: DISTRO written to $dev$(BLOCK_DEV_getPartPrefix $dev_path)$left_part_num successfully." >&2 401 | traps_popUntilLength 0 402 | traps_stop 403 | else 404 | echo "$FUNCNAME: LEFT write to $dev failed!" >&2 405 | return 1 406 | fi 407 | } 408 | -------------------------------------------------------------------------------- /lib/dmi.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | ## SPDX-License-Identifier: GPL-2.0 3 | ## Copyright (C) 2021 Da Xue 4 | 5 | DMI_LINUX_PATH=/sys/class/dmi/id 6 | DMI_BOARD_VENDOR_get(){ 7 | tr -d '\0' < $DMI_LINUX_PATH/board_vendor 8 | } 9 | 10 | DMI_BOARD_NAME_get(){ 11 | tr -d '\0' < $DMI_LINUX_PATH/board_name 12 | } 13 | 14 | 15 | -------------------------------------------------------------------------------- /lib/left.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # SPDX-License-Identifier: GPL-2.0 3 | # Copyright (C) 2022 Da Xue 4 | 5 | LEFT_URL="https://distro.libre.computer/left/left-uefi.img.xz" 6 | LEFT_SHA256SUM_URL="https://distro.libre.computer/left/SHA256SUMS" 7 | 8 | # board dev image param 9 | LEFT_flash(){ 10 | local dev=$1 11 | shift 12 | 13 | local dev_path=/dev/$dev 14 | 15 | local image=$1 16 | shift 17 | 18 | traps_start 19 | local left=$(mktemp) 20 | traps_push rm $left 21 | 22 | if ! BLOCK_DEV_isValid $dev; then 23 | echo "$FUNCNAME: DEVICE $dev is not a valid target." >&2 24 | return 1 25 | fi 26 | 27 | if BLOCK_DEV_isMounted $dev; then 28 | echo "$FUNCNAME: !!!ERROR!!! DEVICE $dev is mounted." >&2 29 | return 1 30 | fi 31 | 32 | if [ ! -w "$dev_path" ]; then 33 | echo "$FUNCNAME: DEVICE $dev is not writable by current user $USER." >&2 34 | return 1 35 | fi 36 | 37 | if [ ! -z "$image" ] && [ ! -f "$image" ]; then 38 | echo "$FUNCNAME: IMAGE $image does not exist." >&2 39 | return 1 40 | fi 41 | 42 | if ! WGET_getHeaders "$LEFT_URL" > $left; then 43 | if grep -io "HTTP/1.1\s404\sNot\sFound" $dist > /dev/null; then 44 | echo "$FUNCNAME: LEFT could not be found at $url." >&2 45 | else 46 | echo "$FUNCNAME: LEFT server could not be reached." >&2 47 | fi 48 | fi 49 | 50 | #local dist_size=$(grep -oi "Content-Length:\s\+[0-9]*" $dist | tail -n 1 | tr -s " " | cut -f 2 -d " ") 51 | #if [ "$dist_size" -lt $((100*1024*1024)) ]; then 52 | # echo "$FUNCNAME: DISTRO size is unexpectedly small." >&2 53 | # return 1 54 | #fi 55 | 56 | #TODO left checksum 57 | if ! DISTRO_get "$LEFT_URL" $left; then 58 | echo "$FUNCNAME: DISTRO could not be downloaded." >&2 59 | return 1 60 | fi 61 | 62 | #if [ $(stat -c %s $dist) -ne $dist_size ]; then 63 | # echo "$FUNCNAME: DISTRO does not match expected size." >&2 64 | # return 1 65 | #fi 66 | #local dist_size=$(stat -c %s $dist) 67 | 68 | if BLOCK_DEV_isMounted $dev; then 69 | echo "$FUNCNAME: !!!ERROR!!! DEVICE $dev is mounted." >&2 70 | return 1 71 | fi 72 | 73 | local left_flash_cmd="xz -cd $left | dd of=$dev_path bs=1M iflag=fullblock oflag=dsync conv=notrunc status=progress" 74 | 75 | if ! TOOLKIT_isInCaseInsensitive "force" "$@"; then 76 | echo "$FUNCNAME: $left_flash_cmd" >&2 77 | echo "$FUNCNAME: run the above command to flash the target device?" >&2 78 | if TOOLKIT_promptYesNo; then 79 | echo "$left_flash_cmd" 80 | else 81 | echo "$FUNCNAME: operation cancelled." >&2 82 | return 1 83 | fi 84 | fi 85 | 86 | local left_flash_bytes=$(eval "$left_flash_cmd 2>&1 | tee /dev/stderr | grep -oE '^[0-9]+ bytes' | tail -n 1 | cut -f 1 -d ' '") 87 | if [ $? -eq 0 ]; then 88 | [ "$dev" = "null" ] || sync $dev_path 89 | echo "$FUNCNAME: LEFT written to $dev successfully." >&2 90 | if [ -z "$left_flash_bytes" ]; then 91 | echo "$FUNCNAME: unable to determine decompressed size." >&2 92 | return 1 93 | elif TOOLKIT_isInCaseInsensitive "verify" "$@"; then 94 | if [ "$dev" = "null" ]; then 95 | echo "$FUNCNAME: null device cannot be verified." >&2 96 | return 1 97 | fi 98 | if cmp -n $left_flash_bytes <(xz -cd $left 2> /dev/null) $dev_path > /dev/null; then 99 | echo "$FUNCNAME: LEFT written to $dev verified." >&2 100 | else 101 | echo "$FUNCNAME: LEFT written to $dev failed verification!" >&2 102 | return 1 103 | fi 104 | fi 105 | # DOWNLOAD 106 | partprobe "$dev_path" 107 | local left_end=$(parted -m "$dev_path" unit s print | tail -n 1 | cut -f 3 -d : | grep -oE [0-9]+) 108 | local left_parted_cmd="parted $dev_path mkpart primary ext4 $((left_end+1))s 100%" 109 | echo "$FUNCNAME: $left_parted_cmd" >&2 110 | echo "$FUNCNAME: run the above command to partition the target device?" >&2 111 | if TOOLKIT_promptYesNo; then 112 | echo "$left_parted_cmd" 113 | eval "$left_parted_cmd" 114 | else 115 | echo "$FUNCNAME: operation cancelled." >&2 116 | return 1 117 | fi 118 | partprobe "$dev_path" 119 | local left_part_num=$(parted -m "$dev_path" unit s print | tail -n 1 | cut -f 1 -d :) 120 | local left_part_path="${dev_path}$(BLOCK_DEV_getPartPrefix $dev_path)${left_part_num}" 121 | mkfs.ext4 -F "$left_part_path" 122 | if [ ! -z "$image" ]; then 123 | local left_part_dir=$(mktemp -d) 124 | traps_push rmdir "$left_part_dir" 125 | mount "$left_part_path" "$left_part_dir" 126 | traps_push umount "$left_part_dir" 127 | local left_distro_filename="${image##*/}" 128 | dd if="$image" of="$left_part_dir/$left_distro_filename" bs=1M oflag=sync status=progress 129 | echo "IMAGE_FILE=$left_distro_filename" > "$left_part_dir/flash.ini" 130 | if [ ! -z "$LEFT_IMAGE_EXPAND" ]; then 131 | echo "IMAGE_EXPAND=1" >> "$left_part_dir/flash.ini" 132 | fi 133 | echo "$FUNCNAME: IMAGE written to $dev$(BLOCK_DEV_getPartPrefix $dev_path)$left_part_num successfully." >&2 134 | else 135 | echo "$FUNCNAME: LEFT setup on $dev$(BLOCK_DEV_getPartPrefix $dev_path)$left_part_num successfully." >&2 136 | fi 137 | traps_popUntilLength 0 138 | traps_stop 139 | else 140 | echo "$FUNCNAME: LEFT write to $dev failed!" >&2 141 | return 1 142 | fi 143 | } 144 | -------------------------------------------------------------------------------- /lib/toolkit.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # SPDX-License-Identifier: GPL-2.0 3 | # Copyright (C) 2022 Da Xue 4 | 5 | TOOLKIT_isIn(){ 6 | local search="$1" 7 | shift 8 | for param in "$@"; do 9 | if [ "$search" = "$param" ]; then 10 | return 0 11 | fi 12 | done 13 | return 1 14 | } 15 | TOOLKIT_isInCaseInsensitive(){ 16 | local search="$1" 17 | shift 18 | for param in "$@"; do 19 | if [ "${search,,}" = "${param,,}" ]; then 20 | return 0 21 | fi 22 | done 23 | return 1 24 | } 25 | TOOLKIT_promptYesNo(){ 26 | while true; do 27 | read -s -n 1 -p "(y/n)" confirm 28 | echo 29 | case "${confirm,,}" in 30 | y|yes) 31 | break 32 | ;; 33 | n|no) 34 | return 1 35 | ;; 36 | esac 37 | done 38 | } 39 | TOOLKIT_urlDecode(){ 40 | : "${*//+/ }" 41 | echo -en "${_//%/\\x}" 42 | } 43 | -------------------------------------------------------------------------------- /lib/traps.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # SPDX-License-Identifier: MIT 3 | # Copyright (C) 2022 Da Xue 4 | 5 | TRAPS_DEBUG=0 6 | TRAPS_SIGNAL=EXIT 7 | 8 | declare -a TRAPS_PARAMS 9 | declare -a TRAPS_LENGTHS 10 | TRAPS_LENGTH= 11 | 12 | function traps_start { 13 | if [ -z "$TRAPS_LENGTH" ]; then 14 | TRAPS_LENGTH=0 15 | if [ "$TRAPS_SIGNAL" = "ERR" ]; then 16 | trap 'traps_exit $LINENO ${#FUNCNAME[@]} "${BASH_SOURCE[@]}" "${BASH_LINENO[@]}" "${FUNCNAME[@]}"' $TRAPS_SIGNAL 17 | else 18 | trap traps_exit $TRAPS_SIGNAL 19 | fi 20 | else 21 | if [ "$TRAPS_DEBUG" -eq 1 ]; then 22 | echo "$FUNCNAME: duplicate call" >&2 23 | fi 24 | return 1 25 | fi 26 | } 27 | 28 | function traps_isEmpty { 29 | if [ -z "$TRAPS_LENGTH" ]; then 30 | if [ "$TRAPS_DEBUG" -eq 1 ]; then 31 | echo "$FUNCNAME: traps not started" >&2 32 | fi 33 | return 2 34 | fi 35 | if [ $TRAPS_LENGTH -gt 0 ]; then 36 | return 1 37 | fi 38 | return 0 39 | } 40 | 41 | function traps_push { 42 | if [ -z "$TRAPS_LENGTH" ]; then 43 | if [ "$TRAPS_DEBUG" -eq 1 ]; then 44 | echo "$FUNCNAME: traps not started" >&2 45 | fi 46 | return 1 47 | fi 48 | TRAPS_PARAMS+=("$@") 49 | TRAPS_LENGTHS+=($#) 50 | ((TRAPS_LENGTH=TRAPS_LENGTH+1)) 51 | } 52 | 53 | function traps_pop { 54 | if [ -z "$TRAPS_LENGTH" ]; then 55 | if [ "$TRAPS_DEBUG" -eq 1 ]; then 56 | echo "$FUNCNAME: traps not started" >&2 57 | fi 58 | return 1 59 | elif [ $TRAPS_LENGTH -gt 0 ]; then 60 | local trap_length=${TRAPS_LENGTHS[@]: -1} 61 | local trap_cmd=("${TRAPS_PARAMS[@]: -$trap_length}") 62 | local traps_params_end=$((${#TRAPS_PARAMS[@]}-trap_length)) 63 | TRAPS_PARAMS=("${TRAPS_PARAMS[@]:0:$traps_params_end}") 64 | TRAPS_LENGTH=$((TRAPS_LENGTH-1)) 65 | TRAPS_LENGTHS=(${TRAPS_LENGTHS[@]:0:$TRAPS_LENGTH}) 66 | if [ "$TRAPS_DEBUG" -eq 1 ]; then 67 | echo "$FUNCNAME: ${trap_cmd[@]}" >&2 68 | fi 69 | "${trap_cmd[@]}" 70 | else 71 | if [ "$TRAPS_DEBUG" -eq 1 ]; then 72 | echo "$FUNCNAME: traps empty" >&2 73 | fi 74 | return 1 75 | fi 76 | } 77 | 78 | function traps_popUntilLength { 79 | if [ -z "$TRAPS_LENGTH" ]; then 80 | if [ "$TRAPS_DEBUG" -eq 1 ]; then 81 | echo "$FUNCNAME: traps not started" >&2 82 | fi 83 | return 1 84 | elif [ "$1" -gt $TRAPS_LENGTH ]; then 85 | echo "$FUNCNAME: traps length below target" >&2 86 | return 1 87 | fi 88 | while [ "$TRAPS_LENGTH" -gt "$1" ]; do 89 | traps_pop 90 | done 91 | } 92 | 93 | function traps_drop { 94 | if [ -z "$TRAPS_LENGTH" ]; then 95 | if [ "$TRAPS_DEBUG" -eq 1 ]; then 96 | echo "$FUNCNAME: traps not started" >&2 97 | fi 98 | return 1 99 | elif [ $TRAPS_LENGTH -gt 0 ]; then 100 | local trap_length=${TRAPS_LENGTHS[@]: -1} 101 | local traps_params_end=$((${#TRAPS_PARAMS[@]}-trap_length)) 102 | TRAPS_PARAMS=("${TRAPS_PARAMS[@]:0:$traps_params_end}") 103 | TRAPS_LENGTH=$((TRAPS_LENGTH-1)) 104 | TRAPS_LENGTHS=(${TRAPS_LENGTHS[@]:0:$TRAPS_LENGTH}) 105 | else 106 | if [ "$TRAPS_DEBUG" -eq 1 ]; then 107 | echo "$FUNCNAME: traps empty" >&2 108 | fi 109 | return 1 110 | fi 111 | } 112 | 113 | 114 | function traps_stop { 115 | if [ "$TRAPS_LENGTH" = "0" ]; then 116 | TRAPS_LENGTH= 117 | trap - $TRAPS_SIGNAL 118 | else 119 | if [ "$TRAPS_DEBUG" -eq 1 ]; then 120 | echo "$FUNCNAME: registered traps" >&2 121 | traps_diag >&2 122 | fi 123 | return 1 124 | fi 125 | } 126 | 127 | function traps_cancel { 128 | if [ -z "$TRAPS_LENGTH" ]; then 129 | if [ "$TRAPS_DEBUG" -eq 1 ]; then 130 | echo "$FUNCNAME: traps not started" >&2 131 | fi 132 | return 1 133 | fi 134 | TRAPS_PARAMS=() 135 | TRAPS_LENGTHS=() 136 | TRAPS_LENGTH= 137 | trap - $TRAPS_SIGNAL 138 | } 139 | 140 | function traps_exit { 141 | if [ "$TRAPS_DEBUG" -eq 1 ] && [ "$TRAPS_SIGNAL" = "ERR" ]; then 142 | local IFS=' ' 143 | trap "traps_exit_now ${*@Q}" EXIT 144 | else 145 | trap - $TRAPS_SIGNAL 146 | fi 147 | if [ -z "$TRAPS_LENGTH" ]; then 148 | if [ "$TRAPS_DEBUG" -eq 1 ]; then 149 | echo "$FUNCNAME: traps not initiated" >&2 150 | fi 151 | return 1 152 | fi 153 | while [ "$TRAPS_LENGTH" -gt 0 ]; do 154 | traps_pop 155 | done 156 | return 1 157 | } 158 | 159 | function traps_exit_now { 160 | echo "$FUNCNAME: $TRAPS_SIGNAL in $3 line $1" >&2 161 | local i=1 162 | while [ $i -lt $2 ]; do 163 | local i_func=$(($2*2+$i+2)) 164 | local i_file=$(($i+3)) 165 | local i_line=$(($2+$i+2)) 166 | echo "$FUNCNAME: trace: ${@:$i_func:1} in ${@:$i_file:1} line ${@:$i_line:1}" >&2 167 | local i=$((i+1)) 168 | done 169 | } 170 | 171 | function traps_diag { 172 | echo "$FUNCNAME params: ${TRAPS_PARAMS[@]}" 173 | echo "$FUNCNAME lengths: ${TRAPS_LENGTHS[@]}" 174 | echo "$FUNCNAME length: $TRAPS_LENGTH" 175 | } 176 | -------------------------------------------------------------------------------- /lib/wget.sh: -------------------------------------------------------------------------------- 1 | WGET_getHeaders(){ 2 | wget -S --spider "$1" 2>&1 3 | } -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Libre Computer Flash Tool 2 | ## Objective 3 | Quickly deploy bootloaders and images to MicroSD or eMMC storage mediums destined for Libre Computer boards. 4 | 5 | ## Warning 6 | This tool writes to raw blocks to the target device. Precautions such as 7 | avoiding root target device have been implemented. However, it is impossible 8 | to account for all scenarios. Before this tool executes writes, it will 9 | display the write command to be executed. Please review it carefully before 10 | confirming the action. Some bootloaders will clobber the GPT entries at the 11 | beginning of the disk. Other bootloaders will write beyond the 1MB starting 12 | point for most partition tools. Make sure you know what you are doing! If the 13 | device you are trying to flash holds important data, back it up before using 14 | this tool! This is your first and only warning. 15 | 16 | ## How to Use 17 | ```bash 18 | git clone https://github.com/libre-computer-project/libretech-flash-tool.git 19 | cd libretech-flash-tool 20 | 21 | ./lft.sh board-list 22 | aml-s905x-cc-v2 23 | aml-s905x-cc 24 | all-h3-cc-h5 25 | all-h3-cc-h3 26 | aml-s805x-ac 27 | roc-rk3399-pc 28 | roc-rk3328-cc 29 | 30 | ./lft.sh dev-list 31 | sdb 32 | 33 | sudo ./lft.sh bl-flash aml-s905x-cc sdb 34 | BOOTLOADER_get: downloading aml-s905x-cc bootloader to /tmp/tmp.otrZBzPL4o. 35 | 36 | --2022-09-02 23:48:50-- https://boot.libre.computer/ci/aml-s905x-cc 37 | Resolving boot.libre.computer (boot.libre.computer)... 192.53.162.101, 2600:3c00::f03c:93ff:fea1:358c 38 | Connecting to boot.libre.computer (boot.libre.computer)|192.53.162.101|:443... connected. 39 | HTTP request sent, awaiting response... 200 OK 40 | Length: 851968 (832K) [application/octet-stream] 41 | Saving to: ‘/tmp/tmp.otrZBzPL4o’ 42 | 43 | /tmp/tmp.otrZBzPL4o 100%[=======================================================>] 832.00K 3.45MB/s in 0.2s 44 | 45 | 2022-09-02 23:48:50 (3.45 MB/s) - ‘/tmp/tmp.otrZBzPL4o’ saved [851968/851968] 46 | 47 | BOOTLOADER_get: downloaded aml-s905x-cc bootloader to /tmp/tmp.otrZBzPL4o. 48 | BOOTLOADER_flash: dd if=/tmp/tmp.otrZBzPL4o of=/dev/sdb oflag=sync bs=512 seek=1 status=progress 49 | BOOTLOADER_flash: run the above command to flash the target device? 50 | (y/n) 51 | dd if=/tmp/tmp.otrZBzPL4o of=/dev/sdb oflag=sync bs=512 seek=1 status=progress 52 | 815616 bytes (816 kB, 796 KiB) copied, 2 s, 407 kB/s 53 | 1664+0 records in 54 | 1664+0 records out 55 | 851968 bytes (852 kB, 832 KiB) copied, 2.09354 s, 407 kB/s 56 | BOOTLOADER_flash: bootloader written to sdb successfully. 57 | ``` -------------------------------------------------------------------------------- /vendor/allwinner/bin/sunxi-fel-x86_64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libre-computer-project/libretech-flash-tool/6290db4bc25f2398bd1c1f4bc66be301a20205c2/vendor/allwinner/bin/sunxi-fel-x86_64 -------------------------------------------------------------------------------- /vendor/rockchip/bin/rockusb-aarch64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libre-computer-project/libretech-flash-tool/6290db4bc25f2398bd1c1f4bc66be301a20205c2/vendor/rockchip/bin/rockusb-aarch64 -------------------------------------------------------------------------------- /vendor/rockchip/bin/rockusb-x86_64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libre-computer-project/libretech-flash-tool/6290db4bc25f2398bd1c1f4bc66be301a20205c2/vendor/rockchip/bin/rockusb-x86_64 --------------------------------------------------------------------------------