openwrtbuilder 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837
  1. #!/usr/bin/env bash
  2. # Build and deploy OpenWRT images using convenient shell-style device profiles.
  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. # @arg $1 string Build mode ("source" or "imagebuilder")
  144. install_dependencies() {
  145. debug "${FUNCNAME[0]}"
  146. local mode="$1"
  147. local -a pkg_list
  148. local lock_file
  149. # Set appropriate lock file based on mode
  150. if [[ "$mode" == "source" ]]; then
  151. lock_file="$BUILD_ROOT/.dependencies_source.lock"
  152. elif [[ "$mode" == "imagebuilder" ]]; then
  153. lock_file="$BUILD_ROOT/.dependencies_ib.lock"
  154. fi
  155. [[ -f $lock_file ]] && debug "$lock_file lock file exists" && return 0
  156. if [[ "$mode" == "source" ]]; then
  157. # For building from source code see:
  158. # https://openwrt.org/docs/guide-developer/toolchain/install-buildsystem
  159. case "$ID" in
  160. fedora|centos)
  161. pkg_list+=(
  162. bzip2
  163. clang # for qosify
  164. diffutils
  165. gcc
  166. gcc-c++
  167. git
  168. llvm15-libs # for qosify
  169. make
  170. ncurses-devel
  171. patch
  172. perl
  173. perl-Data-Dumper
  174. perl-File-Compare
  175. perl-File-Copy
  176. perl-FindBin
  177. perl-IPC-Cmd
  178. perl-JSON-PP
  179. perl-Thread-Queue
  180. perl-Time-Piece
  181. perl-base
  182. python3
  183. python3-devel
  184. python3-pyelftools
  185. python3-setuptools
  186. rsync
  187. swig
  188. tar
  189. unzip
  190. wget
  191. which
  192. ) ;;
  193. debian|ubuntu)
  194. pkg_list+=(
  195. build-essential
  196. clang
  197. file
  198. flex
  199. g++
  200. gawk
  201. gcc-multilib
  202. gettext
  203. git
  204. liblzma-dev
  205. libncurses5-dev
  206. libssl-dev
  207. python3-distutils
  208. rsync
  209. patch
  210. unzip
  211. wget
  212. zlib1g-dev
  213. ) ;;
  214. arch)
  215. pkg_list+=(
  216. autoconf
  217. automake
  218. base-devel
  219. bash
  220. binutils
  221. bison
  222. bzip2
  223. clang
  224. fakeroot
  225. file
  226. findutils
  227. flex
  228. gawk
  229. gcc
  230. gettext
  231. git
  232. grep
  233. groff
  234. gzip
  235. libelf
  236. libtool
  237. libxslt
  238. m4
  239. make
  240. ncurses
  241. net-snmp
  242. openssl
  243. patch
  244. pkgconf
  245. python
  246. rsync
  247. sed
  248. texinfo
  249. time
  250. unzip
  251. util-linux
  252. wget
  253. which
  254. xz
  255. zlib
  256. ) ;;
  257. *) debug "Unsupported OS, skipping dependency install"; return 1 ;;
  258. esac
  259. elif [[ "$mode" == "imagebuilder" ]]; then
  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, skipping dependency install"; return 1 ;;
  295. esac
  296. fi
  297. pkg_install "${pkg_list[@]}" && echo "${pkg_list[@]}" > "$lock_file"
  298. }
  299. # @description Normalize release and set worktree reference
  300. # @arg $1 string Raw release input
  301. # @arg $2 string Build mode ("source" or "imagebuilder")
  302. # @returns string Normalized release and reference
  303. normalize_and_ref() {
  304. debug "${FUNCNAME[0]}" "$*"
  305. local input="$1" mode="$2"
  306. local rel ref branch tag
  307. case "$input" in
  308. snapshot|latest|main|master)
  309. rel="snapshot"
  310. ref="main"
  311. ;;
  312. v[0-9][0-9].[0-9][0-9].*|[0-9][0-9].[0-9][0-9].*)
  313. # strip optional leading “v”
  314. rel="${input#v}"
  315. if [[ "$mode" == "source" ]]; then
  316. branch="openwrt-${rel%.*}"
  317. tag="v$rel"
  318. if ask_ok "Use branch $branch HEAD (y) or tag $tag (n)?"; then
  319. ref="$branch"
  320. else
  321. ref="$tag"
  322. fi
  323. else
  324. ref="$rel"
  325. fi
  326. ;;
  327. *)
  328. if [[ "$mode" == "source" ]]; then
  329. # arbitrary commit-ish allowed
  330. rel="$input"
  331. ref="$input"
  332. else
  333. echo "Error: invalid release '$input'" >&2
  334. exit 1
  335. fi
  336. ;;
  337. esac
  338. printf '%s %s' "$rel" "$ref"
  339. }
  340. # @description Acquires the OpenWRT Image Builder
  341. get_imagebuilder() {
  342. debug "${FUNCNAME[0]}" "$*"
  343. local -a url_file_pairs=("$@")
  344. for ((i=0; i<${#url_file_pairs[@]}; i+=2)); do
  345. local url="${url_file_pairs[i]}"
  346. local file="${url_file_pairs[i+1]}"
  347. # Check if file exists and ask user to remove and redownload
  348. if [[ -f $file ]] && ! ask_ok "Use existing $file?"; then
  349. execute rm -f "$file"
  350. fi
  351. # Download the file if it doesn't exist
  352. if [[ ! -f "$file" ]]; then
  353. echo "Downloading $url to $file using $DL_TOOL"
  354. execute "$DL_TOOL" "-o" "$file" "$url"
  355. fi
  356. done
  357. }
  358. ssh_backup() {
  359. debug "${FUNCNAME[0]}"
  360. local date hostname backup_fname
  361. printf -v date '%(%Y-%m-%d-%H-%M-%S)T'
  362. hostname=$(ssh -qt "$SSH_BACKUP_PATH" echo -n \$HOSTNAME)
  363. backup_fname="backup-$hostname-$date.tar.gz"
  364. [[ -d "$FILES_DIR" ]] || execute mkdir -p "$FILES_DIR"
  365. # Make backup archive on remote
  366. if ! execute "ssh -t $SSH_BACKUP_PATH sysupgrade -b /tmp/$backup_fname"; then
  367. echo "SSH backup failed"
  368. exit 1
  369. fi
  370. # Move backup archive locally
  371. if ! execute "rsync -avz --remove-source-files $SSH_BACKUP_PATH:/tmp/$backup_fname $BUILD_DIR/"; then
  372. echo "Could not copy SSH backup"
  373. exit 1
  374. fi
  375. # Extract backup archive
  376. if ! execute "tar -C $FILES_DIR -xzf $BUILD_DIR/$backup_fname"; then
  377. echo "Could not extract SSH backup"
  378. exit 1
  379. fi
  380. execute "rm $BUILD_DIR/$backup_fname"
  381. }
  382. make_images() {
  383. debug "${FUNCNAME[0]}"
  384. local -a make_opts
  385. # Reuse the existing output
  386. # if [[ -d "$BIN_DIR" ]]; then
  387. # if ask_ok "$BIN_DIR exists. Rebuild?"; then
  388. # execute rm -rf "$BIN_DIR"
  389. # else
  390. # return 0
  391. # fi
  392. # fi
  393. ((DEBUG)) && make_opts+=("V=sc")
  394. debug make "${make_opts[@]}" image BIN_DIR="$BIN_DIR" \
  395. PROFILE="$DEVICE" PACKAGES="$PACKAGES" \
  396. FILES="$FILES_DIR" --directory="$BUILD_DIR" \
  397. --jobs="$(($(nproc) - 1))"
  398. make "${make_opts[@]}" image \
  399. BIN_DIR="$BIN_DIR" \
  400. PROFILE="$DEVICE" \
  401. PACKAGES="$PACKAGES" \
  402. FILES="$FILES_DIR" \
  403. --directory="$BUILD_DIR" \
  404. --jobs="$(($(nproc) - 1))" \
  405. > "$BUILD_DIR/make.log"
  406. }
  407. flash_images() {
  408. debug "${FUNCNAME[0]}"
  409. local img_gz="$1"
  410. local dev="$2"
  411. local img="${img_gz%.gz}"
  412. local partitions
  413. if [[ ! -e "$dev" ]]; then
  414. echo "The device specified by --flash could not be found"
  415. return 1
  416. fi
  417. [[ -f $img_gz ]] || { echo "$img_gz does not exist"; return 1; }
  418. execute gunzip -qfk "$img_gz"
  419. echo "Unmounting target device $dev partitions"
  420. partitions=("$dev"?*)
  421. execute sudo umount "${partitions[@]}"
  422. if execute sudo dd if="$img" of="$dev" bs=2M conv=fsync; then
  423. sync
  424. echo "Image flashed successfully!"
  425. else
  426. echo "dd failed!"
  427. return 1
  428. fi
  429. }
  430. ssh_upgrade() {
  431. debug "${FUNCNAME[0]}"
  432. local img_gz="$1"
  433. local ssh_path="$2"
  434. local img_fname="${img_gz##*/}"
  435. [[ -f $img_gz ]] || { echo "$img_gz is missing, check build output"; return 1; }
  436. echo "Copying '$img_gz' to $ssh_path/tmp/$img_fname"
  437. if ! execute scp "$img_gz" "$ssh_path:/tmp/$img_fname"; then
  438. echo "Could not copy $img_gz to $ssh_path:/tmp/$img_fname"
  439. return 1
  440. fi
  441. echo "Executing remote sysupgrade"
  442. # This may result in weird exit code from closing the ssh connection
  443. # shellcheck disable=SC2029
  444. ssh "$ssh_path" "sysupgrade -F /tmp/$img_fname"
  445. return 0
  446. }
  447. # @description Builds OpenWRT from source code using the the default buildbot as base
  448. # This enables the use of kernel config options in profiles
  449. # @arg $1 string .config seed URL
  450. # @arg $2 string Profile name
  451. # @arg $3 string Worktree ref (commit-ish or branch name)
  452. from_source() {
  453. debug "${FUNCNAME[0]}" "$*"
  454. local seed_url="$1"
  455. local profile="$2"
  456. local ref="$3"
  457. local src_url="https://github.com/openwrt/openwrt.git"
  458. local seed_file="$BUILD_DIR/.config"
  459. local worktree_meta="$SRC_DIR/.git/worktrees/source-$ref"
  460. local pkg config commit seed_file description
  461. local -a make_opts config_opts
  462. echo "Building from source is under development"
  463. # Remove all build directories and worktrees
  464. if ((RESET)); then
  465. if [[ -d "$BUILD_DIR" || -d "$worktree_meta" ]]; then
  466. execute git -C "$SRC_DIR" worktree remove --force --force "$BUILD_DIR"
  467. [[ -d "$BUILD_DIR" ]] && execute rm -rf "$BUILD_DIR"
  468. [[ -d "$worktree_meta" ]] && execute rm -rf "$worktree_meta"
  469. fi
  470. [[ -d "$BUILD_DIR" ]] && execute rm -rf "$BUILD_DIR"
  471. fi
  472. # Pull or clone source repo
  473. if [[ -d "$SRC_DIR" ]]; then
  474. execute git -C "$SRC_DIR" pull
  475. else
  476. execute mkdir -p "$SRC_DIR"
  477. execute git clone "$src_url" "$SRC_DIR"
  478. fi
  479. # Remove existing build dir and add new worktree
  480. if [[ -d "$BUILD_DIR" ]]; then
  481. execute rm -rf "$BUILD_DIR"
  482. fi
  483. execute git -C "$SRC_DIR" worktree prune --verbose
  484. execute git -C "$SRC_DIR" worktree add "$BUILD_DIR" "$ref"
  485. # Print commit info
  486. commit=$(git -C "$BUILD_DIR" rev-parse HEAD)
  487. description=$(git -C "$BUILD_DIR" describe)
  488. echo "Current commit hash: $commit"
  489. echo "Git worktree description: $description"
  490. ((DEBUG)) && git --no-pager -C "$BUILD_DIR" log --all --pretty=format:"%ai %H" | sort -r | head -n1
  491. # Enter worktree
  492. execute pushd "$BUILD_DIR" || return 1
  493. # Begin OpenWRT build process
  494. ((DEBUG)) && make_opts+=("V=sc")
  495. # Cleanup build environment
  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 -a -f &&
  505. ./scripts/feeds install -a -f
  506. # Grab the release seed config
  507. if ! execute "$DL_TOOL" "-o" "$seed_file" "$seed_url"; then
  508. echo "Could not obtain $seed_file from $seed_url"
  509. return 1
  510. fi
  511. # Set compilation output dir
  512. config_opts+=("CONFIG_BINARY_FOLDER=\"$BIN_DIR\"")
  513. # Add custom packages
  514. for pkg in $PACKAGES; do
  515. if [[ $pkg == -* ]]; then
  516. config_opts+=("CONFIG_PACKAGE_${pkg#-}=n") # remove package
  517. else
  518. config_opts+=("CONFIG_PACKAGE_$pkg=y") # add package
  519. fi
  520. done
  521. # Add config options from profile
  522. for config in ${P_ARR[config]}; do
  523. config_opts+=("$config")
  524. done
  525. # Only compile selected fs
  526. execute sed -i '/CONFIG_TARGET_ROOTFS_/d' "$seed_file"
  527. config_opts+=("CONFIG_TARGET_PER_DEVICE_ROOTFS=n")
  528. if [[ $FILESYSTEM == "squashfs" ]]; then
  529. config_opts+=("CONFIG_TARGET_ROOTFS_EXT4FS=n")
  530. config_opts+=("CONFIG_TARGET_ROOTFS_SQUASHFS=y")
  531. elif [[ $FILESYSTEM == "ext4" ]]; then
  532. config_opts+=("CONFIG_TARGET_ROOTFS_SQUASHFS=n")
  533. config_opts+=("CONFIG_TARGET_ROOTFS_EXT4FS=y")
  534. fi
  535. # Only compile selected target image
  536. execute sed -i '/CONFIG_TARGET_DEVICE_/d' "$seed_file"
  537. config_opts+=("CONFIG_TARGET_MULTI_PROFILE=n")
  538. config_opts+=("CONFIG_TARGET_PROFILE=DEVICE_$DEVICE")
  539. config_opts+=("CONFIG_TARGET_${TARGET//\//_}_DEVICE_$DEVICE=y")
  540. config_opts+=("CONFIG_SDK=n")
  541. config_opts+=("CONFIG_SDK_LLVM_BPF=n")
  542. config_opts+=("CONFIG_IB=n")
  543. config_opts+=("CONFIG_MAKE_TOOLCHAIN=n")
  544. # Write options to config seed file
  545. for config in "${config_opts[@]}"; do
  546. debug "Writing $config to $seed_file"
  547. echo "$config" >> "$seed_file"
  548. done
  549. # Serial make prep is more reliable
  550. execute make "${make_opts[@]}" "-j1" defconfig
  551. execute make "${make_opts[@]}" "-j1" download
  552. # make_opts+=("-j$(($(nproc)-1))")
  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 ]] || 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. # Remove dependency lock files for --depends
  654. if ((FORCE_DEPENDS)); then
  655. [[ -f "$BUILD_ROOT/.dependencies_source.lock" ]] && rm -f "$BUILD_ROOT/.dependencies_source.lock"
  656. [[ -f "$BUILD_ROOT/.dependencies_ib.lock" ]] && rm -f "$BUILD_ROOT/.dependencies_ib.lock"
  657. fi
  658. # Run selected profiles
  659. for profile in "${PROFILES[@]}"; do
  660. debug "Running profile: $profile"
  661. if [[ ! ${!profile@a} = A ]]; then
  662. echo "Profile '$profile' does not exist"
  663. return 1
  664. fi
  665. # Store profile in P_ARR nameref
  666. local -n P_ARR="$profile"
  667. local mode="${P_ARR[mode]:="imagebuilder"}"
  668. ((FROM_SOURCE)) && mode="source" # allow cli override
  669. install_dependencies "$mode"
  670. local repo="${P_ARR[repo]:-}"
  671. declare -g FILESYSTEM="${P_ARR[filesystem]:="squashfs"}"
  672. declare -g TARGET="${P_ARR[target]}"
  673. declare -g DEVICE="${P_ARR[device]}"
  674. declare -g PACKAGES="${P_ARR[packages]:-}"
  675. # pull in USER_RELEASE from args or profile default
  676. local raw_release="${USER_RELEASE:=${P_ARR[release]:=$RELEASE}}"
  677. # single call to normalize+ref
  678. debug "normalize_and_ref output: $(normalize_and_ref "$raw_release" "$mode")"
  679. read -r release ref < <(normalize_and_ref "$raw_release" "$mode")
  680. debug "Using release: $release, ref: $ref, mode: $mode"
  681. declare -g SRC_DIR="$BUILD_ROOT/src/.openwrt"
  682. declare -g BUILD_DIR="$BUILD_ROOT/src/$profile/$mode-$ref"
  683. declare -g BIN_DIR="$BUILD_ROOT/bin/$profile/$mode-$ref"
  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 [[ "$mode" == "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/$mode-$ref"
  704. if [[ "$mode" == "source" ]]; then
  705. from_source "$seed_url" "$profile" "$ref" || return $?
  706. elif [[ "$mode" == "imagebuilder" ]]; then
  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. if [[ -v $repo ]]; then
  712. if ! grep -q "$repo" "$BUILD_DIR/repositories.conf"; then
  713. echo "$repo" >> "$BUILD_DIR/repositories.conf"
  714. fi
  715. sed -i '/option check_signature/d' "$BUILD_DIR/repositories.conf"
  716. fi
  717. make_images
  718. # Verify output image for stock builds (in testing)
  719. if [[ ! -v P_ARR[packages] || -z ${P_ARR[packages]} ]]; then
  720. shopt -s nullglob
  721. local -a outfiles=("$BIN_DIR"/*.img.gz "$BIN_DIR"/*.img)
  722. shopt -u nullglob
  723. for outfile in "${outfiles[@]}"; do
  724. verify "$outfile" "$ib_sha256_file" || return 1
  725. done
  726. fi
  727. #copyFiles
  728. fi
  729. [[ -v SSH_BACKUP_PATH ]] && ssh_backup
  730. [[ -v SSH_UPGRADE_PATH ]] && ssh_upgrade "$SYSUPGRADEIMGGZ" "$SSH_UPGRADE_PATH"
  731. [[ -v FLASH_DEV ]] && flash_images "$SYSUPGRADEIMGGZ" "$FLASH_DEV"
  732. done
  733. }
  734. main "$@"
  735. exit