openwrtbuilder 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813
  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:="24.10.0-rc5"}"
  8. # @internal
  9. print_help() {
  10. debug "${FUNCNAME[0]}"
  11. cat <<-'EOF'
  12. Build and deploy OpenWRT images
  13. USAGE:
  14. openwrtbuilder [OPTION [VALUE]]... -p PROFILE [-p PROFILE]...
  15. OPTIONS
  16. --profile,-p PROFILE
  17. --release,-r,--version,-v RELEASE ("snapshot", "22.03.5")
  18. --buildroot,-b PATH
  19. Default: location of openwrtbuilder script
  20. --source
  21. Build image from source, not from Image Builder
  22. --ssh-upgrade HOST
  23. Examples: root@192.168.1.1, root@router.lan
  24. --ssh-backup SSH_PATH
  25. Enabled by default for --ssh-upgrade
  26. --flash,-f DEVICE
  27. Example: /dev/sdX
  28. --reset
  29. Cleanup all source and output files
  30. --yes,-y
  31. Assume yes for all questions (automatic mode)
  32. --verbose
  33. Make make or imagebuilder noisier
  34. --debug,-d
  35. --help,-h
  36. EXAMPLES
  37. ./openwrtbuilder -p r4s -r snapshot
  38. ./openwrtbuilder -p ax6000 -r 23.05.0-rc3 --source --debug
  39. ./openwrtbuilder -p rpi4 -r 22.03.3 --flash /dev/sdX
  40. ./openwrtbuilder -p linksys -r snapshot --ssh-upgrade root@192.168.1.1
  41. EOF
  42. }
  43. # @internal
  44. init() {
  45. debug "${FUNCNAME[0]}"
  46. declare -g ID RPM_MGR SCRIPT_DIR DL_TOOL
  47. ((DEBUG)) || echo "To enable debugging output, use --debug or -d"
  48. # Save the script directory
  49. # https://stackoverflow.com/a/4774063
  50. SCRIPT_DIR="$(cd -- "$(dirname "$0")" >/dev/null 2>&1 || exit $? ; pwd -P)"
  51. if [[ -e "/etc/os-release" ]]; then
  52. source "/etc/os-release"
  53. else
  54. echo "/etc/os-release not found"
  55. echo "Your OS is unsupported"
  56. print_help
  57. exit 1
  58. fi
  59. debug "Detected host platform: $ID"
  60. # normalize distro ID
  61. case "$ID" in
  62. debian|arch) ;;
  63. centos|fedora)
  64. if command -v dnf &>/dev/null; then
  65. RPM_MGR="dnf"
  66. elif command -v yum &>/dev/null; then
  67. RPM_MGR="yum"
  68. fi
  69. ;;
  70. rhel) ID="centos" ;;
  71. linuxmint|neon|*ubuntu*) ID="ubuntu" ;;
  72. *suse*) ID="suse" ;;
  73. raspbian) ID="debian" ;;
  74. *)
  75. echo "Autodetecting distro, this may be unreliable"
  76. for cmd in dnf yum apt pacman; do
  77. if command -v "$cmd" &>/dev/null; then
  78. case "$cmd" in
  79. dnf) ID="fedora"; RPM_MGR="dnf" ;;
  80. yum) ID="centos"; RPM_MGR="yum" ;;
  81. apt) ID="ubuntu" ;;
  82. pacman) ID="arch" ;;
  83. esac
  84. break
  85. fi
  86. done
  87. [[ -z $ID ]] && return 1
  88. ;;
  89. esac
  90. debug "Using host platform: $ID"
  91. # Set distro-specific functions
  92. case "$ID" in
  93. fedora|centos) pkg_install(){ sudo "$RPM_MGR" install -y "$@"; } ;;
  94. debian|ubuntu) pkg_install(){ sudo apt-get install --ignore-missing -y -q0 "$@"; } ;;
  95. suse) pkg_install(){ sudo zypper --non-interactive -q install --force --no-confirm "$@"; } ;;
  96. arch) pkg_install(){ sudo pacman -S --noconfirm --needed "$@"; } ;;
  97. esac
  98. if command -v axel &>/dev/null; then
  99. DL_TOOL="axel"
  100. elif command -v curl &>/dev/null; then
  101. DL_TOOL="curl"
  102. else
  103. echo "Downloading the Image Builder requires axel or curl"
  104. return 1
  105. fi
  106. }
  107. # @description Arguments
  108. parse_input() {
  109. debug "${FUNCNAME[0]}" "$*"
  110. declare -ga PROFILES
  111. declare -g RESET=0 FROM_SOURCE=0 YES=0 VERBOSE=0 DEBUG=0
  112. declare -g USER_RELEASE SSH_UPGRADE_PATH SSH_BACKUP_PATH FLASH_DEV
  113. local long_opts='release:,version:,profile:,buildroot:,source,'
  114. long_opts+='ssh-upgrade:,ssh-backup:,flash:,reset,yes,verbose,debug,help'
  115. if _input=$(getopt -o +r:v:p:b:sf:ydh -l $long_opts -- "$@"); then
  116. eval set -- "$_input"
  117. while true; do
  118. case "$1" in
  119. --release|-r|--version|-v) shift; USER_RELEASE="$1" ;;
  120. --profile|-p) shift; PROFILES+=("$1") ;;
  121. --buildroot|-b) shift; BUILD_ROOT="$1" ;;
  122. --source|-s) FROM_SOURCE=1 ;;
  123. --ssh-upgrade) shift; SSH_UPGRADE_PATH="$1" ;;
  124. --ssh-backup) shift; SSH_BACKUP_PATH="$1" ;;
  125. --flash|-f) shift; FLASH_DEV="$1" ;;
  126. --reset) RESET=1 ;;
  127. --yes|-y) YES=1 ;;
  128. --verbose) VERBOSE=1 ;;
  129. --debug|-d) echo "Debugging on"; DEBUG=1 ;;
  130. --help|-h) print_help; exit 0 ;;
  131. --) shift; break ;;
  132. esac
  133. shift
  134. done
  135. else
  136. echo "Incorrect options provided"
  137. print_help; exit 1
  138. fi
  139. }
  140. # @description Install build dependencies on major distros
  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. axel
  273. perl-IPC-Cmd
  274. zstd) ;;
  275. debian|ubuntu)
  276. pkg_list+=(
  277. build-essential
  278. libncurses5-dev
  279. libncursesw5-dev
  280. zlib1g-dev
  281. gawk
  282. git
  283. gettext
  284. libssl-dev
  285. xsltproc
  286. wget
  287. unzip
  288. python
  289. axel
  290. zstd) ;;
  291. *) debug "Unsupported OS for automatic dependency install"; return 1 ;;
  292. esac
  293. fi
  294. pkg_install "${pkg_list[@]}" && echo "${pkg_list[@]}" > "$lock_file"
  295. fi
  296. }
  297. # @description Acquires the OpenWRT Image Builder
  298. get_imagebuilder() {
  299. debug "${FUNCNAME[0]}" "$*"
  300. local -a url_file_pairs=("$@")
  301. for ((i=0; i<${#url_file_pairs[@]}; i+=2)); do
  302. local url="${url_file_pairs[i]}"
  303. local file="${url_file_pairs[i+1]}"
  304. # Check if file exists and ask user to remove and redownload
  305. if [[ -f $file ]] && ! ask_ok "Use existing $file?"; then
  306. execute rm -f "$file"
  307. fi
  308. # Download the file if it doesn't exist
  309. if [[ ! -f "$file" ]]; then
  310. echo "Downloading $url to $file using $DL_TOOL"
  311. execute "$DL_TOOL" "-o" "$file" "$url"
  312. fi
  313. done
  314. }
  315. add_repos() {
  316. debug "${FUNCNAME[0]}"
  317. if [[ -v P_ARR[repo] ]]; then
  318. if ! grep -q "${P_ARR[repo]}" "$BUILD_DIR/repositories.conf"; then
  319. echo "${P_ARR[repo]}" >> "$BUILD_DIR/repositories.conf"
  320. fi
  321. sed -i '/option check_signature/d' "$BUILD_DIR/repositories.conf"
  322. fi
  323. }
  324. ssh_backup() {
  325. debug "${FUNCNAME[0]}"
  326. local date hostname backup_fname
  327. printf -v date '%(%Y-%m-%d-%H-%M-%S)T'
  328. hostname=$(ssh -qt "$SSH_BACKUP_PATH" echo -n \$HOSTNAME)
  329. backup_fname="backup-$hostname-$date.tar.gz"
  330. [[ -d "$FILES_DIR" ]] || execute mkdir -p "$FILES_DIR"
  331. # Make backup archive on remote
  332. if ! execute "ssh -t $SSH_BACKUP_PATH sysupgrade -b /tmp/$backup_fname"; then
  333. echo "SSH backup failed"
  334. exit 1
  335. fi
  336. # Move backup archive locally
  337. if ! execute "rsync -avz --remove-source-files $SSH_BACKUP_PATH:/tmp/$backup_fname $BUILD_DIR/"; then
  338. echo "Could not copy SSH backup"
  339. exit 1
  340. fi
  341. # Extract backup archive
  342. if ! execute "tar -C $FILES_DIR -xzf $BUILD_DIR/$backup_fname"; then
  343. echo "Could not extract SSH backup"
  344. exit 1
  345. fi
  346. execute "rm $BUILD_DIR/$backup_fname"
  347. }
  348. make_images() {
  349. debug "${FUNCNAME[0]}"
  350. local -a make_opts
  351. # Reuse the existing output
  352. # if [[ -d "$BIN_DIR" ]]; then
  353. # if ask_ok "$BIN_DIR exists. Rebuild?"; then
  354. # execute rm -rf "$BIN_DIR"
  355. # else
  356. # return 0
  357. # fi
  358. # fi
  359. ((VERBOSE)) && make_opts+=("V=s")
  360. debug make "${make_opts[@]}" image BIN_DIR="$BIN_DIR" \
  361. PROFILE="$DEVICE" PACKAGES="$PACKAGES" \
  362. FILES="$FILES_DIR" --directory="$BUILD_DIR" \
  363. --jobs="$(($(nproc) - 1))"
  364. make "${make_opts[@]}" image \
  365. BIN_DIR="$BIN_DIR" \
  366. PROFILE="$DEVICE" \
  367. PACKAGES="$PACKAGES" \
  368. FILES="$FILES_DIR" \
  369. --directory="$BUILD_DIR" \
  370. --jobs="$(($(nproc) - 1))" \
  371. > "$BUILD_DIR/make.log"
  372. }
  373. flash_images() {
  374. debug "${FUNCNAME[0]}"
  375. local img_gz="$1"
  376. local dev="$2"
  377. local img="${img_gz%.gz}"
  378. local partitions
  379. if [[ ! -e "$dev" ]]; then
  380. echo "The device specified by --flash could not be found"
  381. return 1
  382. fi
  383. [[ -f $img_gz ]] || { echo "$img_gz does not exist"; return 1; }
  384. execute gunzip -qfk "$img_gz"
  385. echo "Unmounting target device $dev partitions"
  386. partitions=("$dev"?*)
  387. execute sudo umount "${partitions[@]}"
  388. if execute sudo dd if="$img" of="$dev" bs=2M conv=fsync; then
  389. sync
  390. echo "Image flashed successfully!"
  391. else
  392. echo "dd failed!"
  393. return 1
  394. fi
  395. }
  396. ssh_upgrade() {
  397. debug "${FUNCNAME[0]}"
  398. local img_gz="$1"
  399. local ssh_path="$2"
  400. local img_fname="${img_gz##*/}"
  401. [[ -f $img_gz ]] || { echo "$img_gz is missing, check build output"; return 1; }
  402. echo "Copying '$img_gz' to $ssh_path/tmp/$img_fname"
  403. if ! execute scp "$img_gz" "$ssh_path:/tmp/$img_fname"; then
  404. echo "Could not copy $img_gz to $ssh_path:/tmp/$img_fname"
  405. return 1
  406. fi
  407. echo "Executing remote sysupgrade"
  408. # This may result in weird exit code from closing the ssh connection
  409. # shellcheck disable=SC2029
  410. ssh "$ssh_path" "sysupgrade -F /tmp/$img_fname"
  411. return 0
  412. }
  413. # @description Builds OpenWRT from source code using the the default buildbot as base
  414. # This enables the use of kernel config options in profiles
  415. # @arg $1 string .config seed URL
  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. # Cleanup build environment
  466. ((VERBOSE)) && make_opts+=("V=s")
  467. execute make "${make_opts[@]}" "-j1" distclean
  468. # make clean # compiled output
  469. # make targetclean # compiled output, toolchain
  470. # make dirclean # compiled output, toolchain, build tools
  471. # make distclean # compiled output, toolchain, build tools, .config, feeds, .ccache
  472. # Update package feed
  473. ./scripts/feeds update -i -f &&
  474. ./scripts/feeds update -a -f &&
  475. ./scripts/feeds install -a -f
  476. # Grab the release seed config
  477. if ! execute "$DL_TOOL" "-o" "$seed_file" "$seed_url"; then
  478. echo "Could not obtain $seed_file from $seed_url"
  479. return 1
  480. fi
  481. # Set compilation output dir
  482. config_opts+=("CONFIG_BINARY_FOLDER=\"$BIN_DIR\"")
  483. # Add custom packages
  484. for pkg in $PACKAGES; do
  485. if [[ $pkg == -* ]]; then
  486. config_opts+=("CONFIG_PACKAGE_${pkg#-}=n") # remove package
  487. else
  488. config_opts+=("CONFIG_PACKAGE_$pkg=y") # add package
  489. fi
  490. done
  491. # Add config options from profile
  492. for config in ${P_ARR[config]}; do
  493. config_opts+=("$config")
  494. done
  495. # Only compile selected fs
  496. execute sed -i '/CONFIG_TARGET_ROOTFS_/d' "$seed_file"
  497. config_opts+=("CONFIG_TARGET_PER_DEVICE_ROOTFS=n")
  498. if [[ $FILESYSTEM == "squashfs" ]]; then
  499. config_opts+=("CONFIG_TARGET_ROOTFS_EXT4FS=n")
  500. config_opts+=("CONFIG_TARGET_ROOTFS_SQUASHFS=y")
  501. elif [[ $FILESYSTEM == "ext4" ]]; then
  502. config_opts+=("CONFIG_TARGET_ROOTFS_SQUASHFS=n")
  503. config_opts+=("CONFIG_TARGET_ROOTFS_EXT4FS=y")
  504. fi
  505. # Only compile selected target image
  506. execute sed -i '/CONFIG_TARGET_DEVICE_/d' "$seed_file"
  507. config_opts+=("CONFIG_TARGET_MULTI_PROFILE=n")
  508. config_opts+=("CONFIG_TARGET_PROFILE=DEVICE_$DEVICE")
  509. config_opts+=("CONFIG_TARGET_${TARGET//\//_}_DEVICE_$DEVICE=y")
  510. config_opts+=("CONFIG_SDK=n")
  511. config_opts+=("CONFIG_SDK_LLVM_BPF=n")
  512. config_opts+=("CONFIG_IB=n")
  513. config_opts+=("CONFIG_MAKE_TOOLCHAIN=n")
  514. # Write options to config seed file
  515. for config in "${config_opts[@]}"; do
  516. debug "Writing $config to $seed_file"
  517. echo "$config" >> "$seed_file"
  518. done
  519. # Make prep
  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. execute 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. # @description Backs up a file to a chosen directory using its timestamp
  539. # @arg $1 string File to backup
  540. # @arg $2 string Directory to backup to
  541. backup() {
  542. debug "${FUNCNAME[0]}" "$*"
  543. local file="$1" dir="$2"
  544. local creation_date base_name backup_file
  545. [[ -f $file ]] || { debug "File not found: $file"; return 1; }
  546. [[ -d $dir ]] || execute mkdir -p "$dir" || { debug "Failed to create directory: $dir"; return 1; }
  547. if creation_date=$(stat -c %w "$file" 2>/dev/null || stat -c %y "$file" 2>/dev/null) && \
  548. [[ $creation_date != "-" && -n $creation_date ]] && \
  549. creation_date=$(date -d "$creation_date" +%y%m%d%H%M 2>/dev/null); then
  550. debug "Creation date: $creation_date"
  551. else
  552. creation_date="unknown"
  553. debug "Unable to determine creation date, using 'unknown'"
  554. fi
  555. base_name="${file##*/}"
  556. backup_file="$dir/$creation_date-$base_name"
  557. [[ -f $backup_file ]] || execute cp --archive "$file" "$backup_file"
  558. }
  559. # @section Helper functions
  560. # @internal
  561. debug() { ((DEBUG)) && echo "Debug: $*"; }
  562. ask_ok() {
  563. ((YES)) && return
  564. local r
  565. read -r -p "$* [y/N]: " r
  566. r=${r,,}
  567. [[ "$r" =~ ^(yes|y)$ ]]
  568. }
  569. extract() {
  570. local archive="$1"
  571. local out_dir="$2"
  572. if ! execute tar -axf "$archive" -C "$out_dir" --strip-components 1; then
  573. echo "Extraction failed"
  574. return 1
  575. fi
  576. }
  577. verify() {
  578. local file_to_check="$1" sum_file="$2"
  579. local checksum
  580. command -v sha256sum &>/dev/null || return 1
  581. [[ -f $sum_file && -f $file_to_check ]] || return 1
  582. checksum=$(grep "${file_to_check##*/}" "$sum_file" | cut -f1 -d' ')
  583. echo -n "$checksum $file_to_check" | sha256sum --check --status
  584. }
  585. load() {
  586. local source_file="$1"
  587. # shellcheck disable=SC1090
  588. [[ -f $source_file ]] && source "$source_file"
  589. }
  590. execute() {
  591. if debug "$*"; then
  592. "$@"
  593. else
  594. "$@" &>/dev/null
  595. fi
  596. }
  597. # @description The openwrtbuilder main function
  598. # @internal
  599. main() {
  600. debug "${FUNCNAME[0]}"
  601. init
  602. load "$SCRIPT_DIR/profiles"
  603. parse_input "$@"
  604. # Fallback to SCRIPT_DIR if BUILD_ROOT has not been set
  605. declare -g BUILD_ROOT="${BUILD_ROOT:=$SCRIPT_DIR}"
  606. declare -g FILES_DIR="${FILES_DIR:=$BUILD_ROOT/src/files}"
  607. declare -g BACKUP_DIR="$SCRIPT_DIR/backups"
  608. # This could be dangerous
  609. if [[ $BUILD_ROOT == "/" ]]; then
  610. echo "Invalid --buildroot"
  611. exit 1
  612. fi
  613. for dir in "$BUILD_ROOT/src" "$BUILD_ROOT/bin"; do
  614. [[ -d "$dir" ]] || execute mkdir -p "$dir"
  615. done
  616. # Allow --reset without a profile
  617. if ((RESET)) && [[ ${#PROFILES} -lt 1 ]]; then
  618. for d in "$BUILD_ROOT/src" "$BUILD_ROOT/bin"; do
  619. ask_ok "Remove $d?" && execute rm -rf "$d"
  620. done
  621. exit $?
  622. fi
  623. install_dependencies
  624. for profile in "${PROFILES[@]}"; do
  625. debug "Running profile: $profile"
  626. if [[ ! ${!profile@a} = A ]]; then
  627. echo "Profile '$profile' does not exist"
  628. return 1
  629. fi
  630. # Store profile in P_ARR nameref
  631. declare -gn P_ARR="$profile"
  632. declare -g FILESYSTEM="${P_ARR[filesystem]:="squashfs"}"
  633. declare -g TARGET="${P_ARR[target]}"
  634. declare -g DEVICE="${P_ARR[device]}"
  635. declare -g PACKAGES="${P_ARR[packages]:-}"
  636. declare -g RELEASE="${USER_RELEASE:=${P_ARR[release]:=$RELEASE}}"
  637. # normalize RELEASE
  638. case "$RELEASE" in
  639. snapshot|latest|main|master) RELEASE="snapshot" ;;
  640. v[0-9][0-9].[0-9][0-9].*) RELEASE="${RELEASE#v}" ;;
  641. [0-9][0-9].[0-9][0-9].*) ;;
  642. *)
  643. if ! ((FROM_SOURCE)); then
  644. echo "Error: Invalid release version format"
  645. echo "Use semantic version, tag, or 'snapshot'"
  646. exit 1
  647. fi
  648. ;;
  649. esac
  650. declare -g SRC_DIR="$BUILD_ROOT/src/openwrt"
  651. declare -g WORKTREE_DIR="$BUILD_ROOT/src/$profile/$RELEASE-src"
  652. declare -g BUILD_DIR="$BUILD_ROOT/src/$profile/$RELEASE"
  653. declare -g BIN_DIR="$BUILD_ROOT/bin/$profile/$RELEASE"
  654. if [[ "$RELEASE" == "snapshot" ]]; then
  655. local url_prefix="https://downloads.openwrt.org/snapshots/targets/$TARGET"
  656. local url_filename="openwrt-imagebuilder-${TARGET//\//-}.Linux-x86_64.tar.zst"
  657. local img_fname="openwrt-${TARGET//\//-}-$DEVICE-$FILESYSTEM"
  658. else
  659. local url_prefix="https://downloads.openwrt.org/releases/$RELEASE/targets/$TARGET"
  660. local url_filename="openwrt-imagebuilder-$RELEASE-${TARGET//\//-}.Linux-x86_64.tar.zst"
  661. local img_fname="openwrt-$RELEASE-${TARGET//\//-}-$DEVICE-$FILESYSTEM"
  662. fi
  663. local ib_url="$url_prefix/$url_filename"
  664. local ib_file="$BUILD_DIR/$url_filename"
  665. local ib_sha256_url="$url_prefix/sha256sums"
  666. local ib_sha256_file="$BUILD_DIR/sha256sums"
  667. local seed_url="$url_prefix/config.buildinfo"
  668. if ((FROM_SOURCE)); then
  669. declare -g SYSUPGRADEIMGGZ="$BIN_DIR/targets/$TARGET/$img_fname-sysupgrade.img.gz"
  670. else
  671. declare -g SYSUPGRADEIMGGZ="$BUILD_DIR/$img_fname-sysupgrade.img.gz"
  672. fi
  673. backup "$SYSUPGRADEIMGGZ" "$BACKUP_DIR/$profile/$RELEASE"
  674. if ((RESET)); then
  675. if ((FROM_SOURCE)); then
  676. [[ -d $WORKTREE_DIR ]] && ask_ok "Remove $WORKTREE_DIR?"
  677. execute git worktree remove --force "$WORKTREE_DIR"
  678. execute rm -rf "$WORKTREE_DIR"
  679. elif [[ -d $BUILD_DIR ]] && ask_ok "Remove $BUILD_DIR?"; then
  680. execute rm -rf "$BUILD_DIR"
  681. fi
  682. fi
  683. if ((DEBUG)); then
  684. echo "Profile settings:"
  685. for x in "${!P_ARR[@]}"; do printf "%s=%s\n" "$x" "${P_ARR[$x]}"; done
  686. echo "Environment variables:"
  687. declare -p
  688. fi
  689. if ((FROM_SOURCE)); then
  690. from_source "$seed_url" || return $?
  691. else
  692. [[ -d $BUILD_DIR ]] || mkdir -p "$BUILD_DIR"
  693. get_imagebuilder "$ib_url" "$ib_file" "$ib_sha256_url" "$ib_sha256_file" &&
  694. verify "$ib_file" "$ib_sha256_file" &&
  695. extract "$ib_file" "$BUILD_DIR" || return $?
  696. add_repos
  697. make_images
  698. # Verify output image for stock builds (in testing)
  699. if [[ ! -v P_ARR[packages] || -z ${P_ARR[packages]} ]]; then
  700. shopt -s nullglob
  701. local -a outfiles=("$BIN_DIR"/*.img.gz "$BIN_DIR"/*.img)
  702. shopt -u nullglob
  703. for outfile in "${outfiles[@]}"; do
  704. verify "$outfile" "$ib_sha256_file" || return 1
  705. done
  706. fi
  707. #copyFiles
  708. fi
  709. [[ -v SSH_BACKUP_PATH ]] && ssh_backup
  710. [[ -v SSH_UPGRADE_PATH ]] && ssh_upgrade "$SYSUPGRADEIMGGZ" "$SSH_UPGRADE_PATH"
  711. [[ -v FLASH_DEV ]] && flash_images "$SYSUPGRADEIMGGZ" "$FLASH_DEV"
  712. done
  713. }
  714. main "$@"
  715. exit