openwrtbuilder 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699
  1. #!/usr/bin/env bash
  2. #
  3. # Copyright 2022-23 Bryan C. Roessler
  4. #
  5. # Build and deploy OpenWRT images
  6. #
  7. # Apache 2.0 License
  8. # Set default release
  9. : "${RELEASE:="22.03.3"}"
  10. printHelp() {
  11. debug "${FUNCNAME[0]}"
  12. cat <<-'EOF'
  13. Create and deploy OpenWRT images using the Image Builder.
  14. USAGE:
  15. openwrtbuilder [OPTION [VALUE]] -p PROFILE [-p PROFILE2]...
  16. OPTIONS
  17. --profile, -p PROFILE
  18. --info, -i (print profile info)
  19. --list-profiles, -l
  20. --release, -r, --version, -v RELEASE_VERSION ("snapshot", "22.03.3", etc.)
  21. --buildroot, -b PATH
  22. --ssh-upgrade HOST
  23. Example: root@192.168.1.1
  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. --debug, -d
  31. --help, -h
  32. EOF
  33. }
  34. readInput() {
  35. debug "${FUNCNAME[0]}"
  36. unset RESET
  37. declare -ga PROFILES
  38. declare -g PROFILE_INFO
  39. if _input=$(getopt -o +r:v:p:i:lb:sf:dh -l release:,version:,profile:,info:,list-profiles,buildroot:,from-source,ssh-upgrade:,ssh-backup:,flash:,reset,debug,help -- "$@"); then
  40. eval set -- "$_input"
  41. while true; do
  42. case "$1" in
  43. --release|-r|--version|-v)
  44. shift && declare -g USER_RELEASE="$1"
  45. ;;
  46. --profile|-p)
  47. shift && PROFILES+=("$1")
  48. ;;
  49. --info|-i)
  50. PROFILE_INFO=1
  51. ;;
  52. --list-profiles|-l)
  53. listProfiles && exit $?
  54. ;;
  55. --buildroot|-b)
  56. shift && BUILDROOT="$1"
  57. ;;
  58. --from-source|-s)
  59. FROM_SOURCE=1
  60. ;;
  61. --ssh-upgrade)
  62. shift && SSH_UPGRADE_PATH="$1"
  63. ;;
  64. --ssh-backup)
  65. shift && SSH_BACKUP_PATH="$1"
  66. ;;
  67. --flash|-f)
  68. shift && FLASH_DEV="$1"
  69. ;;
  70. --reset)
  71. RESET=1
  72. ;;
  73. --debug|-d)
  74. echo "Debugging on"
  75. DEBUG=1
  76. ;;
  77. --help|-h)
  78. printHelp && exit 0
  79. ;;
  80. --)
  81. shift
  82. break
  83. ;;
  84. esac
  85. shift
  86. done
  87. else
  88. echo "Incorrect options provided"
  89. printHelp && exit 1
  90. fi
  91. }
  92. listProfiles() {
  93. debug "${FUNCNAME[0]}"
  94. grep "declare -Ag" "$PFILE" | cut -d" " -f3
  95. }
  96. installDependencies() {
  97. debug "${FUNCNAME[0]}"
  98. declare -a pkg_list
  99. # TODO please contribute your platform here
  100. if (( FROM_SOURCE )); then
  101. # For building from source with make
  102. # https://openwrt.org/docs/guide-developer/toolchain/install-buildsystem
  103. case "$ID" in
  104. fedora|centos)
  105. pkg_list+=(
  106. "bash-completion"
  107. "bzip2"
  108. "gcc"
  109. "gcc-c++"
  110. "git"
  111. "make"
  112. "ncurses-devel"
  113. "patch"
  114. "rsync"
  115. "tar"
  116. "unzip"
  117. "wget"
  118. "which"
  119. "diffutils"
  120. "python2"
  121. "python3"
  122. "perl-base"
  123. "perl-Data-Dumper"
  124. "perl-File-Compare"
  125. "perl-File-Copy"
  126. "perl-FindBin"
  127. "perl-Thread-Queue"
  128. )
  129. ;;
  130. debian|ubuntu)
  131. pkg_list+=(
  132. "build-essential"
  133. "clang"
  134. "flex"
  135. "g++"
  136. "gawk"
  137. "gcc-multilib"
  138. "gettext"
  139. "git"
  140. "libncurses5-dev"
  141. "libssl-dev"
  142. "python3-distutils"
  143. "rsync"
  144. "unzip"
  145. "zlib1g-dev"
  146. "file"
  147. "wget"
  148. )
  149. ;;
  150. arch)
  151. pkg_list+=(
  152. "base-devel"
  153. "autoconf"
  154. "automake"
  155. "bash"
  156. "binutils"
  157. "bison"
  158. "bzip2"
  159. "fakeroot"
  160. "file"
  161. "findutils"
  162. "flex"
  163. "gawk"
  164. "gcc"
  165. "gettext"
  166. "git"
  167. "grep"
  168. "groff"
  169. "gzip"
  170. "libelf"
  171. "libtool"
  172. "libxslt"
  173. "m4"
  174. "make"
  175. "ncurses"
  176. "openssl"
  177. "patch"
  178. "pkgconf"
  179. "python"
  180. "rsync"
  181. "sed"
  182. "texinfo"
  183. "time"
  184. "unzip"
  185. "util-linux"
  186. "wget"
  187. "which"
  188. "zlib"
  189. )
  190. ;;
  191. esac
  192. else
  193. # For Imagebuilder
  194. case "$ID" in
  195. fedora|centos)
  196. pkg_list+=(
  197. "@c-development"
  198. "@development-tools"
  199. "@development-libs"
  200. "perl-FindBin"
  201. "zlib-static"
  202. "elfutils-libelf-devel"
  203. "gawk"
  204. "unzip"
  205. "file"
  206. "wget"
  207. "python3"
  208. "python2"
  209. "axel"
  210. )
  211. ;;
  212. debian|ubuntu)
  213. pkg_list+=(
  214. "build-essential"
  215. "libncurses5-dev"
  216. "libncursesw5-dev"
  217. "zlib1g-dev"
  218. "gawk"
  219. "git"
  220. "gettext"
  221. "libssl-dev"
  222. "xsltproc"
  223. "wget"
  224. "unzip"
  225. "python"
  226. "axel"
  227. )
  228. ;;
  229. esac
  230. fi
  231. pkg_install "${pkg_list[@]}"
  232. }
  233. getImageBuilder() {
  234. debug "${FUNCNAME[0]}"
  235. declare dl_tool
  236. if [[ -f "$IB_ARCHIVE" ]]; then
  237. if askOk "Redownload ImageBuilder archive?"; then
  238. rm -f "$IB_ARCHIVE"
  239. else
  240. return 0
  241. fi
  242. fi
  243. if hash axel &>/dev/null; then
  244. dl_tool="axel"
  245. elif hash curl &>/dev/null; then
  246. dl_tool="curl"
  247. else
  248. echo "Downloading the ImageBuilder requires axel or curl!"
  249. return 1
  250. fi
  251. echo "Downloading imagebuilder archive using $dl_tool"
  252. debug "$dl_tool -o $IB_ARCHIVE $IB_URL"
  253. if ! "$dl_tool" -o "$IB_ARCHIVE" "$IB_URL"; then
  254. echo "Could not download imagebuilder archive"
  255. exit 1
  256. fi
  257. if [[ ! -f "$IB_ARCHIVE" ]]; then
  258. echo "Archive missing"
  259. exit 1
  260. fi
  261. # if hash sha256sum &>/dev/null; then
  262. # echo "Verifying checksums"
  263. # debug "$dl_tool -s "${P_ARR[sha256_url]}" | grep $filename | cut -f1 -d' '"
  264. # sha256sum=$($dl_tool -s "${P_ARR[sha256_url]}" |grep "$filename" |cut -f1 -d' ')
  265. # debug "Downloaded sha256sum: $sha256sum"
  266. # fi
  267. echo "Extracting image archive"
  268. [[ ! -d "$BUILDDIR" ]] && mkdir -p "$BUILDDIR"
  269. debug "tar -xf $IB_ARCHIVE -C $BUILDDIR --strip-components 1"
  270. if ! tar -xf "$IB_ARCHIVE" -C "$BUILDDIR" --strip-components 1; then
  271. echo "Extraction failed"
  272. exit 1
  273. fi
  274. }
  275. addRepos() {
  276. debug "${FUNCNAME[0]}"
  277. if [[ -v P_ARR[repo] ]]; then
  278. if ! grep -q "${P_ARR[repo]}" "$BUILDDIR/repositories.conf"; then
  279. echo "${P_ARR[repo]}" >> "$BUILDDIR/repositories.conf"
  280. fi
  281. sed -i '/option check_signature/d' "$BUILDDIR/repositories.conf"
  282. fi
  283. }
  284. sshBackup() {
  285. debug "${FUNCNAME[0]}"
  286. local _date _hostname _backup_fname
  287. [[ -d "$FILESDIR" ]] || mkdir -p "$FILESDIR"
  288. printf -v _date '%(%Y-%m-%d-%H-%M-%S)T'
  289. _hostname=$(ssh -qt "$SSH_BACKUP_PATH" echo -n \$HOSTNAME)
  290. _backup_fname="backup-$_hostname-$_date.tar.gz"
  291. # Make backup archive on remote
  292. debug "ssh -t $SSH_BACKUP_PATH sysupgrade -b /tmp/$_backup_fname"
  293. if ! ssh -t "$SSH_BACKUP_PATH" "sysupgrade -b /tmp/$_backup_fname"; then
  294. echo "SSH backup failed"
  295. exit 1
  296. fi
  297. # Move backup archive locally
  298. debug "rsync -avz --remove-source-files $SSH_BACKUP_PATH:/tmp/$_backup_fname $BUILDDIR/"
  299. if ! rsync -avz --remove-source-files "$SSH_BACKUP_PATH":"/tmp/$_backup_fname" "$BUILDDIR/"; then
  300. echo "Could not copy SSH backup"
  301. exit 1
  302. fi
  303. # Extract backup archive
  304. debug "tar -C $FILESDIR -xzf $BUILDDIR/$_backup_fname"
  305. if ! tar -C "$FILESDIR" -xzf "$BUILDDIR/$_backup_fname"; then
  306. echo "Could not extract SSH backup"
  307. exit 1
  308. fi
  309. rm "$BUILDDIR/$_backup_fname"
  310. }
  311. makeImage() {
  312. debug "${FUNCNAME[0]}"
  313. # Reuse the existing output
  314. if [[ -d "$BUILDDIR" ]]; then
  315. if askOk "$BUILDDIR exists. Rebuild?"; then
  316. rm -rf "$BUILDDIR"
  317. else
  318. return 0
  319. fi
  320. fi
  321. [[ ! -d "$BUILDDIR" ]] && mkdir -p "$BUILDDIR"
  322. if ! make image \
  323. BIN_DIR="$BUILDDIR" \
  324. PROFILE="${P_ARR[profile]}" \
  325. PACKAGES="${P_ARR[packages]}" \
  326. FILES="${FILESDIR}" \
  327. --directory="$BUILDDIR" \
  328. --jobs=$(( $(nproc) - 1 )) \
  329. > make.log; then
  330. echo "Make image failed!"
  331. exit 1
  332. fi
  333. }
  334. flashImage() {
  335. debug "${FUNCNAME[0]}"
  336. declare img img_gz partitions
  337. if [[ ! -e "$FLASH_DEV" ]]; then
  338. echo "The device specified by --flash could not be found"
  339. exit 1
  340. fi
  341. # TODO Roughly chooses the correct image
  342. if [[ -f "$FACTORYIMGGZ" ]]; then
  343. img_gz="$FACTORYIMGGZ"
  344. img="$FACTORYIMG"
  345. elif [[ -f "$SYSUPGRADEIMGGZ" ]]; then
  346. img_gz="$SYSUPGRADEIMGGZ"
  347. img="$SYSUPGRADEIMG"
  348. else
  349. return 1
  350. fi
  351. debug "$img_gz $img"
  352. debug "gunzip -qfk $img_gz"
  353. gunzip -qfk "$img_gz"
  354. echo "Unmounting target device $FLASH_DEV partitions"
  355. partitions=( "$FLASH_DEV"?* )
  356. debug "umount ${partitions[*]}"
  357. sudo umount "${partitions[@]}"
  358. debug "sudo dd if=\"$img\" of=\"$FLASH_DEV\" bs=2M conv=fsync"
  359. if sudo dd if="$img" of="$FLASH_DEV" bs=2M conv=fsync; then
  360. sync
  361. echo "Image flashed sucessfully!"
  362. else
  363. echo "dd failed!"
  364. exit 1
  365. fi
  366. }
  367. sshUpgrade() {
  368. debug "${FUNCNAME[0]}"
  369. echo "Copying '$SYSUPGRADEIMGGZ' to $SSH_UPGRADE_PATH/tmp/"
  370. debug "scp \"$SYSUPGRADEIMGGZ\" \"$SSH_UPGRADE_PATH\":\"/tmp/$SYSUPGRADEIMGGZFNAME\""
  371. if ! scp "$SYSUPGRADEIMGGZ" "$SSH_UPGRADE_PATH":"/tmp/$SYSUPGRADEIMGGZFNAME"; then
  372. echo "Could not access the --ssh-upgrade PATH"
  373. exit 1
  374. fi
  375. echo "Executing remote sysupgrade"
  376. debug "ssh \"$SSH_UPGRADE_PATH\" \"sysupgrade -F /tmp/$SYSUPGRADEIMGGZFNAME\""
  377. # shellcheck disable=SC2029
  378. ssh "$SSH_UPGRADE_PATH" "sysupgrade -F /tmp/$SYSUPGRADEIMGGZFNAME"
  379. }
  380. fromSource() {
  381. debug "${FUNCNAME[0]}"
  382. declare src_url="https://github.com/openwrt/openwrt.git"
  383. declare src_dir="$BUILDDIR/sources/openwrt"
  384. declare -a pkg_list
  385. echo "Building from source is under development"
  386. if [[ ! -d "$src_dir" ]]; then
  387. mkdir -p "$src_dir"
  388. git clone "$src_url" "$src_dir"
  389. fi
  390. pushd "$src_dir" || return 1
  391. if [[ ${P_ARR[release]} == "snapshot" ]]; then
  392. git checkout master
  393. else
  394. git checkout "v$RELEASE"
  395. fi
  396. ./scripts/feeds update -a
  397. ./scripts/feeds install -a
  398. # Grab the release config.seed
  399. k_options=$(curl -s https://downloads.openwrt.org/releases/22.03.3/targets/rockchip/armv8/config.buildinfo)
  400. debug "$k_options"
  401. make distclean
  402. make download
  403. make -j"$(nproc)" world
  404. popd || return 1
  405. exit # TODO exit here for fromSource() testing
  406. }
  407. debug() { (( DEBUG )) && echo "Debug: $*"; }
  408. askOk() {
  409. local _response
  410. read -r -p "$* [y/N]" _response
  411. _response=${_response,,}
  412. [[ ! "$_response" =~ ^(yes|y)$ ]] && return 1
  413. return 0
  414. }
  415. resetAll() {
  416. debug "${FUNCNAME[0]}"
  417. askOk "Remove ${BUILDROOT}/sources and ${BUILDROOT}/bin?" || exit $?
  418. debug "rm -rf ${BUILDROOT}/sources ${BUILDROOT}/bin"
  419. rm -rf "${BUILDROOT:?}/sources" "${BUILDROOT:?}/bin"
  420. }
  421. resetProfile() {
  422. debug "${FUNCNAME[0]}"
  423. askOk "Remove $BUILDDIR?" || exit $?
  424. debug "rm -rf $BUILDDIR"
  425. rm -rf "$BUILDDIR"
  426. }
  427. loadProfiles() {
  428. debug "${FUNCNAME[0]}"
  429. declare -g PFILE
  430. # https://stackoverflow.com/a/4774063
  431. PFILE="$SCRIPTDIR/profiles"
  432. # shellcheck source=./profiles
  433. ! source "$PFILE" && echo "profiles file missing!" && return 1
  434. }
  435. init() {
  436. debug "${FUNCNAME[0]}"
  437. declare -g ID RPM_MGR SCRIPTDIR
  438. debug || echo "To enable debugging output, use --debug or -d"
  439. # Save the script directory
  440. SCRIPTDIR="$(cd -- "$(dirname "$0")" >/dev/null 2>&1 || exit $? ; pwd -P)"
  441. if [[ -e "/etc/os-release" ]]; then
  442. source "/etc/os-release"
  443. else
  444. err "/etc/os-release not found"
  445. err "Your OS is unsupported"
  446. printHelp
  447. exit 1
  448. fi
  449. debug "Detected host platform: $ID"
  450. # normalize distro ID
  451. case "$ID" in
  452. debian|arch)
  453. ;;
  454. centos|fedora)
  455. if hash dnf &>/dev/null; then
  456. RPM_MGR="dnf"
  457. elif hash yum &>/dev/null; then
  458. RPM_MGR="yum"
  459. fi
  460. ;;
  461. rhel)
  462. ID="centos"
  463. ;;
  464. linuxmint|neon|*ubuntu*)
  465. ID="ubuntu"
  466. ;;
  467. *suse*)
  468. ID="suse"
  469. ;;
  470. raspbian)
  471. ID="debian"
  472. ;;
  473. *)
  474. echo "Autodetecting distro, this may be unreliable and --compat may also be required"
  475. if hash dnf &>/dev/null; then
  476. ID="fedora"
  477. RPM_MGR="dnf"
  478. elif hash yum &>/dev/null; then
  479. ID="centos"
  480. RPM_MGR="yum"
  481. elif hash apt &>/dev/null; then
  482. ID="ubuntu"
  483. elif hash pacman &>/dev/null; then
  484. ID="arch"
  485. else
  486. return 1
  487. fi
  488. ;;
  489. esac
  490. debug "Using host platform: $ID"
  491. # Set distro-specific functions
  492. case "$ID" in
  493. fedora|centos)
  494. pkg_install(){ sudo "$RPM_MGR" install -y "$@"; }
  495. ;;
  496. debian|ubuntu)
  497. pkg_install(){ sudo apt-get install -y -q0 "$@"; }
  498. ;;
  499. suse)
  500. pkg_install(){ sudo zypper --non-interactive -q install --force --no-confirm "$@"; }
  501. ;;
  502. arch)
  503. pkg_install(){ sudo pacman -S --noconfirm --needed "$@"; }
  504. ;;
  505. esac
  506. }
  507. main() {
  508. debug "${FUNCNAME[0]}"
  509. init
  510. loadProfiles
  511. readInput "$@"
  512. # Fallback to SCRIPTDIR if BUILDROOT has not been set
  513. declare -g BUILDROOT="${BUILDROOT:=$SCRIPTDIR}"
  514. declare -g FILESDIR="${FILESDIR:=$BUILDROOT/files}"
  515. # Allow --reset without a profile
  516. if [[ ${#PROFILES} -lt 1 ]]; then
  517. if (( RESET )); then
  518. resetAll
  519. exit
  520. else
  521. echo "No profile supplied" && return 1
  522. fi
  523. fi
  524. installDependencies
  525. for profile in "${PROFILES[@]}"; do
  526. debug "Starting profile: $profile"
  527. [[ ! ${!profile@a} = A ]] && echo "Profile '$profile' does not exist" && return 1
  528. # Hold profile settings (from config file) in P_ARR
  529. declare -gn P_ARR="$profile"
  530. # precedence: user input>profile>env>hardcode
  531. declare -g RELEASE="${USER_RELEASE:=${P_ARR[release]:=$RELEASE}}"
  532. declare -g BUILDDIR="$BUILDROOT/${P_ARR[profile]}-$RELEASE"
  533. declare -g IB_ARCHIVE="$BUILDDIR/${P_ARR[profile]}-$RELEASE.tar.xz"
  534. declare -g FILESYSTEM="${P_ARR[filesystem]:="squashfs"}"
  535. # shellcheck disable=SC2154
  536. # TODO: I don't know why shellcheck is catching this
  537. if [[ "$RELEASE" == "snapshot" ]]; then
  538. declare url_prefix="https://downloads.openwrt.org/snapshots/targets/${P_ARR[target]}"
  539. declare url_filename="openwrt-imagebuilder-${P_ARR[target]//\//-}.Linux-x86_64.tar.xz"
  540. declare img_prefix="$BUILDDIR/openwrt-${P_ARR[target]//\//-}-${P_ARR[profile]}-$FILESYSTEM"
  541. else
  542. declare url_prefix="https://downloads.openwrt.org/releases/$RELEASE/targets/${P_ARR[target]}"
  543. declare url_filename="openwrt-imagebuilder-$RELEASE-${P_ARR[target]//\//-}.Linux-x86_64.tar.xz"
  544. declare img_prefix="$BUILDDIR/openwrt-$RELEASE-${P_ARR[target]//\//-}-${P_ARR[profile]}-$FILESYSTEM"
  545. fi
  546. declare -g IB_URL="$url_prefix/$url_filename"
  547. declare -g SHA256_URL="$url_prefix/sha256sums"
  548. declare -g SEED_URL="$url_prefix/config.buildinfo"
  549. declare -g FACTORYIMG="$img_prefix-factory.img"
  550. declare -g FACTORYIMGGZ="$img_prefix-factory.img.gz"
  551. declare -g SYSUPGRADEIMG="$img_prefix-sysupgrade.img"
  552. declare -g SYSUPGRADEIMGGZ="$img_prefix-sysupgrade.img.gz"
  553. declare -g SYSUPGRADEIMGGZFNAME="${SYSUPGRADEIMGGZ##*/}"
  554. if (( DEBUG )) || (( PROFILE_INFO )); then
  555. echo "Profile settings:"
  556. for x in "${!P_ARR[@]}"; do printf "%s=%s\n" "$x" "${P_ARR[$x]}"; done
  557. echo "Build settings:"
  558. cat <<- EOF
  559. ALIAS: $profile
  560. BUILDDIR: $BUILDDIR
  561. TARGET: ${P_ARR[target]}
  562. PROFILE: ${P_ARR[profile]}
  563. RELEASE: $RELEASE
  564. FILESYSTEM: $FILESYSTEM
  565. IB_URL: $IB_URL
  566. IB_ARCHIVE: $IB_ARCHIVE
  567. IB_ARCHIVE: $IB_ARCHIVE
  568. EOF
  569. fi
  570. (( RESET )) && resetProfile
  571. (( FROM_SOURCE )) && fromSource
  572. getImageBuilder
  573. addRepos
  574. #copyFiles
  575. [[ -v SSH_BACKUP_PATH ]] && sshBackup
  576. if makeImage; then
  577. [[ -v SSH_UPGRADE_PATH ]] && sshUpgrade
  578. [[ -v FLASH_DEV ]] && flashImage
  579. fi
  580. done
  581. }
  582. main "$@"
  583. exit