#!/bin/bash set -euo pipefail SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" # list of toolchains to install TOOLCHAIN_LIST=( "aarch64-linux-musl-cross" "x86_64-linux-musl-cross" "x86_64-linux-musl-native" ) # If the user is not root, use sudo SUDOCMD="" [ "$EUID" -eq 0 ] || SUDOCMD="sudo" # If the user is not root, use the home directory of the user who ran the script USER_HOME="$HOME" if [ -n "${SUDO_USER:-}" ] && [ "$SUDO_USER" != "root" ]; then USER_HOME=$(eval echo "~${SUDO_USER}") fi # test sudo is working or we're root (return non-zero if not root!) if ! ${SUDOCMD:-} true; then echo "Error: This script must be run as root or with sudo privileges." >&2 exit 1 fi _die() { echo -e "Error: $1" >&2 exit 1 } # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' # No Color # Function to print status messages print_status() { echo -e "${GREEN}[*] $1${NC}" } print_error() { echo -e "${RED}[!] $1${NC}" } print_warning() { echo -e "${YELLOW}[!] $1${NC}" } #---------------------------------------------------------------------------------------------------------- # Headers on Host (Native) #---------------------------------------------------------------------------------------------------------- # Function to check if a package is installed is_package_installed() { if [ "$OS" = "Alpine Linux" ]; then apk info | grep -q "^$1$" else dpkg -l "$1" fi } function check_packages() { local PACKAGES # Detect distribution if [ -f /etc/os-release ]; then . /etc/os-release OS=$NAME VER=$VERSION_ID else print_error "Could not detect distribution" exit 1 fi print_status "Detected OS: $OS $VER" # Define packages based on distribution case $OS in "Ubuntu") # Common packages for both Ubuntu and Debian PACKAGES="build-essential cmake git wget tar curl ninja-build \ mold nodejs npm perl jq ccache upx-ucl nlohmann-json3-dev \ sed gawk autoconf automake libtool autotools-dev" ;; *) print_error "Unsupported distribution: $OS" exit 1 ;; esac # Install missing packages print_status "Checking and installing required packages..." for pkg in $PACKAGES; do if ! is_package_installed "$pkg"; then print_error "Package $pkg is not installed" exit 1 else print_status "$pkg is already installed" fi done } # function install_libassert() { # # put libassert headers on the host. # echo "Checking for libassert headers" # if [ ! -f "/usr/local/lib/libassert.a" ]; then # echo "libassert not found, installing..." # git clone https://github.com/jeremy-rifkin/libassert.git # #git checkout v2.1.5 # mkdir libassert/build # cd "libassert/build" || exit 1 # cmake .. -DCMAKE_BUILD_TYPE=Release # make -j # ${SUDOCMD:-} make install # cd ../.. # rm -rf libassert # else # echo "libassert headers already installed" # fi # } #---------------------------------------------------------------------------------------------------------- # set_current_arch #---------------------------------------------------------------------------------------------------------- function set_current_arch() { export FULLARCH="$1" # e.g. aarch64-linux-musl-cross # ARCH=aarch64 # ARCH_OS=linux-musl-cross # FULLARCH=aarch64-linux-musl-cross # ARCH_SHORT_PREFIX=aarch64-linux-musl # MUSL_PATH=/home/jde/.musl-cr oss/aarch64-linux-musl-cross # arch should just be x86_64 or aarch64 [ -n "$FULLARCH" ] || _die "No toolchain provided" export MUSL_ROOT_PATH="${USER_HOME}/.musl-cross" export ARCH="${FULLARCH%%-*}" export ARCH_OS="${FULLARCH#*-}" export ARCH_SHORT_PREFIX="${FULLARCH%-*}" export ARCH_MUSL_PATH="${MUSL_ROOT_PATH}/${FULLARCH}" export ARCH_SYSROOT="${ARCH_MUSL_PATH}/${ARCH_SHORT_PREFIX}/sysroot" export LIBPATH="$ARCH_SYSROOT/usr/lib" [ -d "$LIBPATH" ] || export LIBPATH="$ARCH_SYSROOT/usr/lib64" echo "ARCH: ${ARCH}" echo "ARCH_OS: ${ARCH_OS}" echo "ARCH_SHORT_PREFIX: ${ARCH_SHORT_PREFIX}" echo "ARCH_MUSL_PATH: ${ARCH_MUSL_PATH}" echo "ARCH_SYSROOT: ${ARCH_SYSROOT}" echo "LIBPATH: ${LIBPATH}" JOBS=$(nproc) # Set JOBS to the number of available CPU cores # Set cross-compiler and sysroot export CC="$ARCH_MUSL_PATH/bin/${ARCH_SHORT_PREFIX}-gcc" [ -f "$CC" ] || export CC="$ARCH_MUSL_PATH/bin/gcc" export CXX="${ARCH_MUSL_PATH}/bin/${ARCH_SHORT_PREFIX}-g++" [ -f "$CXX" ] || export CXX="$ARCH_MUSL_PATH/bin/g++" export AR="$ARCH_MUSL_PATH/bin/${ARCH_SHORT_PREFIX}-ar" [ -f "$AR" ] || export AR="$ARCH_MUSL_PATH/bin/ar" export RANLIB="$ARCH_MUSL_PATH/bin/${ARCH_SHORT_PREFIX}-ranlib" [ -f "$RANLIB" ] || export RANLIB="$ARCH_MUSL_PATH/bin/ranlib" echo "CC: ${CC}" echo "CXX: ${CXX}" echo "AR: ${AR}" echo "RANLIB: ${RANLIB}" export CFLAGS="--sysroot=$ARCH_SYSROOT -I$ARCH_SYSROOT/usr/include" export CXXFLAGS="--sysroot=$ARCH_SYSROOT -I$ARCH_SYSROOT/usr/include" export CMAKE_EXE_LINKER_FLAGS="-fuse-ld=mold" export CMAKE_MODULE_LINKER_FLAGS="-fuse-ld=mold" export CMAKE_SHARED_LINKER_FLAGS="-fuse-ld=mold" export LDFLAGS="--sysroot=$ARCH_SYSROOT" export MAKEFLAGS="-j${JOBS}" } #---------------------------------------------------------------------------------------------------------- # unwind for musl cross toolchains #---------------------------------------------------------------------------------------------------------- function install_unwind_musl() { set_current_arch "$1" [ -d "$ARCH_MUSL_PATH" ] || _die "MUSL_PATH does not exist: $ARCH_MUSL_PATH" local BUILD_DIR="${SCRIPT_DIR}/build.$ARCH" PREVDIR="$PWD" rm -rf "$BUILD_DIR" && mkdir -p "$BUILD_DIR" && cd "$BUILD_DIR" [ ! -f "$LIBPATH/libunwind.a" ] || echo "Unwind $ARCH is already installed" && return git clone https://github.com/libunwind/libunwind.git cd libunwind autoreconf -i # Configure for cross-compiling CC="$CC" AR="$AR" RANLIB="$RANLIB" ./configure \ --host="$ARCH_SHORT_PREFIX" \ --prefix="$ARCH_SYSROOT/usr" \ --enable-static \ --disable-shared \ --disable-minidebuginfo \ --disable-zlibdebuginfo make -j"$JOBS" ${SUDOCMD:-} make install cd "$PREVDIR" rm -rf "$BUILD_DIR" } #---------------------------------------------------------------------------------------------------------- # libassert for musl cross toolchains #---------------------------------------------------------------------------------------------------------- function install_libassert_musl() { local TOOLCHAIN="$1" set_current_arch "$TOOLCHAIN" echo "Installing libassert for ${ARCH}" local BUILD_DIR="/tmp/libassert-build-${ARCH}" rm -rf "${BUILD_DIR}" mkdir -p "${BUILD_DIR}" pushd "${BUILD_DIR}" git clone --depth 1 --branch v2.1.5 https://github.com/jeremy-rifkin/libassert.git . mkdir build && cd build cmake .. \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_SYSROOT="${ARCH_SYSROOT}" \ -DCMAKE_C_COMPILER="${CC}" \ -DCMAKE_CXX_COMPILER="${CXX}" \ -DCMAKE_INSTALL_PREFIX="${ARCH_SYSROOT}/usr" \ -DBUILD_SHARED_LIBS=OFF make make install popd rm -rf "${BUILD_DIR}" } #---------------------------------------------------------------------------------------------------------- # cpptrace for musl cross toolchains #---------------------------------------------------------------------------------------------------------- function install_cpptrace_musl() { local TOOLCHAIN="$1" set_current_arch "$TOOLCHAIN" echo "Installing cpptrace for ${ARCH}" local BUILD_DIR="/tmp/cpptrace-build-${ARCH}" rm -rf "${BUILD_DIR}" mkdir -p "${BUILD_DIR}" pushd "${BUILD_DIR}" git clone --depth 1 --branch v0.8.3 https://github.com/jeremy-rifkin/cpptrace.git . mkdir build && cd build cmake .. \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_SYSROOT="${ARCH_SYSROOT}" \ -DCMAKE_C_COMPILER="${CC}" \ -DCMAKE_CXX_COMPILER="${CXX}" \ -DCMAKE_INSTALL_PREFIX="${ARCH_SYSROOT}/usr" \ -DCPPTRACE_UNWIND_WITH_LIBUNWIND=ON \ -DCPPTRACE_UNWIND_WITH_UNWIND=OFF \ -DCPPTRACE_UNWIND_WITH_EXECINFO=OFF \ -DCPPTRACE_UNWIND_WITH_WINAPI=OFF \ -DCPPTRACE_UNWIND_WITH_DBGHELP=OFF \ -DCPPTRACE_UNWIND_WITH_NOTHING=OFF \ -DCPPTRACE_NO_AUTO_CONFIG=ON \ -DLibUnwind_INCLUDE_DIR="${ARCH_SYSROOT}/usr/include" \ -DLibUnwind_LIBRARY="${ARCH_SYSROOT}/usr/lib/libunwind.a" \ -DLibUnwind_FOUND=TRUE \ -DBUILD_SHARED_LIBS=OFF \ -DCPPTRACE_STATIC_DEFINE=ON make -j$(nproc) make install popd rm -rf "${BUILD_DIR}" } #---------------------------------------------------------------------------------------------------------- # OpenSSL for musl cross toolchains #---------------------------------------------------------------------------------------------------------- toolchain_to_openssl_target() { case "$1" in aarch64-linux-musl-*) echo "linux-aarch64" ;; x86_64-linux-musl-*) echo "linux-x86_64" ;; *) _die "unsupported toolchain: $1" ;; esac } function install_openssl_musl() { OPENSSL_VERSION=3.5.0 set_current_arch "${1:-}" PREVDIR="$PWD" [ -d "$ARCH_MUSL_PATH" ] || _die "MUSL_PATH does not exist: $ARCH_MUSL_PATH" [ ! -f "$LIBPATH/libssl.a" ] || echo "OpenSSL $ARCH is already installed" && return local BUILD_DIR="${SCRIPT_DIR}/build.$ARCH" rm -rf "$BUILD_DIR" && mkdir -p "$BUILD_DIR" && cd "$BUILD_DIR" # Helper: compare versions (returns 0 if $1 >= $2) version_ge() { [ "$1" = "$2" ] && return 0 [ "$(printf '%s\n' "$1" "$2" | sort -V | head -n1)" = "$2" ] } echo "===============================" echo "Checking OpenSSL for $ARCH..." echo "===============================" OPENSSL_TARGET=$(toolchain_to_openssl_target "$FULLARCH") wget "https://www.openssl.org/source/openssl-${OPENSSL_VERSION}.tar.gz" || _die "Failed to download openssl-${OPENSSL_VERSION}.tar.gz" tar xf "openssl-${OPENSSL_VERSION}.tar.gz" || _die "Failed to extract openssl-${OPENSSL_VERSION}.tar.gz" cd "openssl-${OPENSSL_VERSION}" || _die "Failed to cd to openssl-${OPENSSL_VERSION}" echo "----------------------------------------------" echo "Configuring for $ARCH with sysroot $SYSROOT..." CC="$CC" AR="$AR" RANLIB="$RANLIB" ./Configure "$OPENSSL_TARGET" no-shared --prefix="$SYSROOT/usr" echo "Building..." make -j"$JOBS" echo "Installing to $SYSROOT/usr ..." ${SUDOCMD:-} make install_sw echo "Done for $ARCH." echo cd "$PREVDIR" rm -rf "$BUILD_DIR" echo "OpenSSL built and installed for $FULLARCH" } # ---------------------------------------------------------------------------------------------------------- # MUSL CROSS COMPILERS # ---------------------------------------------------------------------------------------------------------- function check_path() { local BASHRC="${USER_HOME}/.bashrc" local TOOLCHAIN="$1" local MUSL_PATH="$INSTALL_DIR/$TOOLCHAIN/bin" if ! echo "$PATH" | grep -q "$MUSL_PATH"; then echo "Adding $MUSL_PATH to PATH in $BASHRC" PATH_LINE="export PATH=\"$MUSL_PATH:\$PATH\"" if ! grep -Fxq "$PATH_LINE" "$BASHRC"; then echo "" >> "$BASHRC" echo "# Add musl cross compilers to PATH for dropshell" >> "$BASHRC" echo "$PATH_LINE" >> "$BASHRC" echo "Added musl cross compilers to $BASHRC" echo "You should run 'source ~/.bashrc' to update your PATH" else echo "You should run 'source ~/.bashrc' to update your PATH" fi fi } function install_musl_cross() { local TOOLCHAIN="$1" local MUSL_CC_URL="https://getbin.xyz" if [ ! -d "$INSTALL_DIR/$TOOLCHAIN" ]; then echo "Downloading $TOOLCHAIN musl cross toolchain..." wget -nc -O "$TMPDIR/$TOOLCHAIN.tgz" "$MUSL_CC_URL/$TOOLCHAIN.tgz:latest" tar -C "$INSTALL_DIR" -xvf "$TMPDIR/$TOOLCHAIN.tgz" else echo "$TOOLCHAIN musl cross toolchain already installed" fi } function install_musl() { echo "Installing musl toolchain" # Set install directory INSTALL_DIR="$USER_HOME/.musl-cross" mkdir -p "$INSTALL_DIR" TMPDIR=$(mktemp -d) trap 'rm -rf "$TMPDIR"' EXIT for TOOLCHAIN in "${TOOLCHAIN_LIST[@]}"; do install_musl_cross "$TOOLCHAIN" check_path "$TOOLCHAIN" install_openssl_musl "$TOOLCHAIN" install_unwind_musl "$TOOLCHAIN" install_libassert_musl "$TOOLCHAIN" install_cpptrace_musl "$TOOLCHAIN" done # Clean up rm -rf "$TMPDIR" } function output_version() { local VERSIONDATE local CONFIG_DIR="$USER_HOME/.config/dropshell-build/" mkdir -p "$CONFIG_DIR" VERSIONDATE="$(date +%Y.%m%d.%H%M)" echo "$VERSIONDATE" > "$CONFIG_DIR/version" || echo "Error: Failed to write version file to $CONFIG_DIR/version" } #---------------------------------------------------------------------------------------------------------- # Main #---------------------------------------------------------------------------------------------------------- function main() { check_packages install_musl output_version echo "Done" } main "$@"