openwrtbuilder 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816
  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. # @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. ;;
  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. ;;
  291. *)
  292. debug "Skipping dependency install, your OS is unsupported"
  293. return 1
  294. ;;
  295. esac
  296. fi
  297. pkg_install "${pkg_list[@]}" && echo "${pkg_list[@]}" > "$lock_file"
  298. fi
  299. }
  300. # @description Acquires the OpenWRT Image Builder
  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. # @description Builds OpenWRT from source code using the the default buildbot as base
  417. # This enables the use of kernel config options in profiles
  418. # @arg $1 string .config seed URL
  419. from_source() {
  420. debug "${FUNCNAME[0]}" "$*"
  421. local seed_url="$1"
  422. local src_url="https://github.com/openwrt/openwrt.git"
  423. local seed_file="$WORKTREE_DIR/.config"
  424. local pkg config commit seed_file wt_commit description
  425. local -a make_opts config_opts
  426. echo "Building from source is under development"
  427. # Update source code
  428. if [[ ! -d "$SRC_DIR" ]]; then
  429. execute mkdir -p "$SRC_DIR"
  430. execute git clone "$src_url" "$SRC_DIR"
  431. fi
  432. git -C "$SRC_DIR" pull
  433. # Generate commitish for git worktree
  434. case "$RELEASE" in
  435. snapshot) wt_commit="origin/main" ;;
  436. [0-9][0-9].[0-9][0-9].*)
  437. local branch="openwrt-${RELEASE%.*}"
  438. local tag="v$RELEASE"
  439. if ask_ok "Use $branch branch HEAD (y, recommended) or $tag tag (N)?"; then
  440. wt_commit="origin/$branch"
  441. else
  442. wt_commit="$tag"
  443. fi
  444. ;;
  445. *)
  446. debug "Passing '$RELEASE' commit-ish to git worktree"
  447. wt_commit="$RELEASE"
  448. ;;
  449. esac
  450. # TODO There's a bug in the make clean functions that seem to invoke a full make
  451. if [[ -d "$WORKTREE_DIR" ]]; then
  452. execute git -C "$WORKTREE_DIR" checkout "$wt_commit"
  453. execute git -C "$WORKTREE_DIR" pull
  454. else
  455. execute git -C "$SRC_DIR" worktree add --force --detach "$WORKTREE_DIR" "$wt_commit"
  456. fi
  457. # To workaround bug, don't use make *clean, blow it away and start fresh
  458. # [[ -d "$WORKTREE_DIR" ]] && execute rm -rf "$WORKTREE_DIR"
  459. # execute git -C "$SRC_DIR" worktree add --force --detach "$WORKTREE_DIR" "$wt_commit"
  460. # Print commit information
  461. commit=$(git -C "$WORKTREE_DIR" rev-parse HEAD)
  462. description=$(git -C "$WORKTREE_DIR" describe)
  463. echo "Current commit hash: $commit"
  464. echo "Git worktree description: $description"
  465. ((DEBUG)) && git --no-pager -C "$WORKTREE_DIR" log -1
  466. # Enter worktree
  467. execute pushd "$WORKTREE_DIR" || return 1
  468. # Cleanup build environment
  469. ((VERBOSE)) && make_opts+=("V=s")
  470. execute make "${make_opts[@]}" "-j1" dirclean
  471. # make clean # compiled output
  472. # make targetclean # compiled output, toolchain
  473. # make dirclean # compiled output, toolchain, build tools
  474. # make distclean # compiled output, toolchain, build tools, .config, feeds, .ccache
  475. # Update package feed
  476. ./scripts/feeds update -i -f &&
  477. ./scripts/feeds update -a -f &&
  478. ./scripts/feeds install -a -f
  479. # Grab the release seed config
  480. if ! execute "$DL_TOOL" "-o" "$seed_file" "$seed_url"; then
  481. echo "Could not obtain $seed_file from $seed_url"
  482. return 1
  483. fi
  484. # Set compilation output dir
  485. config_opts+=("CONFIG_BINARY_FOLDER=\"$BIN_DIR\"")
  486. # Add custom packages
  487. for pkg in $PACKAGES; do
  488. if [[ $pkg == -* ]]; then
  489. config_opts+=("CONFIG_PACKAGE_${pkg#-}=n") # remove package
  490. else
  491. config_opts+=("CONFIG_PACKAGE_$pkg=y") # add package
  492. fi
  493. done
  494. # Add config options from profile
  495. for config in ${P_ARR[config]}; do
  496. config_opts+=("$config")
  497. done
  498. # Only compile selected fs
  499. execute sed -i '/CONFIG_TARGET_ROOTFS_/d' "$seed_file"
  500. config_opts+=("CONFIG_TARGET_PER_DEVICE_ROOTFS=n")
  501. if [[ $FILESYSTEM == "squashfs" ]]; then
  502. config_opts+=("CONFIG_TARGET_ROOTFS_EXT4FS=n")
  503. config_opts+=("CONFIG_TARGET_ROOTFS_SQUASHFS=y")
  504. elif [[ $FILESYSTEM == "ext4" ]]; then
  505. config_opts+=("CONFIG_TARGET_ROOTFS_SQUASHFS=n")
  506. config_opts+=("CONFIG_TARGET_ROOTFS_EXT4FS=y")
  507. fi
  508. # Only compile selected target image
  509. execute sed -i '/CONFIG_TARGET_DEVICE_/d' "$seed_file"
  510. config_opts+=("CONFIG_TARGET_MULTI_PROFILE=n")
  511. config_opts+=("CONFIG_TARGET_PROFILE=DEVICE_$DEVICE")
  512. config_opts+=("CONFIG_TARGET_${TARGET//\//_}_DEVICE_$DEVICE=y")
  513. config_opts+=("CONFIG_SDK=n")
  514. config_opts+=("CONFIG_SDK_LLVM_BPF=n")
  515. config_opts+=("CONFIG_IB=n")
  516. config_opts+=("CONFIG_MAKE_TOOLCHAIN=n")
  517. # Write options to config seed file
  518. for config in "${config_opts[@]}"; do
  519. debug "Writing $config to $seed_file"
  520. echo "$config" >> "$seed_file"
  521. done
  522. # Make prep
  523. execute make "${make_opts[@]}" "-j1" defconfig
  524. execute make "${make_opts[@]}" "-j1" download
  525. # ((DEBUG)) && make_opts+=("-j1") || make_opts+=("-j$(($(nproc)+1))")
  526. make_opts+=("-j$(($(nproc)+1))")
  527. # Make image
  528. if ! execute ionice -c 3 chrt --idle 0 nice -n19 make "${make_opts[@]}" world; then
  529. echo "Error: make failed"
  530. return 1
  531. fi
  532. execute popd || return 1
  533. # Symlink output images to root of BIN_DIR (match Image Builder)
  534. shopt -s nullglob
  535. for image in "$BIN_DIR/targets/${TARGET}/"*.{img,img.gz,ubi}; do
  536. execute ln -fs "$image" "$BIN_DIR/${image##*/}"
  537. done
  538. shopt -u nullglob
  539. return 0
  540. }
  541. # @description Backs up a file to a chosen directory using its timestamp
  542. # @arg $1 string File to backup
  543. # @arg $2 string Directory to backup to
  544. backup() {
  545. debug "${FUNCNAME[0]}" "$*"
  546. local file="$1" dir="$2"
  547. local creation_date base_name backup_file
  548. [[ -f $file ]] || { debug "File not found: $file"; return 1; }
  549. [[ -d $dir ]] || execute mkdir -p "$dir" || { debug "Failed to create directory: $dir"; return 1; }
  550. if creation_date=$(stat -c %w "$file" 2>/dev/null || stat -c %y "$file" 2>/dev/null) && \
  551. [[ $creation_date != "-" && -n $creation_date ]] && \
  552. creation_date=$(date -d "$creation_date" +%y%m%d%H%M 2>/dev/null); then
  553. debug "Creation date: $creation_date"
  554. else
  555. creation_date="unknown"
  556. debug "Unable to determine creation date, using 'unknown'"
  557. fi
  558. base_name="${file##*/}"
  559. backup_file="$dir/$creation_date-$base_name"
  560. [[ -f $backup_file ]] || execute cp --archive "$file" "$backup_file"
  561. }
  562. # @section Helper functions
  563. # @internal
  564. debug() { ((DEBUG)) && echo "Debug: $*"; }
  565. ask_ok() {
  566. ((YES)) && return
  567. local r
  568. read -r -p "$* [y/N]: " r
  569. r=${r,,}
  570. [[ "$r" =~ ^(yes|y)$ ]]
  571. }
  572. extract() {
  573. local archive="$1"
  574. local out_dir="$2"
  575. if ! execute tar -axf "$archive" -C "$out_dir" --strip-components 1; then
  576. echo "Extraction failed"
  577. return 1
  578. fi
  579. }
  580. verify() {
  581. local file_to_check="$1" sum_file="$2"
  582. local checksum
  583. command -v sha256sum &>/dev/null || return 1
  584. [[ -f $sum_file && -f $file_to_check ]] || return 1
  585. checksum=$(grep "${file_to_check##*/}" "$sum_file" | cut -f1 -d' ')
  586. echo -n "$checksum $file_to_check" | sha256sum --check --status
  587. }
  588. load() {
  589. local source_file="$1"
  590. # shellcheck disable=SC1090
  591. [[ -f $source_file ]] && source "$source_file"
  592. }
  593. execute() {
  594. if debug "$*"; then
  595. "$@"
  596. else
  597. "$@" &>/dev/null
  598. fi
  599. }
  600. # @description The openwrtbuilder main function
  601. # @internal
  602. main() {
  603. debug "${FUNCNAME[0]}"
  604. init
  605. load "$SCRIPT_DIR/profiles"
  606. parse_input "$@"
  607. # Fallback to SCRIPT_DIR if BUILD_ROOT has not been set
  608. declare -g BUILD_ROOT="${BUILD_ROOT:=$SCRIPT_DIR}"
  609. declare -g FILES_DIR="${FILES_DIR:=$BUILD_ROOT/src/files}"
  610. declare -g BACKUP_DIR="$SCRIPT_DIR/backups"
  611. # This could be dangerous
  612. if [[ $BUILD_ROOT == "/" ]]; then
  613. echo "Invalid --buildroot"
  614. exit 1
  615. fi
  616. for dir in "$BUILD_ROOT/src" "$BUILD_ROOT/bin"; do
  617. [[ -d "$dir" ]] || execute mkdir -p "$dir"
  618. done
  619. # Allow --reset without a profile
  620. if ((RESET)) && [[ ${#PROFILES} -lt 1 ]]; then
  621. for d in "$BUILD_ROOT/src" "$BUILD_ROOT/bin"; do
  622. ask_ok "Remove $d?" && execute rm -rf "$d"
  623. done
  624. exit $?
  625. fi
  626. install_dependencies
  627. for profile in "${PROFILES[@]}"; do
  628. debug "Running profile: $profile"
  629. if [[ ! ${!profile@a} = A ]]; then
  630. echo "Profile '$profile' does not exist"
  631. return 1
  632. fi
  633. # Store profile in P_ARR nameref
  634. declare -gn P_ARR="$profile"
  635. declare -g FILESYSTEM="${P_ARR[filesystem]:="squashfs"}"
  636. declare -g TARGET="${P_ARR[target]}"
  637. declare -g DEVICE="${P_ARR[device]}"
  638. declare -g PACKAGES="${P_ARR[packages]:-}"
  639. declare -g RELEASE="${USER_RELEASE:=${P_ARR[release]:=$RELEASE}}"
  640. # normalize RELEASE
  641. case "$RELEASE" in
  642. snapshot|latest|main|master) RELEASE="snapshot" ;;
  643. v[0-9][0-9].[0-9][0-9].*) RELEASE="${RELEASE#v}" ;;
  644. [0-9][0-9].[0-9][0-9].*) ;;
  645. *)
  646. if ! ((FROM_SOURCE)); then
  647. echo "Error: Invalid release version format"
  648. echo "Use semantic version, tag, or 'snapshot'"
  649. exit 1
  650. fi
  651. ;;
  652. esac
  653. declare -g SRC_DIR="$BUILD_ROOT/src/openwrt"
  654. declare -g WORKTREE_DIR="$BUILD_ROOT/src/$profile/$RELEASE-src"
  655. declare -g BUILD_DIR="$BUILD_ROOT/src/$profile/$RELEASE"
  656. declare -g BIN_DIR="$BUILD_ROOT/bin/$profile/$RELEASE"
  657. if [[ "$RELEASE" == "snapshot" ]]; then
  658. local url_prefix="https://downloads.openwrt.org/snapshots/targets/$TARGET"
  659. local url_filename="openwrt-imagebuilder-${TARGET//\//-}.Linux-x86_64.tar.zst"
  660. local img_fname="openwrt-${TARGET//\//-}-$DEVICE-$FILESYSTEM"
  661. else
  662. local url_prefix="https://downloads.openwrt.org/releases/$RELEASE/targets/$TARGET"
  663. local url_filename="openwrt-imagebuilder-$RELEASE-${TARGET//\//-}.Linux-x86_64.tar.xz"
  664. local img_fname="openwrt-$RELEASE-${TARGET//\//-}-$DEVICE-$FILESYSTEM"
  665. fi
  666. local ib_url="$url_prefix/$url_filename"
  667. local ib_file="$BUILD_DIR/$url_filename"
  668. local ib_sha256_url="$url_prefix/sha256sums"
  669. local ib_sha256_file="$BUILD_DIR/sha256sums"
  670. local seed_url="$url_prefix/config.buildinfo"
  671. if ((FROM_SOURCE)); then
  672. declare -g SYSUPGRADEIMGGZ="$BIN_DIR/targets/$TARGET/$img_fname-sysupgrade.img.gz"
  673. else
  674. declare -g SYSUPGRADEIMGGZ="$BUILD_DIR/$img_fname-sysupgrade.img.gz"
  675. fi
  676. backup "$SYSUPGRADEIMGGZ" "$BACKUP_DIR/$profile/$RELEASE"
  677. if ((RESET)); then
  678. if ((FROM_SOURCE)); then
  679. [[ -d $WORKTREE_DIR ]] && ask_ok "Remove $WORKTREE_DIR?"
  680. execute git worktree remove --force "$WORKTREE_DIR"
  681. execute rm -rf "$WORKTREE_DIR"
  682. elif [[ -d $BUILD_DIR ]] && ask_ok "Remove $BUILD_DIR?"; then
  683. execute rm -rf "$BUILD_DIR"
  684. fi
  685. fi
  686. if ((DEBUG)); then
  687. echo "Profile settings:"
  688. for x in "${!P_ARR[@]}"; do printf "%s=%s\n" "$x" "${P_ARR[$x]}"; done
  689. echo "Environment variables:"
  690. declare -p
  691. fi
  692. if ((FROM_SOURCE)); then
  693. from_source "$seed_url" || return $?
  694. else
  695. [[ -d $BUILD_DIR ]] || mkdir -p "$BUILD_DIR"
  696. get_imagebuilder "$ib_url" "$ib_file" "$ib_sha256_url" "$ib_sha256_file" &&
  697. verify "$ib_file" "$ib_sha256_file" &&
  698. extract "$ib_file" "$BUILD_DIR" || return $?
  699. add_repos
  700. make_images
  701. # Verify output image for stock builds (in testing)
  702. if [[ ! -v P_ARR[packages] || -z ${P_ARR[packages]} ]]; then
  703. shopt -s nullglob
  704. local -a outfiles=("$BIN_DIR"/*.img.gz "$BIN_DIR"/*.img)
  705. shopt -u nullglob
  706. for outfile in "${outfiles[@]}"; do
  707. verify "$outfile" "$ib_sha256_file" || return 1
  708. done
  709. fi
  710. #copyFiles
  711. fi
  712. [[ -v SSH_BACKUP_PATH ]] && ssh_backup
  713. [[ -v SSH_UPGRADE_PATH ]] && ssh_upgrade "$SYSUPGRADEIMGGZ" "$SSH_UPGRADE_PATH"
  714. [[ -v FLASH_DEV ]] && flash_images "$SYSUPGRADEIMGGZ" "$FLASH_DEV"
  715. done
  716. }
  717. main "$@"
  718. exit