openwrtbuilder 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818
  1. #!/usr/bin/env bash
  2. # Builds and deploys OpenWRT images
  3. # Copyright 2022-25 Bryan C. Roessler
  4. # Apache 2.0 License
  5. # See README and profiles for device configuration
  6. # Set default release
  7. : "${RELEASE:="24.10.2"}"
  8. # @internal
  9. print_help() {
  10. debug "${FUNCNAME[0]}"
  11. cat <<-'EOF'
  12. Build and deploy OpenWRT images using convenient profiles.
  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. Allows make config options to be passed in profile
  23. Uses git worktree for multi-profile deduplication
  24. --ssh-upgrade HOST
  25. Examples: root@192.168.1.1, root@router.lan
  26. --ssh-backup SSH_PATH
  27. Enabled by default for --ssh-upgrade
  28. --flash,-f DEVICE
  29. Example: /dev/sdX
  30. --reset
  31. Cleanup all source and output files
  32. --depends
  33. Force dependency installation
  34. --yes,-y
  35. Assume yes for all questions (automatic mode)
  36. --debug,-d
  37. --help,-h
  38. EXAMPLES
  39. ./openwrtbuilder -p r4s -r snapshot
  40. ./openwrtbuilder -p ax6000 -r 23.05.0-rc3 --source --debug
  41. ./openwrtbuilder -p rpi4 -r 24.10.0 --flash /dev/sdX
  42. ./openwrtbuilder -p linksys -r snapshot --ssh-upgrade root@192.168.1.1
  43. EOF
  44. }
  45. # @internal
  46. init() {
  47. debug "${FUNCNAME[0]}"
  48. declare -g ID RPM_MGR SCRIPT_DIR DL_TOOL
  49. ((DEBUG)) || echo "To enable debugging output, use --debug or -d"
  50. # Save the script directory
  51. # https://stackoverflow.com/a/4774063
  52. SCRIPT_DIR="$(cd -- "$(dirname "$0")" >/dev/null 2>&1 || exit $? ; pwd -P)"
  53. if [[ -e "/etc/os-release" ]]; then
  54. source "/etc/os-release"
  55. else
  56. echo "/etc/os-release not found"
  57. echo "Your OS is unsupported"
  58. print_help
  59. exit 1
  60. fi
  61. debug "Detected host platform: $ID"
  62. # normalize distro ID
  63. case "$ID" in
  64. debian|arch) ;;
  65. centos|fedora)
  66. if command -v dnf &>/dev/null; then
  67. RPM_MGR="dnf"
  68. elif command -v yum &>/dev/null; then
  69. RPM_MGR="yum"
  70. fi
  71. ;;
  72. rhel) ID="centos" ;;
  73. linuxmint|neon|*ubuntu*) ID="ubuntu" ;;
  74. *suse*) ID="suse" ;;
  75. raspbian) ID="debian" ;;
  76. *)
  77. echo "Autodetecting distro, this may be unreliable"
  78. for cmd in dnf yum apt pacman; do
  79. if command -v "$cmd" &>/dev/null; then
  80. case "$cmd" in
  81. dnf) ID="fedora"; RPM_MGR="dnf" ;;
  82. yum) ID="centos"; RPM_MGR="yum" ;;
  83. apt) ID="ubuntu" ;;
  84. pacman) ID="arch" ;;
  85. esac
  86. break
  87. fi
  88. done
  89. [[ -z $ID ]] && return 1
  90. ;;
  91. esac
  92. debug "Using host platform: $ID"
  93. # Set distro-specific functions
  94. case "$ID" in
  95. fedora|centos) pkg_install(){ sudo "$RPM_MGR" install -y "$@"; } ;;
  96. debian|ubuntu) pkg_install(){ sudo apt-get install --ignore-missing -y -q0 "$@"; } ;;
  97. suse) pkg_install(){ sudo zypper --non-interactive -q install --force --no-confirm "$@"; } ;;
  98. arch) pkg_install(){ sudo pacman -S --noconfirm --needed "$@"; } ;;
  99. esac
  100. if command -v axel &>/dev/null; then
  101. DL_TOOL="axel"
  102. elif command -v curl &>/dev/null; then
  103. DL_TOOL="curl"
  104. else
  105. echo "Downloading the Image Builder requires axel or curl"
  106. return 1
  107. fi
  108. }
  109. # @description Arguments
  110. parse_input() {
  111. debug "${FUNCNAME[0]}" "$*"
  112. declare -ga PROFILES
  113. declare -gi RESET=0 FROM_SOURCE=0 YES=0 DEBUG=0 FORCE_DEPENDS=0
  114. declare -g USER_RELEASE SSH_UPGRADE_PATH SSH_BACKUP_PATH FLASH_DEV
  115. local long_opts='release:,version:,profile:,buildroot:,source,'
  116. long_opts+='ssh-upgrade:,ssh-backup:,flash:,reset,depends,yes,debug,help'
  117. if _input=$(getopt -o +r:v:p:b:sf:ydh -l $long_opts -- "$@"); then
  118. eval set -- "$_input"
  119. while true; do
  120. case "$1" in
  121. --release|-r|--version|-v) shift; USER_RELEASE="$1" ;;
  122. --profile|-p) shift; PROFILES+=("$1") ;;
  123. --buildroot|-b) shift; BUILD_ROOT="$1" ;;
  124. --source|-s) FROM_SOURCE=1 ;;
  125. --ssh-upgrade) shift; SSH_UPGRADE_PATH="$1" ;;
  126. --ssh-backup) shift; SSH_BACKUP_PATH="$1" ;;
  127. --flash|-f) shift; FLASH_DEV="$1" ;;
  128. --reset) RESET=1 ;;
  129. --depends) FORCE_DEPENDS=1 ;;
  130. --yes|-y) YES=1 ;;
  131. --debug|-d) echo "Debugging on"; DEBUG=1 ;;
  132. --help|-h) print_help; exit 0 ;;
  133. --) shift; break ;;
  134. esac
  135. shift
  136. done
  137. else
  138. echo "Incorrect options provided"
  139. print_help; exit 1
  140. fi
  141. }
  142. # @description Install build dependencies on major distros
  143. install_dependencies() {
  144. debug "${FUNCNAME[0]}"
  145. local -a pkg_list
  146. local lock_file
  147. if ((FROM_SOURCE)); then
  148. lock_file="$BUILD_ROOT/.dependencies_source"
  149. else
  150. lock_file="$BUILD_ROOT/.dependencies_ib"
  151. fi
  152. if ((FORCE_DEPENDS)) || [[ ! -f $lock_file ]]; then
  153. if ((FROM_SOURCE)); then
  154. # For building from source code
  155. # https://openwrt.org/docs/guide-developer/toolchain/install-buildsystem
  156. case "$ID" in
  157. fedora|centos)
  158. pkg_list+=(
  159. bash-completion
  160. bzip2
  161. gcc
  162. gcc-c++
  163. git
  164. make
  165. ncurses-devel
  166. patch
  167. rsync
  168. tar
  169. unzip
  170. wget
  171. which
  172. diffutils
  173. python3
  174. python3-devel
  175. python3-setuptools
  176. python3-pyelftools
  177. perl
  178. perl-base
  179. perl-Data-Dumper
  180. perl-File-Compare
  181. perl-File-Copy
  182. perl-FindBin
  183. perl-IPC-Cmd
  184. perl-Thread-Queue
  185. perl-Time-Piece
  186. perl-JSON-PP
  187. swig
  188. clang # for qosify
  189. llvm15-libs
  190. patch)
  191. ;;
  192. debian|ubuntu)
  193. pkg_list+=(
  194. build-essential
  195. clang
  196. flex
  197. g++
  198. gawk
  199. gcc-multilib
  200. gettext
  201. git
  202. libncurses5-dev
  203. libssl-dev
  204. python3-distutils
  205. rsync
  206. unzip
  207. zlib1g-dev
  208. file
  209. wget
  210. patch)
  211. ;;
  212. arch)
  213. pkg_list+=(
  214. base-devel
  215. autoconf
  216. automake
  217. bash
  218. binutils
  219. bison
  220. bzip2
  221. clang
  222. fakeroot
  223. file
  224. findutils
  225. flex
  226. gawk
  227. gcc
  228. gettext
  229. git
  230. grep
  231. groff
  232. gzip
  233. libelf
  234. libtool
  235. libxslt
  236. m4
  237. make
  238. ncurses
  239. openssl
  240. patch
  241. pkgconf
  242. python
  243. rsync
  244. sed
  245. texinfo
  246. time
  247. unzip
  248. util-linux
  249. wget
  250. which
  251. zlib
  252. patch)
  253. ;;
  254. *)
  255. debug "Skipping dependency install, your OS is unsupported"
  256. return 1
  257. ;;
  258. esac
  259. else
  260. # For Imagebuilder
  261. case "$ID" in
  262. fedora|centos)
  263. pkg_list+=(
  264. @c-development
  265. @development-tools
  266. @development-libs
  267. perl-FindBin
  268. zlib-static
  269. elfutils-libelf-devel
  270. gawk
  271. unzip
  272. file
  273. wget
  274. python3
  275. axel
  276. perl-IPC-Cmd
  277. zstd) ;;
  278. debian|ubuntu)
  279. pkg_list+=(
  280. build-essential
  281. libncurses5-dev
  282. libncursesw5-dev
  283. zlib1g-dev
  284. gawk
  285. git
  286. gettext
  287. libssl-dev
  288. xsltproc
  289. wget
  290. unzip
  291. python
  292. axel
  293. zstd) ;;
  294. *) debug "Unsupported OS for automatic dependency install"; return 1 ;;
  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. ((DEBUG)) && make_opts+=("V=sc")
  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="$BIN_DIR" \
  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. ((DEBUG)) && make_opts+=("V=sc")
  470. execute make "${make_opts[@]}" "-j1" distclean
  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. # Use a custom (faster) mirror
  476. # execute sed -i -E 's;git.openwrt.org/(feed|project);github.com/openwrt;' feeds.conf.default
  477. # Update package feed
  478. ./scripts/feeds update -i -f &&
  479. ./scripts/feeds update -a -f &&
  480. ./scripts/feeds install -a -f
  481. # Grab the release seed config
  482. if ! execute "$DL_TOOL" "-o" "$seed_file" "$seed_url"; then
  483. echo "Could not obtain $seed_file from $seed_url"
  484. return 1
  485. fi
  486. # Set compilation output dir
  487. config_opts+=("CONFIG_BINARY_FOLDER=\"$BIN_DIR\"")
  488. # Add custom packages
  489. for pkg in $PACKAGES; do
  490. if [[ $pkg == -* ]]; then
  491. config_opts+=("CONFIG_PACKAGE_${pkg#-}=n") # remove package
  492. else
  493. config_opts+=("CONFIG_PACKAGE_$pkg=y") # add package
  494. fi
  495. done
  496. # Add config options from profile
  497. for config in ${P_ARR[config]}; do
  498. config_opts+=("$config")
  499. done
  500. # Only compile selected fs
  501. execute sed -i '/CONFIG_TARGET_ROOTFS_/d' "$seed_file"
  502. config_opts+=("CONFIG_TARGET_PER_DEVICE_ROOTFS=n")
  503. if [[ $FILESYSTEM == "squashfs" ]]; then
  504. config_opts+=("CONFIG_TARGET_ROOTFS_EXT4FS=n")
  505. config_opts+=("CONFIG_TARGET_ROOTFS_SQUASHFS=y")
  506. elif [[ $FILESYSTEM == "ext4" ]]; then
  507. config_opts+=("CONFIG_TARGET_ROOTFS_SQUASHFS=n")
  508. config_opts+=("CONFIG_TARGET_ROOTFS_EXT4FS=y")
  509. fi
  510. # Only compile selected target image
  511. execute sed -i '/CONFIG_TARGET_DEVICE_/d' "$seed_file"
  512. config_opts+=("CONFIG_TARGET_MULTI_PROFILE=n")
  513. config_opts+=("CONFIG_TARGET_PROFILE=DEVICE_$DEVICE")
  514. config_opts+=("CONFIG_TARGET_${TARGET//\//_}_DEVICE_$DEVICE=y")
  515. config_opts+=("CONFIG_SDK=n")
  516. config_opts+=("CONFIG_SDK_LLVM_BPF=n")
  517. config_opts+=("CONFIG_IB=n")
  518. config_opts+=("CONFIG_MAKE_TOOLCHAIN=n")
  519. # Write options to config seed file
  520. for config in "${config_opts[@]}"; do
  521. debug "Writing $config to $seed_file"
  522. echo "$config" >> "$seed_file"
  523. done
  524. # Make prep
  525. execute make "${make_opts[@]}" "-j1" defconfig
  526. execute make "${make_opts[@]}" "-j1" download
  527. ((DEBUG)) && make_opts+=("-j1") || make_opts+=("-j$(($(nproc)-1))")
  528. # Make image
  529. if ! execute ionice -c 3 chrt --idle 0 nice -n19 make "${make_opts[@]}" world; then
  530. echo "Error: make failed"
  531. return 1
  532. fi
  533. execute popd || return 1
  534. # Symlink output images to root of BIN_DIR (match Image Builder)
  535. shopt -s nullglob
  536. for image in "$BIN_DIR/targets/${TARGET}/"*.{img,img.gz,ubi}; do
  537. execute ln -fs "$image" "$BIN_DIR/${image##*/}"
  538. done
  539. shopt -u nullglob
  540. return 0
  541. }
  542. # @description Backs up a file to a chosen directory using its timestamp
  543. # @arg $1 string File to backup
  544. # @arg $2 string Directory to backup to
  545. backup() {
  546. debug "${FUNCNAME[0]}" "$*"
  547. local file="$1" dir="$2"
  548. local creation_date base_name backup_file
  549. [[ -f $file ]] || { debug "File not found: $file"; return 1; }
  550. [[ -d $dir ]] || execute mkdir -p "$dir" || { debug "Failed to create directory: $dir"; return 1; }
  551. if creation_date=$(stat -c %w "$file" 2>/dev/null || stat -c %y "$file" 2>/dev/null) && \
  552. [[ $creation_date != "-" && -n $creation_date ]] && \
  553. creation_date=$(date -d "$creation_date" +%y%m%d%H%M 2>/dev/null); then
  554. debug "Creation date: $creation_date"
  555. else
  556. creation_date="unknown"
  557. debug "Unable to determine creation date, using 'unknown'"
  558. fi
  559. base_name="${file##*/}"
  560. backup_file="$dir/$creation_date-$base_name"
  561. [[ -f $backup_file ]] || execute cp --archive "$file" "$backup_file"
  562. }
  563. # @section Helper functions
  564. # @internal
  565. debug() { ((DEBUG)) && echo "Debug: $*"; }
  566. ask_ok() {
  567. ((YES)) && return
  568. local r
  569. read -r -p "$* [y/N]: " r
  570. r=${r,,}
  571. [[ "$r" =~ ^(yes|y)$ ]]
  572. }
  573. extract() {
  574. local archive="$1"
  575. local out_dir="$2"
  576. if ! execute tar -axf "$archive" -C "$out_dir" --strip-components 1; then
  577. echo "Extraction failed"
  578. return 1
  579. fi
  580. }
  581. verify() {
  582. local file_to_check="$1" sum_file="$2"
  583. local checksum
  584. command -v sha256sum &>/dev/null || return 1
  585. [[ -f $sum_file && -f $file_to_check ]] || return 1
  586. checksum=$(grep "${file_to_check##*/}" "$sum_file" | cut -f1 -d' ')
  587. echo -n "$checksum $file_to_check" | sha256sum --check --status
  588. }
  589. load() {
  590. local source_file="$1"
  591. # shellcheck disable=SC1090
  592. [[ -f $source_file ]] && source "$source_file"
  593. }
  594. execute() {
  595. if debug "$*"; then
  596. "$@"
  597. else
  598. "$@" &>/dev/null
  599. fi
  600. }
  601. # @description The openwrtbuilder main function
  602. # @internal
  603. main() {
  604. debug "${FUNCNAME[0]}"
  605. init
  606. load "$SCRIPT_DIR/profiles"
  607. parse_input "$@"
  608. # Fallback to SCRIPT_DIR if BUILD_ROOT has not been set
  609. declare -g BUILD_ROOT="${BUILD_ROOT:=$SCRIPT_DIR}"
  610. declare -g FILES_DIR="${FILES_DIR:=$BUILD_ROOT/src/files}"
  611. declare -g BACKUP_DIR="$SCRIPT_DIR/backups"
  612. # This could be dangerous
  613. if [[ $BUILD_ROOT == "/" ]]; then
  614. echo "Invalid --buildroot"
  615. exit 1
  616. fi
  617. for dir in "$BUILD_ROOT/src" "$BUILD_ROOT/bin"; do
  618. [[ -d "$dir" ]] || execute mkdir -p "$dir"
  619. done
  620. # Allow --reset without a profile
  621. if ((RESET)) && [[ ${#PROFILES} -lt 1 ]]; then
  622. for d in "$BUILD_ROOT/src" "$BUILD_ROOT/bin"; do
  623. ask_ok "Remove $d?" && execute rm -rf "$d"
  624. done
  625. exit $?
  626. fi
  627. install_dependencies
  628. for profile in "${PROFILES[@]}"; do
  629. debug "Running profile: $profile"
  630. if [[ ! ${!profile@a} = A ]]; then
  631. echo "Profile '$profile' does not exist"
  632. return 1
  633. fi
  634. # Store profile in P_ARR nameref
  635. declare -gn P_ARR="$profile"
  636. declare -g FILESYSTEM="${P_ARR[filesystem]:="squashfs"}"
  637. declare -g TARGET="${P_ARR[target]}"
  638. declare -g DEVICE="${P_ARR[device]}"
  639. declare -g PACKAGES="${P_ARR[packages]:-}"
  640. declare -g RELEASE="${USER_RELEASE:=${P_ARR[release]:=$RELEASE}}"
  641. # normalize RELEASE
  642. case "$RELEASE" in
  643. snapshot|latest|main|master) RELEASE="snapshot" ;;
  644. v[0-9][0-9].[0-9][0-9].*) RELEASE="${RELEASE#v}" ;;
  645. [0-9][0-9].[0-9][0-9].*) ;;
  646. *)
  647. if ! ((FROM_SOURCE)); then
  648. echo "Error: Invalid release version format"
  649. echo "Use semantic version, tag, or 'snapshot'"
  650. exit 1
  651. fi
  652. ;;
  653. esac
  654. declare -g SRC_DIR="$BUILD_ROOT/src/openwrt"
  655. declare -g WORKTREE_DIR="$BUILD_ROOT/src/$profile/$RELEASE-src"
  656. declare -g BUILD_DIR="$BUILD_ROOT/src/$profile/$RELEASE"
  657. declare -g BIN_DIR="$BUILD_ROOT/bin/$profile/$RELEASE"
  658. if [[ "$RELEASE" == "snapshot" ]]; then
  659. local url_prefix="https://downloads.openwrt.org/snapshots/targets/$TARGET"
  660. local url_filename="openwrt-imagebuilder-${TARGET//\//-}.Linux-x86_64.tar.zst"
  661. local img_fname="openwrt-${TARGET//\//-}-$DEVICE-$FILESYSTEM"
  662. else
  663. local url_prefix="https://downloads.openwrt.org/releases/$RELEASE/targets/$TARGET"
  664. local url_filename="openwrt-imagebuilder-$RELEASE-${TARGET//\//-}.Linux-x86_64.tar.zst"
  665. local img_fname="openwrt-$RELEASE-${TARGET//\//-}-$DEVICE-$FILESYSTEM"
  666. fi
  667. local ib_url="$url_prefix/$url_filename"
  668. local ib_file="$BUILD_DIR/$url_filename"
  669. local ib_sha256_url="$url_prefix/sha256sums"
  670. local ib_sha256_file="$BUILD_DIR/sha256sums"
  671. local seed_url="$url_prefix/config.buildinfo"
  672. if ((FROM_SOURCE)); then
  673. declare -g SYSUPGRADEIMGGZ="$BIN_DIR/targets/$TARGET/$img_fname-sysupgrade.img.gz"
  674. else
  675. declare -g SYSUPGRADEIMGGZ="$BUILD_DIR/$img_fname-sysupgrade.img.gz"
  676. fi
  677. backup "$SYSUPGRADEIMGGZ" "$BACKUP_DIR/$profile/$RELEASE"
  678. if ((RESET)); then
  679. if ((FROM_SOURCE)); then
  680. [[ -d $WORKTREE_DIR ]] && ask_ok "Remove $WORKTREE_DIR?"
  681. execute git worktree remove --force "$WORKTREE_DIR"
  682. execute rm -rf "$WORKTREE_DIR"
  683. elif [[ -d $BUILD_DIR ]] && ask_ok "Remove $BUILD_DIR?"; then
  684. execute rm -rf "$BUILD_DIR"
  685. fi
  686. fi
  687. if ((DEBUG)); then
  688. echo "Profile settings:"
  689. for x in "${!P_ARR[@]}"; do printf "%s=%s\n" "$x" "${P_ARR[$x]}"; done
  690. echo "Environment variables:"
  691. declare -p
  692. fi
  693. if ((FROM_SOURCE)); then
  694. from_source "$seed_url" || return $?
  695. else
  696. [[ -d $BUILD_DIR ]] || mkdir -p "$BUILD_DIR"
  697. get_imagebuilder "$ib_url" "$ib_file" "$ib_sha256_url" "$ib_sha256_file" &&
  698. verify "$ib_file" "$ib_sha256_file" &&
  699. extract "$ib_file" "$BUILD_DIR" || return $?
  700. add_repos
  701. make_images
  702. # Verify output image for stock builds (in testing)
  703. if [[ ! -v P_ARR[packages] || -z ${P_ARR[packages]} ]]; then
  704. shopt -s nullglob
  705. local -a outfiles=("$BIN_DIR"/*.img.gz "$BIN_DIR"/*.img)
  706. shopt -u nullglob
  707. for outfile in "${outfiles[@]}"; do
  708. verify "$outfile" "$ib_sha256_file" || return 1
  709. done
  710. fi
  711. #copyFiles
  712. fi
  713. [[ -v SSH_BACKUP_PATH ]] && ssh_backup
  714. [[ -v SSH_UPGRADE_PATH ]] && ssh_upgrade "$SYSUPGRADEIMGGZ" "$SSH_UPGRADE_PATH"
  715. [[ -v FLASH_DEV ]] && flash_images "$SYSUPGRADEIMGGZ" "$FLASH_DEV"
  716. done
  717. }
  718. main "$@"
  719. exit