#!/usr/bin/env bash # shellcheck disable=SC2039,SC2059 # Title: build-bootstrap.sh # Description: A script to build bootstrap archives for the termux-app # from local package sources instead of debs published in # apt repo like done by generate-bootstrap.sh. It allows # bootstrap archives to be easily built for (forked) termux # apps without having to publish an apt repo first. # Usage: run "build-bootstrap.sh --help" version=0.1.0 set -e . $(dirname "$(realpath "$0")")/properties.sh BOOTSTRAP_TMPDIR=$(mktemp -d "${TMPDIR:-/tmp}/bootstrap-tmp.XXXXXXXX") # By default, bootstrap archives are compatible with Android >=7.0 # and <10. BOOTSTRAP_ANDROID10_COMPATIBLE=false # By default, bootstrap archives will be built for all architectures # supported by Termux application. # Override with option '--architectures'. TERMUX_DEFAULT_ARCHITECTURES=("aarch64" "arm" "i686" "x86_64") TERMUX_ARCHITECTURES=("${TERMUX_DEFAULT_ARCHITECTURES[@]}") TERMUX_PACKAGES_DIRECTORY="/home/builder/termux-packages" TERMUX_BUILT_DEBS_DIRECTORY="$TERMUX_PACKAGES_DIRECTORY/output" TERMUX_BUILT_PACKAGES_DIRECTORY="/data/data/.built-packages" IGNORE_BUILD_SCRIPT_NOT_FOUND_ERROR=1 FORCE_BUILD_PACKAGES=0 # A list of packages to build declare -a PACKAGES=() # A list of non-essential packages to build. # By default it is empty, but can be filled with option '--add'. declare -a ADDITIONAL_PACKAGES=() # A list of already extracted packages declare -a EXTRACTED_PACKAGES=() # A list of options to pass to build-package.sh declare -a BUILD_PACKAGE_OPTIONS=() # Check for some important utilities that may not be available for # some reason. for cmd in ar awk curl grep gzip find sed tar xargs xz zip; do if [ -z "$(command -v $cmd)" ]; then echo "[!] Utility '$cmd' is not available in PATH." exit 1 fi done # Build deb files for package and its dependencies deb from source for arch build_package() { local return_value local package_arch="$1" local package_name="$2" local build_output # Build package from source # stderr will be redirected to stdout and both will be captured into variable and printed on screen cd "$TERMUX_PACKAGES_DIRECTORY" echo $'\n\n\n'"[*] Building '$package_name'..." exec 99>&1 build_output="$("$TERMUX_PACKAGES_DIRECTORY"/build-package.sh "${BUILD_PACKAGE_OPTIONS[@]}" -a "$package_arch" "$package_name" 2>&1 | tee >(cat - >&99); exit ${PIPESTATUS[0]})"; return_value=$? echo "[*] Building '$package_name' exited with exit code $return_value" exec 99>&- if [ $return_value -ne 0 ]; then echo "Failed to build package '$package_name' for arch '$package_arch'" 1>&2 # Dependency packages may not have a build.sh, so we ignore the error. # A better way should be implemented to validate if its actually a dependency # and not a required package itself, by removing dependencies from PACKAGES array. if [[ $IGNORE_BUILD_SCRIPT_NOT_FOUND_ERROR == "1" ]] && [[ "$build_output" == *"No build.sh script at package dir"* ]]; then echo "Ignoring error 'No build.sh script at package dir'" 1>&2 return 0 fi fi return $return_value } # Extract *.deb files to the bootstrap root. extract_debs() { local current_package_name local data_archive local control_archive local package_tmpdir local deb local file cd "$TERMUX_BUILT_DEBS_DIRECTORY" if [ -z "$(ls -A)" ]; then echo $'\n\n\n'"No debs found" return 1 else echo $'\n\n\n'"Deb Files:" echo "\"" ls echo "\"" fi for deb in *.deb; do current_package_name="$(echo "$deb" | sed -E 's/^([^_]+).*/\1/' )" echo "current_package_name: '$current_package_name'" if [[ "$current_package_name" == *"-static" ]]; then echo "[*] Skipping static package '$deb'..." continue fi if [[ " ${EXTRACTED_PACKAGES[*]} " == *" $current_package_name "* ]]; then echo "[*] Skipping already extracted package '$current_package_name'..." continue fi EXTRACTED_PACKAGES+=("$current_package_name") package_tmpdir="${BOOTSTRAP_PKGDIR}/${current_package_name}" mkdir -p "$package_tmpdir" rm -rf "$package_tmpdir"/* echo "[*] Extracting '$deb'..." (cd "$package_tmpdir" ar x "$TERMUX_BUILT_DEBS_DIRECTORY/$deb" # data.tar may have extension different from .xz if [ -f "./data.tar.xz" ]; then data_archive="data.tar.xz" elif [ -f "./data.tar.gz" ]; then data_archive="data.tar.gz" else echo "No data.tar.* found in '$deb'." return 1 fi # Do same for control.tar. if [ -f "./control.tar.xz" ]; then control_archive="control.tar.xz" elif [ -f "./control.tar.gz" ]; then control_archive="control.tar.gz" else echo "No control.tar.* found in '$deb'." return 1 fi # Extract files. tar xf "$data_archive" -C "$BOOTSTRAP_ROOTFS" if ! ${BOOTSTRAP_ANDROID10_COMPATIBLE}; then # Register extracted files. tar tf "$data_archive" | sed -E -e 's@^\./@/@' -e 's@^/$@/.@' -e 's@^([^./])@/\1@' > "${BOOTSTRAP_ROOTFS}/${TERMUX_PREFIX}/var/lib/dpkg/info/${current_package_name}.list" # Generate checksums (md5). tar xf "$data_archive" find data -type f -print0 | xargs -0 -r md5sum | sed 's@^\.$@@g' > "${BOOTSTRAP_ROOTFS}/${TERMUX_PREFIX}/var/lib/dpkg/info/${current_package_name}.md5sums" # Extract metadata. tar xf "$control_archive" { cat control echo "Status: install ok installed" echo } >> "${BOOTSTRAP_ROOTFS}/${TERMUX_PREFIX}/var/lib/dpkg/status" # Additional data: conffiles & scripts for file in conffiles postinst postrm preinst prerm; do if [ -f "${PWD}/${file}" ]; then cp "$file" "${BOOTSTRAP_ROOTFS}/${TERMUX_PREFIX}/var/lib/dpkg/info/${current_package_name}.${file}" fi done fi ) done } # Final stage: generate bootstrap archive and place it to current # working directory. # Information about symlinks is stored in file SYMLINKS.txt. create_bootstrap_archive() { echo $'\n\n\n'"[*] Creating 'bootstrap-${1}.zip'..." (cd "${BOOTSTRAP_ROOTFS}/${TERMUX_PREFIX}" # Do not store symlinks in bootstrap archive. # Instead, put all information to SYMLINKS.txt while read -r -d '' link; do echo "$(readlink "$link")←${link}" >> SYMLINKS.txt rm -f "$link" done < <(find . -type l -print0) zip -r9 "${BOOTSTRAP_TMPDIR}/bootstrap-${1}.zip" ./* ) mv -f "${BOOTSTRAP_TMPDIR}/bootstrap-${1}.zip" "$TERMUX_PACKAGES_DIRECTORY/" echo "[*] Finished successfully (${1})." } set_build_bootstrap_traps() { #set traps for the build_bootstrap_trap itself trap 'build_bootstrap_trap' EXIT trap 'build_bootstrap_trap TERM' TERM trap 'build_bootstrap_trap INT' INT trap 'build_bootstrap_trap HUP' HUP trap 'build_bootstrap_trap QUIT' QUIT return 0 } build_bootstrap_trap() { local build_bootstrap_trap_exit_code=$? trap - EXIT [ -h "$TERMUX_BUILT_PACKAGES_DIRECTORY" ] && rm -f "$TERMUX_BUILT_PACKAGES_DIRECTORY" [ -d "$BOOTSTRAP_TMPDIR" ] && rm -rf "$BOOTSTRAP_TMPDIR" [ -n "$1" ] && trap - "$1"; exit $build_bootstrap_trap_exit_code } show_usage() { cat <<'HELP_EOF' build-bootstraps.sh is a script to build bootstrap archives for the termux-app from local package sources instead of debs published in apt repo like done by generate-bootstrap.sh. It allows bootstrap archives to be easily built for (forked) termux apps without having to publish an apt repo first. Usage: build-bootstraps.sh [command_options] Available command_options: [ -h | --help ] Display this help screen [ -f ] Force build even if packages have already been built. [ --android10 ] Generate bootstrap archives for Android 10+ for apk packaging system. [ -a | --add ] Additional packages to include into bootstrap archive. Multiple packages should be passed as comma-separated list. [ --architectures ] Override default list of architectures for which bootstrap archives will be created. Multiple architectures should be passed as comma-separated list. The package name/prefix that the bootstrap is built for is defined by TERMUX_APP_PACKAGE in 'scrips/properties.sh'. It defaults to 'me.sergiotarxz.openmg.x11'. If package name is changed, make sure to run `./scripts/run-docker.sh ./clean.sh` or pass '-f' to force rebuild of packages. ### Examples Build default bootstrap archives for all supported archs: ./scripts/run-docker.sh ./scripts/build-bootstraps.sh &> build.log Build default bootstrap archive for aarch64 arch only: ./scripts/run-docker.sh ./scripts/build-bootstraps.sh --architectures aarch64 &> build.log Build bootstrap archive with additionall openssh package for aarch64 arch only: ./scripts/run-docker.sh ./scripts/build-bootstraps.sh --architectures aarch64 --add openssh &> build.log HELP_EOF echo $'\n'"TERMUX_APP_PACKAGE: \"$TERMUX_APP_PACKAGE\"" echo "TERMUX_PREFIX: \"${TERMUX_PREFIX[*]}\"" echo "TERMUX_ARCHITECTURES: \"${TERMUX_ARCHITECTURES[*]}\"" } main() { local return_value while (($# > 0)); do case "$1" in -h|--help) show_usage return 0 ;; --android10) BOOTSTRAP_ANDROID10_COMPATIBLE=true ;; -a|--add) if [ $# -gt 1 ] && [ -n "$2" ] && [[ $2 != -* ]]; then for pkg in $(echo "$2" | tr ',' ' '); do ADDITIONAL_PACKAGES+=("$pkg") done unset pkg shift 1 else echo "[!] Option '--add' requires an argument." 1>&2 show_usage return 1 fi ;; --architectures) if [ $# -gt 1 ] && [ -n "$2" ] && [[ $2 != -* ]]; then TERMUX_ARCHITECTURES=() for arch in $(echo "$2" | tr ',' ' '); do TERMUX_ARCHITECTURES+=("$arch") done unset arch shift 1 else echo "[!] Option '--architectures' requires an argument." 1>&2 show_usage return 1 fi ;; -f) BUILD_PACKAGE_OPTIONS+=("-f") FORCE_BUILD_PACKAGES=1 ;; *) echo "[!] Got unknown option '$1'" 1>&2 show_usage return 1 ;; esac shift 1 done set_build_bootstrap_traps for package_arch in "${TERMUX_ARCHITECTURES[@]}"; do if [[ " ${TERMUX_DEFAULT_ARCHITECTURES[*]} " != *" $package_arch "* ]]; then echo "Unsupported architecture '$package_arch' for in architectures list: '${TERMUX_ARCHITECTURES[*]}'" 1>&2 echo "Supported architectures: '${TERMUX_DEFAULT_ARCHITECTURES[*]}'" 1>&2 return 1 fi done for package_arch in "${TERMUX_ARCHITECTURES[@]}"; do # The termux_step_finish_build stores package version in .built-packages directory, but # its not arch independent. So instead we create an arch specific one and symlink it # to the .built-packages directory so that users can easily switch arches without having # to rebuild packages TERMUX_BUILT_PACKAGES_DIRECTORY_FOR_ARCH="$TERMUX_BUILT_PACKAGES_DIRECTORY-$package_arch" mkdir -p "$TERMUX_BUILT_PACKAGES_DIRECTORY_FOR_ARCH" if [ -f "$TERMUX_BUILT_PACKAGES_DIRECTORY" ] || [ -d "$TERMUX_BUILT_PACKAGES_DIRECTORY" ]; then rm -rf "$TERMUX_BUILT_PACKAGES_DIRECTORY" fi ln -sf "$TERMUX_BUILT_PACKAGES_DIRECTORY_FOR_ARCH" "$TERMUX_BUILT_PACKAGES_DIRECTORY" if [[ $FORCE_BUILD_PACKAGES == "1" ]]; then rm -f "$TERMUX_BUILT_PACKAGES_DIRECTORY_FOR_ARCH"/* rm -f "$TERMUX_BUILT_DEBS_DIRECTORY"/* fi BOOTSTRAP_ROOTFS="$BOOTSTRAP_TMPDIR/rootfs-${package_arch}" BOOTSTRAP_PKGDIR="$BOOTSTRAP_TMPDIR/packages-${package_arch}" # Create initial directories for $TERMUX_PREFIX if ! ${BOOTSTRAP_ANDROID10_COMPATIBLE}; then mkdir -p "${BOOTSTRAP_ROOTFS}/${TERMUX_PREFIX}/etc/apt/apt.conf.d" mkdir -p "${BOOTSTRAP_ROOTFS}/${TERMUX_PREFIX}/etc/apt/preferences.d" mkdir -p "${BOOTSTRAP_ROOTFS}/${TERMUX_PREFIX}/var/lib/dpkg/info" mkdir -p "${BOOTSTRAP_ROOTFS}/${TERMUX_PREFIX}/var/lib/dpkg/triggers" mkdir -p "${BOOTSTRAP_ROOTFS}/${TERMUX_PREFIX}/var/lib/dpkg/updates" mkdir -p "${BOOTSTRAP_ROOTFS}/${TERMUX_PREFIX}/var/log/apt" touch "${BOOTSTRAP_ROOTFS}/${TERMUX_PREFIX}/var/lib/dpkg/available" touch "${BOOTSTRAP_ROOTFS}/${TERMUX_PREFIX}/var/lib/dpkg/status" fi mkdir -p "${BOOTSTRAP_ROOTFS}/${TERMUX_PREFIX}/tmp" PACKAGES=() EXTRACTED_PACKAGES=() # Package manager. if ! ${BOOTSTRAP_ANDROID10_COMPATIBLE}; then PACKAGES+=("apt") fi # Core utilities. PACKAGES+=("bash") PACKAGES+=("bzip2") if ! ${BOOTSTRAP_ANDROID10_COMPATIBLE}; then PACKAGES+=("command-not-found") else PACKAGES+=("proot") fi PACKAGES+=("coreutils") PACKAGES+=("curl") PACKAGES+=("dash") PACKAGES+=("diffutils") PACKAGES+=("findutils") PACKAGES+=("gawk") PACKAGES+=("grep") PACKAGES+=("gzip") PACKAGES+=("less") PACKAGES+=("procps") PACKAGES+=("psmisc") PACKAGES+=("sed") PACKAGES+=("tar") PACKAGES+=("termux-exec") PACKAGES+=("termux-keyring") PACKAGES+=("termux-tools") PACKAGES+=("util-linux") PACKAGES+=("xz-utils") # Additional. PACKAGES+=("ed") PACKAGES+=("debianutils") PACKAGES+=("dos2unix") PACKAGES+=("inetutils") PACKAGES+=("lsof") PACKAGES+=("nano") PACKAGES+=("net-tools") PACKAGES+=("patch") PACKAGES+=("unzip") # Handle additional packages. for add_pkg in "${ADDITIONAL_PACKAGES[@]}"; do if [[ " ${PACKAGES[*]} " != *" $add_pkg "* ]]; then PACKAGES+=("$add_pkg") fi done unset add_pkg # Build packages. for package_name in "${PACKAGES[@]}"; do set +e build_package "$package_arch" "$package_name" || return $? set -e done # Extract all debs. extract_debs || return $? # Create bootstrap archive. create_bootstrap_archive "$package_arch" || return $? done } main "$@"