openwrtbuilder 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802
  1. #!/usr/bin/env bash
  2. # Builds and deploys OpenWRT images
  3. # Copyright 2022-24 Bryan C. Roessler
  4. # Apache 2.0 License
  5. # See README.md and ./profiles for device configuration
  6. # Set default release
  7. : "${RELEASE:="23.05.5"}"
  8. print_help() {
  9. debug "${FUNCNAME[0]}"
  10. cat <<-'EOF'
  11. Build and deploy OpenWRT images
  12. USAGE:
  13. openwrtbuilder [OPTION [VALUE]]... -p PROFILE [-p PROFILE]...
  14. OPTIONS
  15. --profile,-p PROFILE
  16. --release,-r,--version,-v RELEASE ("snapshot", "22.03.5")
  17. --buildroot,-b PATH
  18. Default: location of openwrtbuilder script
  19. --source
  20. Build image from source, not from Image Builder
  21. --ssh-upgrade HOST
  22. Examples: root@192.168.1.1, root@router.lan
  23. --ssh-backup SSH_PATH
  24. Enabled by default for --ssh-upgrade
  25. --flash,-f DEVICE
  26. Example: /dev/sdX
  27. --reset
  28. Cleanup all source and output files
  29. --yes,-y
  30. Assume yes for all questions (automatic mode)
  31. --verbose
  32. Make make or imagebuilder noisier
  33. --debug,-d
  34. --help,-h
  35. EXAMPLES
  36. ./openwrtbuilder -p r4s -r snapshot
  37. ./openwrtbuilder -p ax6000 -r 23.05.0-rc3 --source --debug
  38. ./openwrtbuilder -p rpi4 -r 22.03.3 --flash /dev/sdX
  39. ./openwrtbuilder -p linksys -r snapshot --ssh-upgrade root@192.168.1.1
  40. EOF
  41. }
  42. init() {
  43. debug "${FUNCNAME[0]}"
  44. declare -g ID RPM_MGR SCRIPT_DIR DL_TOOL
  45. ((DEBUG)) || echo "To enable debugging output, use --debug or -d"
  46. # Save the script directory
  47. # https://stackoverflow.com/a/4774063
  48. SCRIPT_DIR="$(cd -- "$(dirname "$0")" >/dev/null 2>&1 || exit $? ; pwd -P)"
  49. if [[ -e "/etc/os-release" ]]; then
  50. source "/etc/os-release"
  51. else
  52. echo "/etc/os-release not found"
  53. echo "Your OS is unsupported"
  54. print_help
  55. exit 1
  56. fi
  57. debug "Detected host platform: $ID"
  58. # normalize distro ID
  59. case "$ID" in
  60. debian|arch) ;;
  61. centos|fedora)
  62. if command -v dnf &>/dev/null; then
  63. RPM_MGR="dnf"
  64. elif command -v yum &>/dev/null; then
  65. RPM_MGR="yum"
  66. fi
  67. ;;
  68. rhel) ID="centos" ;;
  69. linuxmint|neon|*ubuntu*) ID="ubuntu" ;;
  70. *suse*) ID="suse" ;;
  71. raspbian) ID="debian" ;;
  72. *)
  73. echo "Autodetecting distro, this may be unreliable"
  74. if command -v dnf &>/dev/null; then
  75. ID="fedora"
  76. RPM_MGR="dnf"
  77. elif command -v yum &>/dev/null; then
  78. ID="centos"
  79. RPM_MGR="yum"
  80. elif command -v apt &>/dev/null; then
  81. ID="ubuntu"
  82. elif command -v pacman &>/dev/null; then
  83. ID="arch"
  84. else
  85. return 1
  86. fi
  87. ;;
  88. esac
  89. debug "Using host platform: $ID"
  90. # Set distro-specific functions
  91. case "$ID" in
  92. fedora|centos) pkg_install(){ sudo "$RPM_MGR" install -y "$@"; } ;;
  93. debian|ubuntu) pkg_install(){ sudo apt-get install --ignore-missing -y -q0 "$@"; } ;;
  94. suse) pkg_install(){ sudo zypper --non-interactive -q install --force --no-confirm "$@"; } ;;
  95. arch) pkg_install(){ sudo pacman -S --noconfirm --needed "$@"; } ;;
  96. esac
  97. if command -v axel &>/dev/null; then
  98. DL_TOOL="axel"
  99. elif command -v curl &>/dev/null; then
  100. DL_TOOL="curl"
  101. else
  102. echo "Downloading the Image Builder requires axel or curl"
  103. return 1
  104. fi
  105. }
  106. parse_input() {
  107. debug "${FUNCNAME[0]}" "$*"
  108. declare -ga PROFILES
  109. declare -g RESET=0 FROM_SOURCE=0 YES=0 VERBOSE=0 DEBUG=0
  110. declare -g USER_RELEASE SSH_UPGRADE_PATH SSH_BACKUP_PATH FLASH_DEV
  111. local long_opts='release:,version:,profile:,buildroot:,source,'
  112. long_opts+='ssh-upgrade:,ssh-backup:,flash:,reset,yes,verbose,debug,help'
  113. if _input=$(getopt -o +r:v:p:b:sf:ydh -l $long_opts -- "$@"); then
  114. eval set -- "$_input"
  115. while true; do
  116. case "$1" in
  117. --release|-r|--version|-v) shift; USER_RELEASE="$1" ;;
  118. --profile|-p) shift; PROFILES+=("$1") ;;
  119. --buildroot|-b) shift; BUILD_ROOT="$1" ;;
  120. --source|-s) FROM_SOURCE=1 ;;
  121. --ssh-upgrade) shift; SSH_UPGRADE_PATH="$1" ;;
  122. --ssh-backup) shift; SSH_BACKUP_PATH="$1" ;;
  123. --flash|-f) shift; FLASH_DEV="$1" ;;
  124. --reset) RESET=1 ;;
  125. --yes|-y) YES=1 ;;
  126. --verbose) VERBOSE=1 ;;
  127. --debug|-d) echo "Debugging on"; DEBUG=1 ;;
  128. --help|-h) print_help; exit 0 ;;
  129. --)
  130. shift
  131. break
  132. ;;
  133. esac
  134. shift
  135. done
  136. else
  137. echo "Incorrect options provided"
  138. print_help; exit 1
  139. fi
  140. }
  141. install_dependencies() {
  142. debug "${FUNCNAME[0]}"
  143. local -a pkg_list
  144. local lock_file
  145. if ((FROM_SOURCE)); then
  146. lock_file="$BUILD_ROOT/.dependencies_source"
  147. else
  148. lock_file="$BUILD_ROOT/.dependencies_ib"
  149. fi
  150. if [[ ! -f $lock_file ]]; then
  151. if ((FROM_SOURCE)); then
  152. # For building from source code
  153. # https://openwrt.org/docs/guide-developer/toolchain/install-buildsystem
  154. case "$ID" in
  155. fedora|centos)
  156. pkg_list+=(
  157. bash-completion
  158. bzip2
  159. gcc
  160. gcc-c++
  161. git
  162. make
  163. ncurses-devel
  164. patch
  165. rsync
  166. tar
  167. unzip
  168. wget
  169. which
  170. diffutils
  171. python3
  172. python3-devel
  173. python3-setuptools
  174. python3-pyelftools
  175. perl-base
  176. perl-Data-Dumper
  177. perl-File-Compare
  178. perl-File-Copy
  179. perl-FindBin
  180. perl-IPC-Cmd
  181. perl-Thread-Queue
  182. perl-Time-Piece
  183. perl-JSON-PP
  184. swig
  185. clang # for qosify
  186. llvm15-libs
  187. patch)
  188. ;;
  189. debian|ubuntu)
  190. pkg_list+=(
  191. build-essential
  192. clang
  193. flex
  194. g++
  195. gawk
  196. gcc-multilib
  197. gettext
  198. git
  199. libncurses5-dev
  200. libssl-dev
  201. python3-distutils
  202. rsync
  203. unzip
  204. zlib1g-dev
  205. file
  206. wget
  207. patch)
  208. ;;
  209. arch)
  210. pkg_list+=(
  211. base-devel
  212. autoconf
  213. automake
  214. bash
  215. binutils
  216. bison
  217. bzip2
  218. clang
  219. fakeroot
  220. file
  221. findutils
  222. flex
  223. gawk
  224. gcc
  225. gettext
  226. git
  227. grep
  228. groff
  229. gzip
  230. libelf
  231. libtool
  232. libxslt
  233. m4
  234. make
  235. ncurses
  236. openssl
  237. patch
  238. pkgconf
  239. python
  240. rsync
  241. sed
  242. texinfo
  243. time
  244. unzip
  245. util-linux
  246. wget
  247. which
  248. zlib
  249. patch)
  250. ;;
  251. *)
  252. debug "Skipping dependency install, your OS is unsupported"
  253. return 1
  254. ;;
  255. esac
  256. else
  257. # For Imagebuilder
  258. case "$ID" in
  259. fedora|centos)
  260. pkg_list+=(
  261. @c-development
  262. @development-tools
  263. @development-libs
  264. perl-FindBin
  265. zlib-static
  266. elfutils-libelf-devel
  267. gawk
  268. unzip
  269. file
  270. wget
  271. python3
  272. python2
  273. axel
  274. perl-IPC-Cmd)
  275. ;;
  276. debian|ubuntu)
  277. pkg_list+=(
  278. build-essential
  279. libncurses5-dev
  280. libncursesw5-dev
  281. zlib1g-dev
  282. gawk
  283. git
  284. gettext
  285. libssl-dev
  286. xsltproc
  287. wget
  288. unzip
  289. python
  290. axel)
  291. ;;
  292. *)
  293. debug "Skipping dependency install, your OS is unsupported"
  294. return 1
  295. ;;
  296. esac
  297. fi
  298. pkg_install "${pkg_list[@]}" && echo "${pkg_list[@]}" > "$lock_file"
  299. fi
  300. }
  301. get_imagebuilder() {
  302. debug "${FUNCNAME[0]}" "$*"
  303. local -a url_file_pairs=("$@")
  304. for ((i=0; i<${#url_file_pairs[@]}; i+=2)); do
  305. local url="${url_file_pairs[i]}"
  306. local file="${url_file_pairs[i+1]}"
  307. # Check if file exists and ask user to remove and redownload
  308. if [[ -f $file ]] && ! ask_ok "Use existing $file?"; then
  309. execute rm -f "$file"
  310. fi
  311. # Download the file if it doesn't exist
  312. if [[ ! -f "$file" ]]; then
  313. echo "Downloading $url to $file using $DL_TOOL"
  314. execute "$DL_TOOL" "-o" "$file" "$url"
  315. fi
  316. done
  317. }
  318. add_repos() {
  319. debug "${FUNCNAME[0]}"
  320. if [[ -v P_ARR[repo] ]]; then
  321. if ! grep -q "${P_ARR[repo]}" "$BUILD_DIR/repositories.conf"; then
  322. echo "${P_ARR[repo]}" >> "$BUILD_DIR/repositories.conf"
  323. fi
  324. sed -i '/option check_signature/d' "$BUILD_DIR/repositories.conf"
  325. fi
  326. }
  327. ssh_backup() {
  328. debug "${FUNCNAME[0]}"
  329. local date hostname backup_fname
  330. printf -v date '%(%Y-%m-%d-%H-%M-%S)T'
  331. hostname=$(ssh -qt "$SSH_BACKUP_PATH" echo -n \$HOSTNAME)
  332. backup_fname="backup-$hostname-$date.tar.gz"
  333. [[ -d "$FILES_DIR" ]] || execute mkdir -p "$FILES_DIR"
  334. # Make backup archive on remote
  335. if ! execute "ssh -t $SSH_BACKUP_PATH sysupgrade -b /tmp/$backup_fname"; then
  336. echo "SSH backup failed"
  337. exit 1
  338. fi
  339. # Move backup archive locally
  340. if ! execute "rsync -avz --remove-source-files $SSH_BACKUP_PATH:/tmp/$backup_fname $BUILD_DIR/"; then
  341. echo "Could not copy SSH backup"
  342. exit 1
  343. fi
  344. # Extract backup archive
  345. if ! execute "tar -C $FILES_DIR -xzf $BUILD_DIR/$backup_fname"; then
  346. echo "Could not extract SSH backup"
  347. exit 1
  348. fi
  349. execute "rm $BUILD_DIR/$backup_fname"
  350. }
  351. make_images() {
  352. debug "${FUNCNAME[0]}"
  353. local -a make_opts
  354. # Reuse the existing output
  355. # if [[ -d "$BIN_DIR" ]]; then
  356. # if ask_ok "$BIN_DIR exists. Rebuild?"; then
  357. # execute rm -rf "$BIN_DIR"
  358. # else
  359. # return 0
  360. # fi
  361. # fi
  362. ((VERBOSE)) && make_opts+=("V=s")
  363. debug make "${make_opts[@]}" image BIN_DIR="$BIN_DIR" \
  364. PROFILE="$DEVICE" PACKAGES="$PACKAGES" \
  365. FILES="$FILES_DIR" --directory="$BUILD_DIR" \
  366. --jobs="$(($(nproc) - 1))"
  367. make "${make_opts[@]}" image \
  368. BIN_DIR="$BINDIR" \
  369. PROFILE="$DEVICE" \
  370. PACKAGES="$PACKAGES" \
  371. FILES="$FILES_DIR" \
  372. --directory="$BUILD_DIR" \
  373. --jobs="$(($(nproc) - 1))" \
  374. > "$BUILD_DIR/make.log"
  375. }
  376. flash_images() {
  377. debug "${FUNCNAME[0]}"
  378. local img_gz="$1"
  379. local dev="$2"
  380. local img="${img_gz%.gz}"
  381. local partitions
  382. if [[ ! -e "$dev" ]]; then
  383. echo "The device specified by --flash could not be found"
  384. return 1
  385. fi
  386. [[ -f $img_gz ]] || { echo "$img_gz does not exist"; return 1; }
  387. execute gunzip -qfk "$img_gz"
  388. echo "Unmounting target device $dev partitions"
  389. partitions=("$dev"?*)
  390. execute sudo umount "${partitions[@]}"
  391. if execute sudo dd if="$img" of="$dev" bs=2M conv=fsync; then
  392. sync
  393. echo "Image flashed successfully!"
  394. else
  395. echo "dd failed!"
  396. return 1
  397. fi
  398. }
  399. ssh_upgrade() {
  400. debug "${FUNCNAME[0]}"
  401. local img_gz="$1"
  402. local ssh_path="$2"
  403. local img_fname="${img_gz##*/}"
  404. [[ -f $img_gz ]] || { echo "$img_gz is missing, check build output"; return 1; }
  405. echo "Copying '$img_gz' to $ssh_path/tmp/$img_fname"
  406. if ! execute scp "$img_gz" "$ssh_path:/tmp/$img_fname"; then
  407. echo "Could not copy $img_gz to $ssh_path:/tmp/$img_fname"
  408. return 1
  409. fi
  410. echo "Executing remote sysupgrade"
  411. # This may result in weird exit code from closing the ssh connection
  412. # shellcheck disable=SC2029
  413. ssh "$ssh_path" "sysupgrade -F /tmp/$img_fname"
  414. return 0
  415. }
  416. from_source() {
  417. debug "${FUNCNAME[0]}" "$*"
  418. local seed_url="$1"
  419. local src_url="https://github.com/openwrt/openwrt.git"
  420. local seed_file="$WORKTREE_DIR/.config"
  421. local pkg config commit seed_file wt_commit description
  422. local -a make_opts config_opts
  423. echo "Building from source is under development"
  424. # Update source code
  425. if [[ ! -d "$SRC_DIR" ]]; then
  426. execute mkdir -p "$SRC_DIR"
  427. execute git clone "$src_url" "$SRC_DIR"
  428. fi
  429. git -C "$SRC_DIR" pull
  430. # Generate commitish for git worktree
  431. case "$RELEASE" in
  432. snapshot) wt_commit="origin/main" ;;
  433. [0-9][0-9].[0-9][0-9].*)
  434. local branch="openwrt-${RELEASE%.*}"
  435. local tag="v$RELEASE"
  436. if ask_ok "Use $branch branch HEAD (y, recommended) or $tag tag (N)?"; then
  437. wt_commit="origin/$branch"
  438. else
  439. wt_commit="$tag"
  440. fi
  441. ;;
  442. *)
  443. debug "Passing '$RELEASE' commit-ish to git worktree"
  444. wt_commit="$RELEASE"
  445. ;;
  446. esac
  447. # TODO There's a bug in the make clean functions that seem to invoke a full make
  448. if [[ -d "$WORKTREE_DIR" ]]; then
  449. execute git -C "$WORKTREE_DIR" checkout "$wt_commit"
  450. execute git -C "$WORKTREE_DIR" pull
  451. else
  452. execute git -C "$SRC_DIR" worktree add --force --detach "$WORKTREE_DIR" "$wt_commit"
  453. fi
  454. # To workaround bug, don't use make *clean, blow it away and start fresh
  455. # [[ -d "$WORKTREE_DIR" ]] && execute rm -rf "$WORKTREE_DIR"
  456. # execute git -C "$SRC_DIR" worktree add --force --detach "$WORKTREE_DIR" "$wt_commit"
  457. # Print commit information
  458. commit=$(git -C "$WORKTREE_DIR" rev-parse HEAD)
  459. description=$(git -C "$WORKTREE_DIR" describe)
  460. echo "Current commit hash: $commit"
  461. echo "Git worktree description: $description"
  462. ((DEBUG)) && git --no-pager -C "$WORKTREE_DIR" log -1
  463. # Enter worktree
  464. execute pushd "$WORKTREE_DIR" || return 1
  465. # Update package feed
  466. ./scripts/feeds update -i -f &&
  467. ./scripts/feeds update -a -f &&
  468. ./scripts/feeds install -a -f
  469. # Grab the release seed config
  470. if ! execute "$DL_TOOL" "-o" "$seed_file" "$seed_url"; then
  471. echo "Could not obtain $seed_file from $seed_url"
  472. return 1
  473. fi
  474. # Set compilation output dir
  475. config_opts+=("CONFIG_BINARY_FOLDER=\"$BIN_DIR\"")
  476. # Add custom packages
  477. for pkg in $PACKAGES; do
  478. if [[ $pkg == -* ]]; then
  479. config_opts+=("CONFIG_PACKAGE_${pkg#-}=n") # remove package
  480. else
  481. config_opts+=("CONFIG_PACKAGE_$pkg=y") # add package
  482. fi
  483. done
  484. # Add config options from profile
  485. for config in ${P_ARR[config]}; do
  486. config_opts+=("$config")
  487. done
  488. # Only compile selected fs
  489. execute sed -i '/CONFIG_TARGET_ROOTFS_/d' "$seed_file"
  490. config_opts+=("CONFIG_TARGET_PER_DEVICE_ROOTFS=n")
  491. if [[ $FILESYSTEM == "squashfs" ]]; then
  492. config_opts+=("CONFIG_TARGET_ROOTFS_EXT4FS=n")
  493. config_opts+=("CONFIG_TARGET_ROOTFS_SQUASHFS=y")
  494. elif [[ $FILESYSTEM == "ext4" ]]; then
  495. config_opts+=("CONFIG_TARGET_ROOTFS_SQUASHFS=n")
  496. config_opts+=("CONFIG_TARGET_ROOTFS_EXT4FS=y")
  497. fi
  498. # Only compile selected target image
  499. execute sed -i '/CONFIG_TARGET_DEVICE_/d' "$seed_file"
  500. config_opts+=("CONFIG_TARGET_MULTI_PROFILE=n")
  501. config_opts+=("CONFIG_TARGET_PROFILE=DEVICE_$DEVICE")
  502. config_opts+=("CONFIG_TARGET_${TARGET//\//_}_DEVICE_$DEVICE=y")
  503. config_opts+=("CONFIG_SDK=n")
  504. config_opts+=("CONFIG_SDK_LLVM_BPF=n")
  505. config_opts+=("CONFIG_IB=n")
  506. config_opts+=("CONFIG_MAKE_TOOLCHAIN=n")
  507. # Write options to config seed file
  508. for config in "${config_opts[@]}"; do
  509. debug "Writing $config to $seed_file"
  510. echo "$config" >> "$seed_file"
  511. done
  512. # Cleaning modes
  513. # make clean # compiled output
  514. # make targetclean # compiled output, toolchain
  515. # make dirclean # compiled output, toolchain, build tools
  516. # make distclean # compiled output, toolchain, build tools, .config, feeds, .ccache
  517. # Make prep
  518. ((VERBOSE)) && make_opts+=("V=s")
  519. execute make "${make_opts[@]}" "-j1" distclean # TODO 'dirclean' has a bug that triggers menuconfig
  520. execute make "${make_opts[@]}" "-j1" defconfig
  521. execute make "${make_opts[@]}" "-j1" download
  522. # ((DEBUG)) && make_opts+=("-j1") || make_opts+=("-j$(($(nproc)+1))")
  523. make_opts+=("-j$(($(nproc)+1))")
  524. # Make image
  525. if ! execute ionice -c 3 chrt --idle 0 nice -n19 make "${make_opts[@]}" world; then
  526. echo "Error: make failed"
  527. return 1
  528. fi
  529. popd || return 1
  530. # Symlink output images to root of BIN_DIR (match Image Builder)
  531. shopt -s nullglob
  532. for image in "$BIN_DIR/targets/${TARGET}/"*.{img,img.gz,ubi}; do
  533. execute ln -fs "$image" "$BIN_DIR/${image##*/}"
  534. done
  535. shopt -u nullglob
  536. return 0
  537. }
  538. # Generic helpers
  539. debug() { ((DEBUG)) && echo "Debug: $*"; }
  540. ask_ok() {
  541. ((YES)) && return
  542. local r
  543. read -r -p "$* [y/N]: " r
  544. r=${r,,}
  545. [[ "$r" =~ ^(yes|y)$ ]]
  546. }
  547. extract() {
  548. debug "${FUNCNAME[0]}" "$*"
  549. local archive="$1"
  550. local out_dir="$2"
  551. if ! execute tar -axf "$archive" -C "$out_dir" --strip-components 1; then
  552. echo "Extraction failed"
  553. return 1
  554. fi
  555. }
  556. backup_image() {
  557. debug "${FUNCNAME[0]} $*"
  558. local file="$1" dir="$2" count=1
  559. [[ -f $file ]] || return 1
  560. local creation_date
  561. creation_date=$(stat -c %w "$file" 2>/dev/null || stat -c %y "$file" | cut -d' ' -f1)
  562. creation_date=${creation_date:-unknown} # Default to "unknown" if no creation date
  563. [[ "$creation_date" == "-" ]] && creation_date="unknown"
  564. local base_name; base_name=$(basename "$file")
  565. execute mkdir -p "$dir"
  566. while [[ -e "$dir/$creation_date-$base_name.bk.$count" ]]; do ((count++)); done
  567. execute mv "$file" "$dir/$creation_date-$base_name.bk.$count"
  568. }
  569. verify() {
  570. debug "${FUNCNAME[0]}" "$*"
  571. local file_to_check="$1"
  572. local sumfile="$2"
  573. local checksum
  574. command -v sha256sum &>/dev/null || return 1
  575. [[ -f $sumfile && -f $file_to_check ]] || return 1
  576. checksum=$(grep "${file_to_check##*/}" "$sumfile" | cut -f1 -d' ')
  577. echo -n "$checksum $file_to_check" | sha256sum --check --status
  578. }
  579. load() {
  580. debug "${FUNCNAME[0]}" "$*"
  581. local source_file="$1"
  582. # shellcheck disable=SC1090
  583. [[ -f $source_file ]] && source "$source_file"
  584. }
  585. execute() {
  586. if debug "$*"; then
  587. "$@"
  588. else
  589. "$@" &>/dev/null
  590. fi
  591. }
  592. main() {
  593. debug "${FUNCNAME[0]}"
  594. init
  595. load "$SCRIPT_DIR/profiles"
  596. parse_input "$@"
  597. # Fallback to SCRIPT_DIR if BUILD_ROOT has not been set
  598. declare -g BUILD_ROOT="${BUILD_ROOT:=$SCRIPT_DIR}"
  599. declare -g FILES_DIR="${FILES_DIR:=$BUILD_ROOT/src/files}"
  600. declare -g BACKUP_DIR="$SCRIPT_DIR/backups"
  601. # This could be dangerous
  602. if [[ $BUILD_ROOT == "/" ]]; then
  603. echo "Invalid --buildroot"
  604. exit 1
  605. fi
  606. for dir in "$BUILD_ROOT/src" "$BUILD_ROOT/bin"; do
  607. [[ -d "$dir" ]] || execute mkdir -p "$dir"
  608. done
  609. # Allow --reset without a profile
  610. if ((RESET)) && [[ ${#PROFILES} -lt 1 ]]; then
  611. for d in "$BUILD_ROOT/src" "$BUILD_ROOT/bin"; do
  612. ask_ok "Remove $d?" && execute rm -rf "$d"
  613. done
  614. exit $?
  615. fi
  616. install_dependencies
  617. for profile in "${PROFILES[@]}"; do
  618. debug "Running profile: $profile"
  619. if [[ ! ${!profile@a} = A ]]; then
  620. echo "Profile '$profile' does not exist"
  621. return 1
  622. fi
  623. # Store profile in P_ARR nameref
  624. declare -gn P_ARR="$profile"
  625. declare -g FILESYSTEM="${P_ARR[filesystem]:="squashfs"}"
  626. declare -g TARGET="${P_ARR[target]}"
  627. declare -g DEVICE="${P_ARR[device]}"
  628. declare -g PACKAGES="${P_ARR[packages]:-}"
  629. declare -g RELEASE="${USER_RELEASE:=${P_ARR[release]:=$RELEASE}}"
  630. # normalize RELEASE
  631. case "$RELEASE" in
  632. snapshot|latest|main|master) RELEASE="snapshot" ;;
  633. v[0-9][0-9].[0-9][0-9].*) RELEASE="${RELEASE#v}" ;;
  634. [0-9][0-9].[0-9][0-9].*) ;;
  635. *)
  636. if ! ((FROM_SOURCE)); then
  637. echo "Error: Invalid release version format"
  638. echo "Use semantic version, tag, or 'snapshot'"
  639. exit 1
  640. fi
  641. ;;
  642. esac
  643. declare -g SRC_DIR="$BUILD_ROOT/src/openwrt"
  644. declare -g WORKTREE_DIR="$BUILD_ROOT/src/$profile/$RELEASE-src"
  645. declare -g BUILD_DIR="$BUILD_ROOT/src/$profile/$RELEASE"
  646. declare -g BIN_DIR="$BUILD_ROOT/bin/$profile/$RELEASE"
  647. if [[ "$RELEASE" == "snapshot" ]]; then
  648. local url_prefix="https://downloads.openwrt.org/snapshots/targets/$TARGET"
  649. local url_filename="openwrt-imagebuilder-${TARGET//\//-}.Linux-x86_64.tar.zst"
  650. local img_fname="openwrt-${TARGET//\//-}-$DEVICE-$FILESYSTEM"
  651. else
  652. local url_prefix="https://downloads.openwrt.org/releases/$RELEASE/targets/$TARGET"
  653. local url_filename="openwrt-imagebuilder-$RELEASE-${TARGET//\//-}.Linux-x86_64.tar.xz"
  654. local img_fname="openwrt-$RELEASE-${TARGET//\//-}-$DEVICE-$FILESYSTEM"
  655. fi
  656. local ib_url="$url_prefix/$url_filename"
  657. local ib_file="$BUILD_DIR/$url_filename"
  658. local ib_sha256_url="$url_prefix/sha256sums"
  659. local ib_sha256_file="$BUILD_DIR/sha256sums"
  660. local seed_url="$url_prefix/config.buildinfo"
  661. if ((FROM_SOURCE)); then
  662. declare -g SYSUPGRADEIMGGZ="$BIN_DIR/targets/$img_fname-sysupgrade.img.gz"
  663. else
  664. declare -g SYSUPGRADEIMGGZ="$BUILD_DIR/$img_fname-sysupgrade.img.gz"
  665. fi
  666. backup_image "$SYSUPGRADEIMGGZ" "$BACKUP_DIR/$profile/$RELEASE"
  667. if ((RESET)); then
  668. if ((FROM_SOURCE)); then
  669. [[ -d $WORKTREE_DIR ]] && ask_ok "Remove $WORKTREE_DIR?"
  670. execute git worktree remove --force "$WORKTREE_DIR"
  671. execute rm -rf "$WORKTREE_DIR"
  672. elif [[ -d $BUILD_DIR ]] && ask_ok "Remove $BUILD_DIR?"; then
  673. execute rm -rf "$BUILD_DIR"
  674. fi
  675. fi
  676. if ((DEBUG)); then
  677. echo "Profile settings:"
  678. for x in "${!P_ARR[@]}"; do printf "%s=%s\n" "$x" "${P_ARR[$x]}"; done
  679. echo "Environment variables:"
  680. declare -p
  681. fi
  682. if ((FROM_SOURCE)); then
  683. from_source "$seed_url" || return $?
  684. else
  685. [[ -d $BUILD_DIR ]] || mkdir -p "$BUILD_DIR"
  686. get_imagebuilder "$ib_url" "$ib_file" "$ib_sha256_url" "$ib_sha256_file" &&
  687. verify "$ib_file" "$ib_sha256_file" &&
  688. extract "$ib_file" "$BUILD_DIR" || return $?
  689. add_repos
  690. make_images
  691. # Verify output image for stock builds (in testing)
  692. if [[ ! -v P_ARR[packages] || -z ${P_ARR[packages]} ]]; then
  693. shopt -s nullglob
  694. local -a outfiles=("$BIN_DIR"/*.img.gz "$BIN_DIR"/*.img)
  695. shopt -u nullglob
  696. for outfile in "${outfiles[@]}"; do
  697. verify "$outfile" "$ib_sha256_file" || return 1
  698. done
  699. fi
  700. #copyFiles
  701. fi
  702. [[ -v SSH_BACKUP_PATH ]] && ssh_backup
  703. [[ -v SSH_UPGRADE_PATH ]] && ssh_upgrade "$SYSUPGRADEIMGGZ" "$SSH_UPGRADE_PATH"
  704. [[ -v FLASH_DEV ]] && flash_images "$SYSUPGRADEIMGGZ" "$FLASH_DEV"
  705. done
  706. }
  707. main "$@"
  708. exit