openwrtbuilder 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828
  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 see:
  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. clang # for qosify
  162. diffutils
  163. gcc
  164. gcc-c++
  165. git
  166. libev-devel
  167. libtirpc-devel
  168. llvm15-libs
  169. make
  170. ncurses-devel
  171. net-snmp-devel
  172. pam-devel
  173. patch
  174. patch
  175. perl
  176. perl-Data-Dumper
  177. perl-File-Compare
  178. perl-File-Copy
  179. perl-FindBin
  180. perl-IPC-Cmd
  181. perl-JSON-PP
  182. perl-Thread-Queue
  183. perl-Time-Piece
  184. perl-base
  185. python3
  186. python3-devel
  187. python3-pyelftools
  188. python3-setuptools
  189. rsync
  190. swig
  191. tar
  192. unzip
  193. wget
  194. which
  195. xz-devel
  196. ) ;;
  197. debian|ubuntu)
  198. pkg_list+=(
  199. build-essential
  200. clang
  201. file
  202. flex
  203. g++
  204. gawk
  205. gcc-multilib
  206. gettext
  207. git
  208. libev-dev
  209. liblzma-dev
  210. libncurses5-dev
  211. libpam-dev
  212. libsnmp-dev
  213. libssl-dev
  214. libtirpc-dev
  215. python3-distutils
  216. rsync
  217. patch
  218. unzip
  219. wget
  220. zlib1g-dev
  221. ) ;;
  222. arch)
  223. pkg_list+=(
  224. autoconf
  225. automake
  226. base-devel
  227. bash
  228. binutils
  229. bison
  230. bzip2
  231. clang
  232. fakeroot
  233. file
  234. findutils
  235. flex
  236. gawk
  237. gcc
  238. gettext
  239. git
  240. grep
  241. groff
  242. gzip
  243. libelf
  244. libev
  245. libtirpc
  246. libtool
  247. libxslt
  248. m4
  249. make
  250. ncurses
  251. net-snmp
  252. openssl
  253. pam
  254. patch
  255. patch
  256. pkgconf
  257. python
  258. rsync
  259. sed
  260. texinfo
  261. time
  262. unzip
  263. util-linux
  264. wget
  265. which
  266. xz
  267. zlib
  268. ) ;;
  269. *) debug "Unsupported OS, skipping dependencies"; return 1 ;;
  270. esac
  271. else
  272. # For Imagebuilder
  273. case "$ID" in
  274. fedora|centos)
  275. pkg_list+=(
  276. @c-development
  277. @development-tools
  278. @development-libs
  279. perl-FindBin
  280. zlib-static
  281. elfutils-libelf-devel
  282. gawk
  283. unzip
  284. file
  285. wget
  286. python3
  287. axel
  288. perl-IPC-Cmd
  289. zstd) ;;
  290. debian|ubuntu)
  291. pkg_list+=(
  292. build-essential
  293. libncurses5-dev
  294. libncursesw5-dev
  295. zlib1g-dev
  296. gawk
  297. git
  298. gettext
  299. libssl-dev
  300. xsltproc
  301. wget
  302. unzip
  303. python
  304. axel
  305. zstd) ;;
  306. *) debug "Unsupported OS for automatic dependency install"; return 1 ;;
  307. esac
  308. fi
  309. pkg_install "${pkg_list[@]}" && echo "${pkg_list[@]}" > "$lock_file"
  310. fi
  311. }
  312. # @description Acquires the OpenWRT Image Builder
  313. get_imagebuilder() {
  314. debug "${FUNCNAME[0]}" "$*"
  315. local -a url_file_pairs=("$@")
  316. for ((i=0; i<${#url_file_pairs[@]}; i+=2)); do
  317. local url="${url_file_pairs[i]}"
  318. local file="${url_file_pairs[i+1]}"
  319. # Check if file exists and ask user to remove and redownload
  320. if [[ -f $file ]] && ! ask_ok "Use existing $file?"; then
  321. execute rm -f "$file"
  322. fi
  323. # Download the file if it doesn't exist
  324. if [[ ! -f "$file" ]]; then
  325. echo "Downloading $url to $file using $DL_TOOL"
  326. execute "$DL_TOOL" "-o" "$file" "$url"
  327. fi
  328. done
  329. }
  330. add_repos() {
  331. debug "${FUNCNAME[0]}"
  332. if [[ -v P_ARR[repo] ]]; then
  333. if ! grep -q "${P_ARR[repo]}" "$BUILD_DIR/repositories.conf"; then
  334. echo "${P_ARR[repo]}" >> "$BUILD_DIR/repositories.conf"
  335. fi
  336. sed -i '/option check_signature/d' "$BUILD_DIR/repositories.conf"
  337. fi
  338. }
  339. ssh_backup() {
  340. debug "${FUNCNAME[0]}"
  341. local date hostname backup_fname
  342. printf -v date '%(%Y-%m-%d-%H-%M-%S)T'
  343. hostname=$(ssh -qt "$SSH_BACKUP_PATH" echo -n \$HOSTNAME)
  344. backup_fname="backup-$hostname-$date.tar.gz"
  345. [[ -d "$FILES_DIR" ]] || execute mkdir -p "$FILES_DIR"
  346. # Make backup archive on remote
  347. if ! execute "ssh -t $SSH_BACKUP_PATH sysupgrade -b /tmp/$backup_fname"; then
  348. echo "SSH backup failed"
  349. exit 1
  350. fi
  351. # Move backup archive locally
  352. if ! execute "rsync -avz --remove-source-files $SSH_BACKUP_PATH:/tmp/$backup_fname $BUILD_DIR/"; then
  353. echo "Could not copy SSH backup"
  354. exit 1
  355. fi
  356. # Extract backup archive
  357. if ! execute "tar -C $FILES_DIR -xzf $BUILD_DIR/$backup_fname"; then
  358. echo "Could not extract SSH backup"
  359. exit 1
  360. fi
  361. execute "rm $BUILD_DIR/$backup_fname"
  362. }
  363. make_images() {
  364. debug "${FUNCNAME[0]}"
  365. local -a make_opts
  366. # Reuse the existing output
  367. # if [[ -d "$BIN_DIR" ]]; then
  368. # if ask_ok "$BIN_DIR exists. Rebuild?"; then
  369. # execute rm -rf "$BIN_DIR"
  370. # else
  371. # return 0
  372. # fi
  373. # fi
  374. ((DEBUG)) && make_opts+=("V=sc")
  375. debug make "${make_opts[@]}" image BIN_DIR="$BIN_DIR" \
  376. PROFILE="$DEVICE" PACKAGES="$PACKAGES" \
  377. FILES="$FILES_DIR" --directory="$BUILD_DIR" \
  378. --jobs="$(($(nproc) - 1))"
  379. make "${make_opts[@]}" image \
  380. BIN_DIR="$BIN_DIR" \
  381. PROFILE="$DEVICE" \
  382. PACKAGES="$PACKAGES" \
  383. FILES="$FILES_DIR" \
  384. --directory="$BUILD_DIR" \
  385. --jobs="$(($(nproc) - 1))" \
  386. > "$BUILD_DIR/make.log"
  387. }
  388. flash_images() {
  389. debug "${FUNCNAME[0]}"
  390. local img_gz="$1"
  391. local dev="$2"
  392. local img="${img_gz%.gz}"
  393. local partitions
  394. if [[ ! -e "$dev" ]]; then
  395. echo "The device specified by --flash could not be found"
  396. return 1
  397. fi
  398. [[ -f $img_gz ]] || { echo "$img_gz does not exist"; return 1; }
  399. execute gunzip -qfk "$img_gz"
  400. echo "Unmounting target device $dev partitions"
  401. partitions=("$dev"?*)
  402. execute sudo umount "${partitions[@]}"
  403. if execute sudo dd if="$img" of="$dev" bs=2M conv=fsync; then
  404. sync
  405. echo "Image flashed successfully!"
  406. else
  407. echo "dd failed!"
  408. return 1
  409. fi
  410. }
  411. ssh_upgrade() {
  412. debug "${FUNCNAME[0]}"
  413. local img_gz="$1"
  414. local ssh_path="$2"
  415. local img_fname="${img_gz##*/}"
  416. [[ -f $img_gz ]] || { echo "$img_gz is missing, check build output"; return 1; }
  417. echo "Copying '$img_gz' to $ssh_path/tmp/$img_fname"
  418. if ! execute scp "$img_gz" "$ssh_path:/tmp/$img_fname"; then
  419. echo "Could not copy $img_gz to $ssh_path:/tmp/$img_fname"
  420. return 1
  421. fi
  422. echo "Executing remote sysupgrade"
  423. # This may result in weird exit code from closing the ssh connection
  424. # shellcheck disable=SC2029
  425. ssh "$ssh_path" "sysupgrade -F /tmp/$img_fname"
  426. return 0
  427. }
  428. # @description Builds OpenWRT from source code using the the default buildbot as base
  429. # This enables the use of kernel config options in profiles
  430. # @arg $1 string .config seed URL
  431. from_source() {
  432. debug "${FUNCNAME[0]}" "$*"
  433. local seed_url="$1"
  434. local src_url="https://github.com/openwrt/openwrt.git"
  435. local seed_file="$WORKTREE_DIR/.config"
  436. local worktree_name; worktree_name="$(basename "$WORKTREE_DIR")"
  437. local worktree_meta="$SRC_DIR/.git/worktrees/$worktree_name"
  438. local pkg config commit seed_file wt_commit description
  439. local -a make_opts config_opts
  440. echo "Building from source is under development"
  441. if ((DEBUG)); then
  442. echo "Profile settings:"
  443. for x in "${!P_ARR[@]}"; do printf "%s=%s\n" "$x" "${P_ARR[$x]}"; done
  444. echo "Environment variables:"
  445. declare -p
  446. fi
  447. if ((RESET)); then
  448. if [[ -d "$WORKTREE_DIR" || -d "$worktree_meta" ]]; then
  449. execute git -C "$SRC_DIR" worktree remove --force --force "$WORKTREE_DIR"
  450. [[ -d "$WORKTREE_DIR" ]] && execute rm -rf "$WORKTREE_DIR"
  451. [[ -d "$worktree_meta" ]] && execute rm -rf "$worktree_meta"
  452. fi
  453. [[ -d "$BUILD_DIR" ]] && execute rm -rf "$BUILD_DIR"
  454. fi
  455. # Update source code
  456. if [[ ! -d "$SRC_DIR" ]]; then
  457. execute mkdir -p "$SRC_DIR"
  458. execute git clone "$src_url" "$SRC_DIR"
  459. fi
  460. execute git -C "$SRC_DIR" worktree prune --verbose
  461. execute git -C "$SRC_DIR" pull
  462. # Generate commitish for git worktree
  463. case "$RELEASE" in
  464. snapshot) wt_commit="origin/main" ;;
  465. [0-9][0-9].[0-9][0-9].*)
  466. local branch="openwrt-${RELEASE%.*}"
  467. local tag="v$RELEASE"
  468. if ask_ok "Use $branch branch HEAD (y, recommended) or $tag tag (n)?"; then
  469. wt_commit="origin/$branch"
  470. else
  471. wt_commit="$tag"
  472. fi
  473. ;;
  474. *)
  475. debug "Passing '$RELEASE' commit-ish to git worktree"
  476. wt_commit="$RELEASE"
  477. ;;
  478. esac
  479. # Pull existing or add new git worktree
  480. if [[ -d "$WORKTREE_DIR" && -d "$worktree_meta" ]]; then
  481. execute git -C "$WORKTREE_DIR" checkout "$wt_commit"
  482. execute git -C "$WORKTREE_DIR" pull
  483. else
  484. execute git -C "$SRC_DIR" worktree add --force --force --detach "$WORKTREE_DIR" "$wt_commit"
  485. fi
  486. # Print commit information
  487. commit=$(git -C "$WORKTREE_DIR" rev-parse HEAD)
  488. description=$(git -C "$WORKTREE_DIR" describe)
  489. echo "Current commit hash: $commit"
  490. echo "Git worktree description: $description"
  491. ((DEBUG)) && git --no-pager -C "$WORKTREE_DIR" log -1
  492. # Enter worktree
  493. execute pushd "$WORKTREE_DIR" || return 1
  494. # Cleanup build environment
  495. ((DEBUG)) && make_opts+=("V=sc")
  496. execute make "${make_opts[@]}" "-j1" distclean
  497. # make clean # compiled output
  498. # make targetclean # compiled output, toolchain
  499. # make dirclean # compiled output, toolchain, build tools
  500. # make distclean # compiled output, toolchain, build tools, .config, feeds, .ccache
  501. # Use a custom (faster) mirror
  502. # execute sed -i -E 's;git.openwrt.org/(feed|project);github.com/openwrt;' feeds.conf.default
  503. # Update package feed
  504. ./scripts/feeds update -i -f &&
  505. ./scripts/feeds update -a -f &&
  506. ./scripts/feeds install -a -f
  507. # Grab the release seed config
  508. if ! execute "$DL_TOOL" "-o" "$seed_file" "$seed_url"; then
  509. echo "Could not obtain $seed_file from $seed_url"
  510. return 1
  511. fi
  512. # Set compilation output dir
  513. config_opts+=("CONFIG_BINARY_FOLDER=\"$BIN_DIR\"")
  514. # Add custom packages
  515. for pkg in $PACKAGES; do
  516. if [[ $pkg == -* ]]; then
  517. config_opts+=("CONFIG_PACKAGE_${pkg#-}=n") # remove package
  518. else
  519. config_opts+=("CONFIG_PACKAGE_$pkg=y") # add package
  520. fi
  521. done
  522. # Add config options from profile
  523. for config in ${P_ARR[config]}; do
  524. config_opts+=("$config")
  525. done
  526. # Only compile selected fs
  527. execute sed -i '/CONFIG_TARGET_ROOTFS_/d' "$seed_file"
  528. config_opts+=("CONFIG_TARGET_PER_DEVICE_ROOTFS=n")
  529. if [[ $FILESYSTEM == "squashfs" ]]; then
  530. config_opts+=("CONFIG_TARGET_ROOTFS_EXT4FS=n")
  531. config_opts+=("CONFIG_TARGET_ROOTFS_SQUASHFS=y")
  532. elif [[ $FILESYSTEM == "ext4" ]]; then
  533. config_opts+=("CONFIG_TARGET_ROOTFS_SQUASHFS=n")
  534. config_opts+=("CONFIG_TARGET_ROOTFS_EXT4FS=y")
  535. fi
  536. # Only compile selected target image
  537. execute sed -i '/CONFIG_TARGET_DEVICE_/d' "$seed_file"
  538. config_opts+=("CONFIG_TARGET_MULTI_PROFILE=n")
  539. config_opts+=("CONFIG_TARGET_PROFILE=DEVICE_$DEVICE")
  540. config_opts+=("CONFIG_TARGET_${TARGET//\//_}_DEVICE_$DEVICE=y")
  541. config_opts+=("CONFIG_SDK=n")
  542. config_opts+=("CONFIG_SDK_LLVM_BPF=n")
  543. config_opts+=("CONFIG_IB=n")
  544. config_opts+=("CONFIG_MAKE_TOOLCHAIN=n")
  545. # Write options to config seed file
  546. for config in "${config_opts[@]}"; do
  547. debug "Writing $config to $seed_file"
  548. echo "$config" >> "$seed_file"
  549. done
  550. # Make prep
  551. execute make "${make_opts[@]}" "-j1" defconfig
  552. execute make "${make_opts[@]}" "-j1" download
  553. ((DEBUG)) && make_opts+=("-j1") || make_opts+=("-j$(($(nproc)-1))")
  554. # Make image
  555. if ! execute ionice -c 3 chrt --idle 0 nice -n19 make "${make_opts[@]}" world; then
  556. echo "Error: make failed"
  557. return 1
  558. fi
  559. execute popd || return 1
  560. # Symlink output images to root of BIN_DIR (match Image Builder)
  561. shopt -s nullglob
  562. for image in "$BIN_DIR/targets/${TARGET}/"*.{img,img.gz,ubi}; do
  563. execute ln -fs "$image" "$BIN_DIR/${image##*/}"
  564. done
  565. shopt -u nullglob
  566. return 0
  567. }
  568. # @description Backs up a file to a chosen directory using its timestamp
  569. # @arg $1 string File to backup
  570. # @arg $2 string Directory to backup to
  571. backup() {
  572. debug "${FUNCNAME[0]}" "$*"
  573. local file="$1" dir="$2"
  574. local creation_date base_name backup_file
  575. [[ -f $file ]] || { debug "File not found: $file"; return 1; }
  576. [[ -d $dir ]] || execute mkdir -p "$dir" || { debug "Failed to create directory: $dir"; return 1; }
  577. if creation_date=$(stat -c %w "$file" 2>/dev/null || stat -c %y "$file" 2>/dev/null) && \
  578. [[ $creation_date != "-" && -n $creation_date ]] && \
  579. creation_date=$(date -d "$creation_date" +%y%m%d%H%M 2>/dev/null); then
  580. debug "Creation date: $creation_date"
  581. else
  582. creation_date="unknown"
  583. debug "Unable to determine creation date, using 'unknown'"
  584. fi
  585. base_name="${file##*/}"
  586. backup_file="$dir/$creation_date-$base_name"
  587. [[ -f $backup_file ]] || execute cp --archive "$file" "$backup_file"
  588. }
  589. # @section Helper functions
  590. # @internal
  591. debug() { ((DEBUG)) && echo "Debug: $*"; }
  592. ask_ok() {
  593. ((YES)) && return
  594. local r
  595. read -r -p "$* [y/N]: " r
  596. r=${r,,}
  597. [[ "$r" =~ ^(yes|y)$ ]]
  598. }
  599. extract() {
  600. local archive="$1"
  601. local out_dir="$2"
  602. if ! execute tar -axf "$archive" -C "$out_dir" --strip-components 1; then
  603. echo "Extraction failed"
  604. return 1
  605. fi
  606. }
  607. verify() {
  608. local file_to_check="$1" sum_file="$2"
  609. local checksum
  610. command -v sha256sum &>/dev/null || return 1
  611. [[ -f $sum_file && -f $file_to_check ]] || return 1
  612. checksum=$(grep "${file_to_check##*/}" "$sum_file" | cut -f1 -d' ')
  613. echo -n "$checksum $file_to_check" | sha256sum --check --status
  614. }
  615. load() {
  616. local source_file="$1"
  617. # shellcheck disable=SC1090
  618. [[ -f $source_file ]] && source "$source_file"
  619. }
  620. execute() {
  621. if debug "$*"; then
  622. "$@"
  623. else
  624. "$@" &>/dev/null
  625. fi
  626. }
  627. # @description The openwrtbuilder main function
  628. # @internal
  629. main() {
  630. debug "${FUNCNAME[0]}"
  631. init
  632. load "$SCRIPT_DIR/profiles"
  633. parse_input "$@"
  634. # Fallback to SCRIPT_DIR if BUILD_ROOT has not been set
  635. declare -g BUILD_ROOT="${BUILD_ROOT:=$SCRIPT_DIR}"
  636. declare -g FILES_DIR="${FILES_DIR:=$BUILD_ROOT/src/files}"
  637. declare -g BACKUP_DIR="$SCRIPT_DIR/backups"
  638. # This could be dangerous
  639. if [[ $BUILD_ROOT == "/" ]]; then
  640. echo "Invalid --buildroot"
  641. exit 1
  642. fi
  643. for dir in "$BUILD_ROOT/src" "$BUILD_ROOT/bin"; do
  644. [[ -d "$dir" ]] || execute mkdir -p "$dir"
  645. done
  646. # Allow --reset without a profile
  647. if ((RESET)) && [[ ${#PROFILES} -lt 1 ]]; then
  648. for d in "$BUILD_ROOT/src" "$BUILD_ROOT/bin"; do
  649. ask_ok "Remove $d?" && execute rm -rf "$d"
  650. done
  651. exit $?
  652. fi
  653. install_dependencies
  654. for profile in "${PROFILES[@]}"; do
  655. debug "Running profile: $profile"
  656. if [[ ! ${!profile@a} = A ]]; then
  657. echo "Profile '$profile' does not exist"
  658. return 1
  659. fi
  660. # Store profile in P_ARR nameref
  661. declare -gn P_ARR="$profile"
  662. declare -g FILESYSTEM="${P_ARR[filesystem]:="squashfs"}"
  663. declare -g TARGET="${P_ARR[target]}"
  664. declare -g DEVICE="${P_ARR[device]}"
  665. declare -g PACKAGES="${P_ARR[packages]:-}"
  666. declare -g RELEASE="${USER_RELEASE:=${P_ARR[release]:=$RELEASE}}"
  667. # normalize RELEASE
  668. case "$RELEASE" in
  669. snapshot|latest|main|master) RELEASE="snapshot" ;;
  670. v[0-9][0-9].[0-9][0-9].*) RELEASE="${RELEASE#v}" ;;
  671. [0-9][0-9].[0-9][0-9].*) ;;
  672. *)
  673. if ! ((FROM_SOURCE)); then
  674. echo "Error: Invalid release version format"
  675. echo "Use semantic version, tag, or 'snapshot'"
  676. exit 1
  677. fi
  678. ;;
  679. esac
  680. declare -g SRC_DIR="$BUILD_ROOT/src/openwrt"
  681. declare -g WORKTREE_DIR="$BUILD_ROOT/src/$profile/$RELEASE-src"
  682. declare -g BUILD_DIR="$BUILD_ROOT/src/$profile/$RELEASE"
  683. declare -g BIN_DIR="$BUILD_ROOT/bin/$profile/$RELEASE"
  684. if [[ "$RELEASE" == "snapshot" ]]; then
  685. local url_prefix="https://downloads.openwrt.org/snapshots/targets/$TARGET"
  686. local url_filename="openwrt-imagebuilder-${TARGET//\//-}.Linux-x86_64.tar.zst"
  687. local img_fname="openwrt-${TARGET//\//-}-$DEVICE-$FILESYSTEM"
  688. else
  689. local url_prefix="https://downloads.openwrt.org/releases/$RELEASE/targets/$TARGET"
  690. local url_filename="openwrt-imagebuilder-$RELEASE-${TARGET//\//-}.Linux-x86_64.tar.zst"
  691. local img_fname="openwrt-$RELEASE-${TARGET//\//-}-$DEVICE-$FILESYSTEM"
  692. fi
  693. local ib_url="$url_prefix/$url_filename"
  694. local ib_file="$BUILD_DIR/$url_filename"
  695. local ib_sha256_url="$url_prefix/sha256sums"
  696. local ib_sha256_file="$BUILD_DIR/sha256sums"
  697. local seed_url="$url_prefix/config.buildinfo"
  698. if ((FROM_SOURCE)); then
  699. declare -g SYSUPGRADEIMGGZ="$BIN_DIR/targets/$TARGET/$img_fname-sysupgrade.img.gz"
  700. else
  701. declare -g SYSUPGRADEIMGGZ="$BUILD_DIR/$img_fname-sysupgrade.img.gz"
  702. fi
  703. backup "$SYSUPGRADEIMGGZ" "$BACKUP_DIR/$profile/$RELEASE"
  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