openwrtbuilder 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825
  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. 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. # @description Acquires the OpenWRT Image Builder
  302. get_imagebuilder() {
  303. debug "${FUNCNAME[0]}" "$*"
  304. local -a url_file_pairs=("$@")
  305. for ((i=0; i<${#url_file_pairs[@]}; i+=2)); do
  306. local url="${url_file_pairs[i]}"
  307. local file="${url_file_pairs[i+1]}"
  308. # Check if file exists and ask user to remove and redownload
  309. if [[ -f $file ]] && ! ask_ok "Use existing $file?"; then
  310. execute rm -f "$file"
  311. fi
  312. # Download the file if it doesn't exist
  313. if [[ ! -f "$file" ]]; then
  314. echo "Downloading $url to $file using $DL_TOOL"
  315. execute "$DL_TOOL" "-o" "$file" "$url"
  316. fi
  317. done
  318. }
  319. add_repos() {
  320. debug "${FUNCNAME[0]}"
  321. if [[ -v P_ARR[repo] ]]; then
  322. if ! grep -q "${P_ARR[repo]}" "$BUILD_DIR/repositories.conf"; then
  323. echo "${P_ARR[repo]}" >> "$BUILD_DIR/repositories.conf"
  324. fi
  325. sed -i '/option check_signature/d' "$BUILD_DIR/repositories.conf"
  326. fi
  327. }
  328. ssh_backup() {
  329. debug "${FUNCNAME[0]}"
  330. local date hostname backup_fname
  331. printf -v date '%(%Y-%m-%d-%H-%M-%S)T'
  332. hostname=$(ssh -qt "$SSH_BACKUP_PATH" echo -n \$HOSTNAME)
  333. backup_fname="backup-$hostname-$date.tar.gz"
  334. [[ -d "$FILES_DIR" ]] || execute mkdir -p "$FILES_DIR"
  335. # Make backup archive on remote
  336. if ! execute "ssh -t $SSH_BACKUP_PATH sysupgrade -b /tmp/$backup_fname"; then
  337. echo "SSH backup failed"
  338. exit 1
  339. fi
  340. # Move backup archive locally
  341. if ! execute "rsync -avz --remove-source-files $SSH_BACKUP_PATH:/tmp/$backup_fname $BUILD_DIR/"; then
  342. echo "Could not copy SSH backup"
  343. exit 1
  344. fi
  345. # Extract backup archive
  346. if ! execute "tar -C $FILES_DIR -xzf $BUILD_DIR/$backup_fname"; then
  347. echo "Could not extract SSH backup"
  348. exit 1
  349. fi
  350. execute "rm $BUILD_DIR/$backup_fname"
  351. }
  352. make_images() {
  353. debug "${FUNCNAME[0]}"
  354. local -a make_opts
  355. # Reuse the existing output
  356. # if [[ -d "$BIN_DIR" ]]; then
  357. # if ask_ok "$BIN_DIR exists. Rebuild?"; then
  358. # execute rm -rf "$BIN_DIR"
  359. # else
  360. # return 0
  361. # fi
  362. # fi
  363. ((VERBOSE)) && make_opts+=("V=s")
  364. debug make "${make_opts[@]}" image BIN_DIR="$BIN_DIR" \
  365. PROFILE="$DEVICE" PACKAGES="$PACKAGES" \
  366. FILES="$FILES_DIR" --directory="$BUILD_DIR" \
  367. --jobs="$(($(nproc) - 1))"
  368. make "${make_opts[@]}" image \
  369. BIN_DIR="$BINDIR" \
  370. PROFILE="$DEVICE" \
  371. PACKAGES="$PACKAGES" \
  372. FILES="$FILES_DIR" \
  373. --directory="$BUILD_DIR" \
  374. --jobs="$(($(nproc) - 1))" \
  375. > "$BUILD_DIR/make.log"
  376. }
  377. flash_images() {
  378. debug "${FUNCNAME[0]}"
  379. local img_gz="$1"
  380. local dev="$2"
  381. local img="${img_gz%.gz}"
  382. local partitions
  383. if [[ ! -e "$dev" ]]; then
  384. echo "The device specified by --flash could not be found"
  385. return 1
  386. fi
  387. [[ -f $img_gz ]] || { echo "$img_gz does not exist"; return 1; }
  388. execute gunzip -qfk "$img_gz"
  389. echo "Unmounting target device $dev partitions"
  390. partitions=("$dev"?*)
  391. execute sudo umount "${partitions[@]}"
  392. if execute sudo dd if="$img" of="$dev" bs=2M conv=fsync; then
  393. sync
  394. echo "Image flashed successfully!"
  395. else
  396. echo "dd failed!"
  397. return 1
  398. fi
  399. }
  400. ssh_upgrade() {
  401. debug "${FUNCNAME[0]}"
  402. local img_gz="$1"
  403. local ssh_path="$2"
  404. local img_fname="${img_gz##*/}"
  405. [[ -f $img_gz ]] || { echo "$img_gz is missing, check build output"; return 1; }
  406. echo "Copying '$img_gz' to $ssh_path/tmp/$img_fname"
  407. if ! execute scp "$img_gz" "$ssh_path:/tmp/$img_fname"; then
  408. echo "Could not copy $img_gz to $ssh_path:/tmp/$img_fname"
  409. return 1
  410. fi
  411. echo "Executing remote sysupgrade"
  412. # This may result in weird exit code from closing the ssh connection
  413. # shellcheck disable=SC2029
  414. ssh "$ssh_path" "sysupgrade -F /tmp/$img_fname"
  415. return 0
  416. }
  417. # @description Builds OpenWRT from source code using the the default buildbot as base
  418. # This enables the use of kernel config options in profiles
  419. # @arg $1 string .config seed URL
  420. from_source() {
  421. debug "${FUNCNAME[0]}" "$*"
  422. local seed_url="$1"
  423. local src_url="https://github.com/openwrt/openwrt.git"
  424. local seed_file="$WORKTREE_DIR/.config"
  425. local pkg config commit seed_file wt_commit description
  426. local -a make_opts config_opts
  427. echo "Building from source is under development"
  428. # Update source code
  429. if [[ ! -d "$SRC_DIR" ]]; then
  430. execute mkdir -p "$SRC_DIR"
  431. execute git clone "$src_url" "$SRC_DIR"
  432. fi
  433. git -C "$SRC_DIR" pull
  434. # Generate commitish for git worktree
  435. case "$RELEASE" in
  436. snapshot) wt_commit="origin/main" ;;
  437. [0-9][0-9].[0-9][0-9].*)
  438. local branch="openwrt-${RELEASE%.*}"
  439. local tag="v$RELEASE"
  440. if ask_ok "Use $branch branch HEAD (y, recommended) or $tag tag (N)?"; then
  441. wt_commit="origin/$branch"
  442. else
  443. wt_commit="$tag"
  444. fi
  445. ;;
  446. *)
  447. debug "Passing '$RELEASE' commit-ish to git worktree"
  448. wt_commit="$RELEASE"
  449. ;;
  450. esac
  451. # TODO There's a bug in the make clean functions that seem to invoke a full make
  452. if [[ -d "$WORKTREE_DIR" ]]; then
  453. execute git -C "$WORKTREE_DIR" checkout "$wt_commit"
  454. execute git -C "$WORKTREE_DIR" pull
  455. else
  456. execute git -C "$SRC_DIR" worktree add --force --detach "$WORKTREE_DIR" "$wt_commit"
  457. fi
  458. # To workaround bug, don't use make *clean, blow it away and start fresh
  459. # [[ -d "$WORKTREE_DIR" ]] && execute rm -rf "$WORKTREE_DIR"
  460. # execute git -C "$SRC_DIR" worktree add --force --detach "$WORKTREE_DIR" "$wt_commit"
  461. # Print commit information
  462. commit=$(git -C "$WORKTREE_DIR" rev-parse HEAD)
  463. description=$(git -C "$WORKTREE_DIR" describe)
  464. echo "Current commit hash: $commit"
  465. echo "Git worktree description: $description"
  466. ((DEBUG)) && git --no-pager -C "$WORKTREE_DIR" log -1
  467. # Enter worktree
  468. execute pushd "$WORKTREE_DIR" || return 1
  469. # Update package feed
  470. ./scripts/feeds update -i -f &&
  471. ./scripts/feeds update -a -f &&
  472. ./scripts/feeds install -a -f
  473. # Cleanup build environment
  474. ((VERBOSE)) && make_opts+=("V=s")
  475. execute make "${make_opts[@]}" "-j1" distclean # TODO 'dirclean' has a bug that triggers menuconfig
  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. # Cleaning modes
  520. # make clean # compiled output
  521. # make targetclean # compiled output, toolchain
  522. # make dirclean # compiled output, toolchain, build tools
  523. # make distclean # compiled output, toolchain, build tools, .config, feeds, .ccache
  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_opts+=("-j$(($(nproc)+1))")
  529. # Make image
  530. if ! execute ionice -c 3 chrt --idle 0 nice -n19 make "${make_opts[@]}" world; then
  531. echo "Error: make failed"
  532. return 1
  533. fi
  534. popd || return 1
  535. # Symlink output images to root of BIN_DIR (match Image Builder)
  536. shopt -s nullglob
  537. for image in "$BIN_DIR/targets/${TARGET}/"*.{img,img.gz,ubi}; do
  538. execute ln -fs "$image" "$BIN_DIR/${image##*/}"
  539. done
  540. shopt -u nullglob
  541. return 0
  542. }
  543. # @description Backs up a file to a chosen directory using its timestamp
  544. # @arg $1 string File to backup
  545. # @arg $2 string Directory to backup to
  546. backup() {
  547. debug "${FUNCNAME[0]}" "$*"
  548. local file="$1" dir="$2" count=1
  549. local creation_date base_name backup_file
  550. [[ -f $file ]] || { debug "File not found: $file"; return 1; }
  551. [[ -d $dir ]] || execute mkdir -p "$dir" || { debug "Failed to create directory: $dir"; return 1; }
  552. if creation_date=$(stat -c %w "$file" 2>/dev/null || stat -c %y "$file" 2>/dev/null) && \
  553. [[ $creation_date != "-" && -n $creation_date ]] && \
  554. creation_date=$(date -d "$creation_date" +%y%m%d%H%M 2>/dev/null); then
  555. debug "Creation date: $creation_date"
  556. else
  557. creation_date="unknown"
  558. debug "Unable to determine creation date, using 'unknown'"
  559. fi
  560. base_name="${file##*/}"
  561. backup_file="$dir/$creation_date-$base_name.bak"
  562. while [[ -e $backup_file ]]; do
  563. ((count++))
  564. backup_file="$dir/$creation_date-$base_name.bak$count"
  565. done
  566. if execute mv "$file" "$backup_file"; then
  567. debug "Backup created: $backup_file"
  568. return 0
  569. else
  570. debug "Failed to create backup for: $file"
  571. return 1
  572. fi
  573. }
  574. # @section Helper functions
  575. # @internal
  576. debug() { ((DEBUG)) && echo "Debug: $*"; }
  577. ask_ok() {
  578. ((YES)) && return
  579. local r
  580. read -r -p "$* [y/N]: " r
  581. r=${r,,}
  582. [[ "$r" =~ ^(yes|y)$ ]]
  583. }
  584. extract() {
  585. local archive="$1"
  586. local out_dir="$2"
  587. if ! execute tar -axf "$archive" -C "$out_dir" --strip-components 1; then
  588. echo "Extraction failed"
  589. return 1
  590. fi
  591. }
  592. verify() {
  593. local file_to_check="$1" sum_file="$2"
  594. local checksum
  595. command -v sha256sum &>/dev/null || return 1
  596. [[ -f $sum_file && -f $file_to_check ]] || return 1
  597. checksum=$(grep "${file_to_check##*/}" "$sum_file" | cut -f1 -d' ')
  598. echo -n "$checksum $file_to_check" | sha256sum --check --status
  599. }
  600. load() {
  601. local source_file="$1"
  602. # shellcheck disable=SC1090
  603. [[ -f $source_file ]] && source "$source_file"
  604. }
  605. execute() {
  606. if debug "$*"; then
  607. "$@"
  608. else
  609. "$@" &>/dev/null
  610. fi
  611. }
  612. # @description The openwrtbuilder main function
  613. # @internal
  614. main() {
  615. debug "${FUNCNAME[0]}"
  616. init
  617. load "$SCRIPT_DIR/profiles"
  618. parse_input "$@"
  619. # Fallback to SCRIPT_DIR if BUILD_ROOT has not been set
  620. declare -g BUILD_ROOT="${BUILD_ROOT:=$SCRIPT_DIR}"
  621. declare -g FILES_DIR="${FILES_DIR:=$BUILD_ROOT/src/files}"
  622. declare -g BACKUP_DIR="$SCRIPT_DIR/backups"
  623. # This could be dangerous
  624. if [[ $BUILD_ROOT == "/" ]]; then
  625. echo "Invalid --buildroot"
  626. exit 1
  627. fi
  628. for dir in "$BUILD_ROOT/src" "$BUILD_ROOT/bin"; do
  629. [[ -d "$dir" ]] || execute mkdir -p "$dir"
  630. done
  631. # Allow --reset without a profile
  632. if ((RESET)) && [[ ${#PROFILES} -lt 1 ]]; then
  633. for d in "$BUILD_ROOT/src" "$BUILD_ROOT/bin"; do
  634. ask_ok "Remove $d?" && execute rm -rf "$d"
  635. done
  636. exit $?
  637. fi
  638. install_dependencies
  639. for profile in "${PROFILES[@]}"; do
  640. debug "Running profile: $profile"
  641. if [[ ! ${!profile@a} = A ]]; then
  642. echo "Profile '$profile' does not exist"
  643. return 1
  644. fi
  645. # Store profile in P_ARR nameref
  646. declare -gn P_ARR="$profile"
  647. declare -g FILESYSTEM="${P_ARR[filesystem]:="squashfs"}"
  648. declare -g TARGET="${P_ARR[target]}"
  649. declare -g DEVICE="${P_ARR[device]}"
  650. declare -g PACKAGES="${P_ARR[packages]:-}"
  651. declare -g RELEASE="${USER_RELEASE:=${P_ARR[release]:=$RELEASE}}"
  652. # normalize RELEASE
  653. case "$RELEASE" in
  654. snapshot|latest|main|master) RELEASE="snapshot" ;;
  655. v[0-9][0-9].[0-9][0-9].*) RELEASE="${RELEASE#v}" ;;
  656. [0-9][0-9].[0-9][0-9].*) ;;
  657. *)
  658. if ! ((FROM_SOURCE)); then
  659. echo "Error: Invalid release version format"
  660. echo "Use semantic version, tag, or 'snapshot'"
  661. exit 1
  662. fi
  663. ;;
  664. esac
  665. declare -g SRC_DIR="$BUILD_ROOT/src/openwrt"
  666. declare -g WORKTREE_DIR="$BUILD_ROOT/src/$profile/$RELEASE-src"
  667. declare -g BUILD_DIR="$BUILD_ROOT/src/$profile/$RELEASE"
  668. declare -g BIN_DIR="$BUILD_ROOT/bin/$profile/$RELEASE"
  669. if [[ "$RELEASE" == "snapshot" ]]; then
  670. local url_prefix="https://downloads.openwrt.org/snapshots/targets/$TARGET"
  671. local url_filename="openwrt-imagebuilder-${TARGET//\//-}.Linux-x86_64.tar.zst"
  672. local img_fname="openwrt-${TARGET//\//-}-$DEVICE-$FILESYSTEM"
  673. else
  674. local url_prefix="https://downloads.openwrt.org/releases/$RELEASE/targets/$TARGET"
  675. local url_filename="openwrt-imagebuilder-$RELEASE-${TARGET//\//-}.Linux-x86_64.tar.xz"
  676. local img_fname="openwrt-$RELEASE-${TARGET//\//-}-$DEVICE-$FILESYSTEM"
  677. fi
  678. local ib_url="$url_prefix/$url_filename"
  679. local ib_file="$BUILD_DIR/$url_filename"
  680. local ib_sha256_url="$url_prefix/sha256sums"
  681. local ib_sha256_file="$BUILD_DIR/sha256sums"
  682. local seed_url="$url_prefix/config.buildinfo"
  683. if ((FROM_SOURCE)); then
  684. declare -g SYSUPGRADEIMGGZ="$BIN_DIR/targets/$TARGET/$img_fname-sysupgrade.img.gz"
  685. else
  686. declare -g SYSUPGRADEIMGGZ="$BUILD_DIR/$img_fname-sysupgrade.img.gz"
  687. fi
  688. backup "$SYSUPGRADEIMGGZ" "$BACKUP_DIR/$profile/$RELEASE"
  689. if ((RESET)); then
  690. if ((FROM_SOURCE)); then
  691. [[ -d $WORKTREE_DIR ]] && ask_ok "Remove $WORKTREE_DIR?"
  692. execute git worktree remove --force "$WORKTREE_DIR"
  693. execute rm -rf "$WORKTREE_DIR"
  694. elif [[ -d $BUILD_DIR ]] && ask_ok "Remove $BUILD_DIR?"; then
  695. execute rm -rf "$BUILD_DIR"
  696. fi
  697. fi
  698. if ((DEBUG)); then
  699. echo "Profile settings:"
  700. for x in "${!P_ARR[@]}"; do printf "%s=%s\n" "$x" "${P_ARR[$x]}"; done
  701. echo "Environment variables:"
  702. declare -p
  703. fi
  704. if ((FROM_SOURCE)); then
  705. from_source "$seed_url" || return $?
  706. else
  707. [[ -d $BUILD_DIR ]] || mkdir -p "$BUILD_DIR"
  708. get_imagebuilder "$ib_url" "$ib_file" "$ib_sha256_url" "$ib_sha256_file" &&
  709. verify "$ib_file" "$ib_sha256_file" &&
  710. extract "$ib_file" "$BUILD_DIR" || return $?
  711. add_repos
  712. make_images
  713. # Verify output image for stock builds (in testing)
  714. if [[ ! -v P_ARR[packages] || -z ${P_ARR[packages]} ]]; then
  715. shopt -s nullglob
  716. local -a outfiles=("$BIN_DIR"/*.img.gz "$BIN_DIR"/*.img)
  717. shopt -u nullglob
  718. for outfile in "${outfiles[@]}"; do
  719. verify "$outfile" "$ib_sha256_file" || return 1
  720. done
  721. fi
  722. #copyFiles
  723. fi
  724. [[ -v SSH_BACKUP_PATH ]] && ssh_backup
  725. [[ -v SSH_UPGRADE_PATH ]] && ssh_upgrade "$SYSUPGRADEIMGGZ" "$SSH_UPGRADE_PATH"
  726. [[ -v FLASH_DEV ]] && flash_images "$SYSUPGRADEIMGGZ" "$FLASH_DEV"
  727. done
  728. }
  729. main "$@"
  730. exit