openwrtbuilder 23 KB

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