#!/bin/bash -e ############################################################################### # MIT License # # Copyright (c) 2025 slave2anonymous # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. ############################################################################### ############################################################################### # i2pd Static Build Script for macOS # # Description: # Automates the process of building a static, universal binary for i2pd # with optional embedded tunnel configurations. This script provides a # flexible and reproducible build process for the I2P daemon on macOS. # # Features: # - Builds i2pd with configurable versions of dependencies # - Supports creating universal binaries for x86_64 and arm64 architectures # - Optional external tunnel configuration embedding # - Comprehensive logging and build stage management # - Flexible build stage selection # # Requirements: # - macOS Ventura (13.x) # - Xcode 14.3.1 with Command Line Tools # - Homebrew # - git # - cmake # - Sufficient disk space in /tmp or custom working directory # # Compatibility Targets: # - x86_64-apple-macos10.12 # - arm64-apple-macos11 # Ensures backward compatibility with older macOS versions # # Dependency Versions: # - i2pd: Configurable (default: 2.53.0) # - Boost: Configurable (default: 1_84_0) # - OpenSSL: Configurable (default: 3.5.0) # # Usage: # ./build_i2pd.sh [options] # # Options: # -d, --directory DIR Custom working directory (default: /tmp/i2pd) # -l, --log FILE Log output to specified file # -s, --stage STAGE Start build from specific stage # -t, --tunnels FILE Specify external tunnels configuration file # -h, --help Show help message # # Build Stages: # boost - Build Boost libraries # openssl - Build OpenSSL libraries # zlib - Build Zlib libraries # i2pd - Build i2pd # i2pd-make - Run 'make' for the i2pd build (assumes previous i2pd stage is complete) # universal - Create universal binary # # Examples: # ./build_i2pd.sh # Full build from scratch # ./build_i2pd.sh -d /path/to/build # Custom build directory # ./build_i2pd.sh -l build.log # Log to file # ./build_i2pd.sh -s openssl # Start from OpenSSL stage # ./build_i2pd.sh -t tunnels.conf # Include custom tunnels # # Compatibility Notes: # - Compiled with deployment targets: # * x86_64: macOS 10.12 (Sierra) # * arm64: macOS 11.0 (Big Sur) # - Ensures wide system compatibility # - Tested on macOS Ventura with Xcode 14.3.1 # # Build Configuration Flags: # - MACOSX_DEPLOYMENT_TARGET set to support minimum macOS versions # - Compiler optimizations for performance and compatibility # - Static linking of dependencies # # Compiler Optimization Levels: # - O2 optimization for balanced performance # - Architecture-specific optimizations # - Link-time optimization (LTO) enabled # # Security Hardening: # - Position Independent Executable (PIE) # - Stack protection # - Fortified source # - Reduced attack surface # # Performance Considerations: # - Minimized external dependencies # - Optimized for both Intel and Apple Silicon # - Reduced binary size # - No runtime library dependencies # # Recommended System Preparation: # 1. Install Xcode 14.3.1 from App Store # 2. Install Xcode Command Line Tools: # xcode-select --install # 3. Install Homebrew: # /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" # 4. Install build dependencies: # brew install cmake git # # Potential Build Customizations: # - Adjust dependency versions # - Modify compiler flags # - Add custom patches # - Embed specific tunnel configurations # # Known Limitations: # - Requires internet connection for downloading dependencies # - Build process may take significant time # - Requires approximately 4GB free disk space # # Troubleshooting: # - Ensure all dependencies are installed # - Check log file for detailed build information # - Verify tunnel configuration syntax if using -t option # - Review build environment requirements # # Project Links: # - Script Repository: https://github.com/slave2anonymous/i2pd-static-macos # - i2pd: https://github.com/PurpleI2P/i2pd # - I2P Network: https://geti2p.net/ # # Community & Support: # - GitHub Issues: https://github.com/PurpleI2P/i2pd/issues # - I2P Community Forums # # Version: 1.0.0 # Last Updated: 2025-08-19 ############################################################################### # Configuration export I2PD_VERSION="2.53.0" export I2PD_CONFIG_FILE="$DEV_PATH/i2pd/libi2pd/Config.cpp" export I2PD_CLIENT_CONTEXT_FILE="$DEV_PATH/i2pd/libi2pd_client/ClientContext.cpp" export BOOST_VERSION="1_84_0" export OPENSSL_VERSION="3.5.0" export ZLIB_VERSION="1.3.1" export DEV_PATH="/tmp/i2pd" export LOG_FILE="" SCRIPTPATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 && pwd -P )" # Help function show_help() { echo "i2pd Static Build Script for macOS" echo "Usage: $0 [options]" echo "" echo "Options:" echo " -d, --directory DIR Set custom working directory (default: /tmp/i2pd)" echo " -l, --log FILE Log output to specified file" echo " -s, --stage STAGE Start build from specific stage" echo " -t, --tunnels FILE Specify external tunnels configuration file" echo " -h, --help Show this help message" echo "" echo "Build Stages:" echo " boost - Build Boost libraries" echo " openssl - Build OpenSSL libraries" echo " zlib - Build Zlib libraries" echo " i2pd - Build i2pd" echo " i2pd-make - Run 'make' for the i2pd build (assumes previous i2pd stage is complete)" echo " universal - Create universal binary" echo "" echo "Examples:" echo " $0 # Full build from scratch" echo " $0 -d /path/to/build # Custom build directory" echo " $0 -l build.log # Log to file" echo " $0 -s openssl # Start from OpenSSL stage" echo " $0 -d /build -l build.log -s i2pd -t tunnels.conf # Combine options" echo "" echo "Note: Stages are cumulative. Starting from 'openssl' will also build zlib and i2pd." } # Function to log messages log_message() { local message="$1" echo "$message" if [ -n "$LOG_FILE" ]; then echo "[$(date '+%Y-%m-%d %H:%M:%S')] $message" >> "$LOG_FILE" fi } # Wrapper function for commands to log output execute_with_logging() { local log_prefix="[CMD] " # Separate environment variables from the command local env_vars=() local command_args=() local is_env=true for arg in "$@"; do if [[ "$is_env" == "true" && "$arg" == *"="* ]]; then # Collect environment variables env_vars+=("$arg") else # Switch to collecting command arguments is_env=false command_args+=("$arg") fi done # Log the full command log_message "${log_prefix}Executing: ${env_vars[*]} ${command_args[*]}" # Execute command with environment variables if [ ${#env_vars[@]} -gt 0 ]; then env "${env_vars[@]}" "${command_args[@]}" 2>&1 | while IFS= read -r line; do echo "${log_prefix}${line}" | tee -a "$LOG_FILE" done else # Standard command execution without environment variables "${command_args[@]}" 2>&1 | while IFS= read -r line; do echo "${log_prefix}${line}" | tee -a "$LOG_FILE" done fi # Capture and return the exit status local status="${PIPESTATUS[0]}" if [ $status -ne 0 ]; then log_message "${log_prefix}Command failed with status $status" fi return $status } # Parse command-line arguments with long options support parse_args() { # Reset variables DEV_PATH="/tmp/i2pd" LOG_FILE="" START_STAGE="" # Parse arguments manually while [[ $# -gt 0 ]]; do case "$1" in -d|--directory) DEV_PATH="$2" shift 2 ;; -l|--log) LOG_FILE="$2" shift 2 ;; -s|--stage) START_STAGE="$2" shift 2 ;; -h|--help) show_help exit 0 ;; -t|--tunnels) TUNNELS_CONFIG="$2" shift 2 ;; *) echo "Unknown option: $1" show_help exit 1 ;; esac done # Export tunnels config for use in other functions export TUNNELS_CONFIG } # Validate stage input validate_stage() { local valid_stages=("boost" "openssl" "zlib" "i2pd" "i2pd-make" "universal") local stage="$1" for valid_stage in "${valid_stages[@]}"; do if [[ "$stage" == "$valid_stage" ]]; then return 0 fi done log_message "[ERROR] Invalid stage: $stage" echo "Valid stages are: ${valid_stages[*]}" exit 1 } # Main script execution function main() { # Parse command-line arguments parse_args "$@" # Validate start stage if provided if [ -n "$START_STAGE" ]; then validate_stage "$START_STAGE" fi # Set up logging if [ -n "$LOG_FILE" ]; then touch "$LOG_FILE" log_message "Logging to $LOG_FILE" fi # Check for required tools cmd_check_and_install "brew" cmd_check_and_install "cmake" read -p "Press enter to start building at work dir: $DEV_PATH" # Create working directory [ ! -d "$DEV_PATH" ] && mkdir -p "$DEV_PATH" cd "$DEV_PATH" # Record start time BEGIN_DATE_IN_SEC=$(date "+%s") log_message "Build started at: $(date -r $BEGIN_DATE_IN_SEC)" # Determine starting stage if [ -z "$START_STAGE" ]; then START_STAGE="boost" fi # Run build stages case "$START_STAGE" in boost) run_stage "boost" run_stage "openssl" run_stage "zlib" run_stage "i2pd" run_stage "universal" ;; openssl) run_stage "openssl" run_stage "zlib" run_stage "i2pd" run_stage "universal" ;; zlib) run_stage "zlib" run_stage "i2pd" run_stage "universal" ;; i2pd) run_stage "i2pd" run_stage "universal" ;; i2pd-make) run_stage "i2pd-make" run_stage "universal" ;; universal) run_stage "universal" ;; *) log_message "[ERROR] Invalid stage: $START_STAGE" exit 1 ;; esac # Calculate and log total build time END_DATE_IN_SEC=$(date "+%s") log_message "Build completed at: $(date -r $END_DATE_IN_SEC)" TOTAL_SEC=$((END_DATE_IN_SEC - BEGIN_DATE_IN_SEC)) log_message "Total build time: $TOTAL_SEC seconds ($(($TOTAL_SEC/60)) minutes)" } # Detailed implementation of build stages (using your original script logic) # Function to check if a command is installed and install it if it's not cmd_check_and_install() { local cmd=$1 if ! command -v "$cmd" &> /dev/null; then log_message "[ERROR] $cmd Command not found" if [ "$cmd" == "brew" ]; then /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" else brew install "$cmd" fi fi # Check if the command is installed again if ! command -v "$cmd" &> /dev/null; then log_message "[ERROR] $cmd is not installed. Please install $cmd first." exit 1 fi } # Run stages functions (these will contain the original build steps) run_stage() { local stage="$1" log_message "Running stage: $stage" case "$stage" in boost) build_boost ;; openssl) build_openssl ;; zlib) build_zlib ;; i2pd) build_i2pd ;; i2pd-make) build_i2pd_make ;; universal) create_universal_binary ;; *) log_message "[ERROR] Invalid stage: $stage" exit 1 ;; esac } # Detailed build stage functions (placeholders for your original logic) build_boost() { log_message "Building Boost ${BOOST_VERSION}" # Check if Boost is installed via Homebrew if brew list --formula | grep -q '^boost$'; then log_message "Boost is installed via Homebrew. Uninstalling Boost..." brew uninstall boost fi # Download Boost if not exists [ ! -f "$DEV_PATH/boost_${BOOST_VERSION}.tar.bz2" ] && { # Try multiple download methods with verbose output log_message "Attempting to download Boost ${BOOST_VERSION}" # Try with additional curl options for better error handling curl -v \ --connect-timeout 30 \ --retry 3 \ --retry-delay 5 \ --retry-max-time 120 \ -L \ --tlsv1.2 \ --cacert /etc/ssl/cert.pem \ "https://archives.boost.io/release/${BOOST_VERSION//_/.}/source/boost_$BOOST_VERSION.tar.bz2" -o "boost_$BOOST_VERSION.tar.bz2" || { # Fallback to alternative download sources log_message "[WARNING] Primary download failed. Trying alternative sources..." # Alternative download methods local alt_sources=( "https://boostorg.jfrog.io/artifactory/main/release/${BOOST_VERSION//_/.}/source/boost_$BOOST_VERSION.tar.bz2" "https://sourceforge.net/projects/boost/files/boost/${BOOST_VERSION//_/.}/boost_$BOOST_VERSION.tar.bz2" ) local download_success=false for source in "${alt_sources[@]}"; do log_message "Attempting to download from: $source" curl -v \ --connect-timeout 30 \ --retry 3 \ --retry-delay 5 \ --retry-max-time 120 \ -L \ --tlsv1.2 \ --cacert /etc/ssl/cert.pem \ "$source" -o "boost_$BOOST_VERSION.tar.bz2" && { download_success=true break } done # Check if download was successful if [ "$download_success" = false ]; then log_message "[ERROR] Failed to download Boost ${BOOST_VERSION} from all sources" # Additional diagnostics log_message "Network Diagnostics:" ping -c 4 archives.boost.io || log_message "Ping to archives.boost.io failed" curl -v https://archives.boost.io || log_message "Curl connection test failed" exit 1 else log_message "Boost download complete!" fi } } # Extract Boost [ ! -d "$DEV_PATH/boost_$BOOST_VERSION" ] && { log_message "Extracting boost_$BOOST_VERSION.tar.bz2" tar -xvf boost_$BOOST_VERSION.tar.bz2 || { log_message "[ERROR] Extract of $DEV_PATH/boost_${BOOST_VERSION}.tar.bz2 failed!" exit 1 } } cd "$DEV_PATH/boost_$BOOST_VERSION" # Bootstrap Boost log_message "./bootstrap.sh" execute_with_logging ./bootstrap.sh # BOOST build for x86_64 log_message "Building Boost for x86_64" [ -d "$DEV_PATH/boost_$BOOST_VERSION/stage-x86_64" ] && rm -rf "$DEV_PATH/boost_$BOOST_VERSION/stage-x86_64" execute_with_logging ./b2 toolset=clang-darwin \ cxxflags="-arch x86_64 -target x86_64-apple-macos10.12" \ target-os=darwin \ variant=release \ link=static \ runtime-link=static \ address-model=64 \ --build-type=minimal \ --with-system \ --with-filesystem \ --with-program_options \ --with-date_time \ --stagedir=stage-x86_64 || { log_message "[ERROR] Boost x86_64 build failed!" exit 1 } # BOOST build for arm64 log_message "Building Boost for arm64" [ -d "$DEV_PATH/boost_$BOOST_VERSION/stage-arm64" ] && rm -rf "$DEV_PATH/boost_$BOOST_VERSION/stage-arm64" execute_with_logging ./b2 toolset=clang-darwin \ cxxflags="-arch arm64 -target arm64-apple-macos11" \ -a \ target-os=darwin \ variant=release \ link=static \ runtime-link=static \ address-model=64 \ --build-type=minimal \ --with-system \ --with-filesystem \ --with-program_options \ --with-date_time \ --stagedir=stage-arm64 || { log_message "[ERROR] Boost arm64 build failed!" exit 1 } log_message "Boost build completed successfully" cd "$DEV_PATH" } build_openssl() { log_message "Building OpenSSL" # Check if Openssl is installed via Homebrew if brew list --formula | grep -q '^openssl@3$'; then log_message "openssl@3 is installed via Homebrew. Uninstalling openssl@3..." brew uninstall openssl@3 fi # Clone OpenSSL if not exists [ ! -d "$DEV_PATH/openssl" ] && { git clone https://github.com/openssl/openssl cd "$DEV_PATH/openssl" git checkout openssl-${OPENSSL_VERSION} } # Update existing repo [ -d "$DEV_PATH/openssl" ] && { cd "$DEV_PATH/openssl" git stash git checkout openssl-${OPENSSL_VERSION} } # OpenSSL build for x86_64 log_message "Building OpenSSL for x86_64" [ ! -d "$DEV_PATH/openssl_x86_64" ] && cp -r "$DEV_PATH/openssl" "$DEV_PATH/openssl_x86_64" cd "$DEV_PATH/openssl_x86_64" [ -d "$DEV_PATH/stage-x86_64" ] && rm -rf "$DEV_PATH/stage-x86_64" execute_with_logging ./Configure darwin64-x86_64-cc \ no-rc2 no-rc4 no-rc5 no-idea no-bf no-cast no-whirlpool \ no-md2 no-md4 no-ripemd no-mdc2 no-camellia no-seed \ no-comp no-rfc3779 no-ec2m no-ssl2 no-srp no-sctp \ no-srtp no-shared no-tests \ --prefix="$DEV_PATH/stage-x86_64" \ CFLAGS="-O3 -Wall -target x86_64-apple-macos10.12" && \ perl configdata.pm --dump || { log_message "[ERROR] OpenSSL x86_64 configuration failed!" exit 1 } # Clean and build x86_64 [ -d "$DEV_PATH/openssl_x86_64" ] && make clean execute_with_logging make depend || { log_message "[ERROR] OpenSSL x86_64 make depend failed!" exit 1 } execute_with_logging make || { log_message "[ERROR] OpenSSL x86_64 make failed!" exit 1 } execute_with_logging make install_sw || { log_message "[ERROR] OpenSSL x86_64 make install_sw failed!" exit 1 } # OpenSSL build for arm64 log_message "Building OpenSSL for arm64" [ ! -d "$DEV_PATH/openssl_arm64" ] && cp -r "$DEV_PATH/openssl" "$DEV_PATH/openssl_arm64" cd "$DEV_PATH/openssl_arm64" [ -d "$DEV_PATH/stage-arm64" ] && rm -rf "$DEV_PATH/stage-arm64" execute_with_logging ./Configure darwin64-arm64-cc \ no-rc2 no-rc4 no-rc5 no-idea no-bf no-cast no-whirlpool \ no-md2 no-md4 no-ripemd no-mdc2 no-camellia no-seed \ no-comp no-rfc3779 no-ec2m no-ssl2 no-srp no-sctp \ no-srtp no-shared no-tests \ --prefix="$DEV_PATH/stage-arm64" \ CFLAGS="-O3 -Wall -target arm64-apple-macos11" && \ perl configdata.pm --dump || { log_message "[ERROR] OpenSSL arm64 configuration failed!" exit 1 } # Clean and build arm64 [ -d "$DEV_PATH/openssl_arm64" ] && make clean execute_with_logging make depend || { log_message "[ERROR] OpenSSL arm64 make depend failed!" exit 1 } execute_with_logging make || { log_message "[ERROR] OpenSSL arm64 make failed!" exit 1 } execute_with_logging make install_sw || { log_message "[ERROR] OpenSSL arm64 make install_sw failed!" exit 1 } log_message "OpenSSL build completed successfully" cd "$DEV_PATH" } build_zlib() { log_message "Building Zlib" # Clone Zlib if not exists [ ! -d "$DEV_PATH/zlib" ] && { cd "$DEV_PATH" git clone https://github.com/madler/zlib cd "$DEV_PATH/zlib" git checkout v${ZLIB_VERSION} } # Update CMake minimum required version log_message "Updating CMake minimum required version" sed -i '' 's/cmake_minimum_required(VERSION 2\.4\.4)/cmake_minimum_required(VERSION 3.5)/' "$DEV_PATH/zlib/CMakeLists.txt" # Zlib build for x86_64 log_message "Building Zlib for x86_64" cd "$DEV_PATH/zlib" [ -d "$DEV_PATH/zlib/build_x86_64" ] && rm -rf "$DEV_PATH/zlib/build_x86_64" mkdir "$DEV_PATH/zlib/build_x86_64" cd "$DEV_PATH/zlib/build_x86_64" execute_with_logging cmake .. -B . -G 'Unix Makefiles' \ -DBUILD_TYPE=Release \ -DCMAKE_C_FLAGS="-O3 -Wall -target x86_64-apple-macos10.12" \ -DBUILD_SHARED_LIBS=OFF \ -DSKIP_INSTALL_FILES=YES \ -DCMAKE_INSTALL_PREFIX="$DEV_PATH/stage-x86_64" \ -DCMAKE_OSX_ARCHITECTURES:STRING=x86_64 || { log_message "[ERROR] Zlib x86_64 CMake configuration failed!" exit 1 } execute_with_logging make || { log_message "[ERROR] Zlib x86_64 make failed!" exit 1 } execute_with_logging make install || { log_message "[ERROR] Zlib x86_64 make install failed!" exit 1 } # Zlib build for arm64 log_message "Building Zlib for arm64" cd "$DEV_PATH/zlib" [ -d "$DEV_PATH/zlib/build_arm64" ] && rm -rf "$DEV_PATH/zlib/build_arm64" mkdir "$DEV_PATH/zlib/build_arm64" cd "$DEV_PATH/zlib/build_arm64" execute_with_logging cmake .. -B . -G 'Unix Makefiles' \ -DBUILD_TYPE=Release \ -DCMAKE_C_FLAGS="-O3 -Wall -target arm64-apple-macos11" \ -DBUILD_SHARED_LIBS=OFF \ -DSKIP_INSTALL_FILES=YES \ -DCMAKE_INSTALL_PREFIX="$DEV_PATH/stage-arm64" \ -DCMAKE_OSX_ARCHITECTURES:STRING=arm64 || { log_message "[ERROR] Zlib arm64 CMake configuration failed!" exit 1 } execute_with_logging make || { log_message "[ERROR] Zlib arm64 make failed!" exit 1 } execute_with_logging make install || { log_message "[ERROR] Zlib arm64 make install failed!" exit 1 } # Remove .dylib files log_message "Cleaning up dynamic libraries" find "$DEV_PATH/stage-x86_64" -mindepth 1 -name \*.dylib -exec rm -rf {} \; find "$DEV_PATH/stage-arm64" -mindepth 1 -name \*.dylib -exec rm -rf {} \; log_message "Zlib build completed successfully" cd "$DEV_PATH" } patch_config() { # Patch Config.cpp log_message "Patching $I2PD_CONFIG_FILE" cd "$DEV_PATH/i2pd" git checkout "$I2PD_VERSION" -- "$I2PD_CONFIG_FILE" sed -i '' \ -e 's/("daemon", bool_switch()->default_value(false)/("daemon", bool_switch()->default_value(true)/' \ -e 's/("http.enabled", value()->default_value(true)/("http.enabled", value()->default_value(false)/' \ -e 's/("httpproxy.enabled", value()->default_value(true)/("httpproxy.enabled", value()->default_value(false)/' \ -e 's/("socksproxy.enabled", value()->default_value(true)/("socksproxy.enabled", value()->default_value(false)/' \ -e 's/("sam.enabled", value()->default_value(true)/("sam.enabled", value()->default_value(false)/' \ "$I2PD_CONFIG_FILE" && log_message "Patching $I2PD_CONFIG_FILE done!" || ( log_message "[ERROR] Patching $I2PD_CONFIG_FILE failed!"; exit 1 ) } # Function for patching ClientContext.cpp to include tunnels.conf in i2pd static build # This function modifies the ClientContext.cpp file to hardcode tunnel configurations # directly into the i2pd binary, eliminating the need for an external tunnels.conf file # # The function converts a standard tunnels.conf configuration into C++ property tree # initialization code that will be embedded in the ClientContext.cpp file # # Input file format example: # [tunnel1] # type = client # address = 127.0.0.1 # port = 10001 # destination = something.b32.i2p # keys = transparent # [tunnel2] # type = udpclient # address = 127.0.0.1 # port = 1194 # destination = another.b32.i2p # keys = tunnel2.dat # [server1] # type = server # host = 127.0.0.1 # port = 8080 # keys = server1.dat # # Benefits: # - Allows static configuration of tunnels during compilation # - Removes dependency on external configuration files # - Enables reproducible builds with predefined tunnel settings # patch_client_context() { local TUNNELS_FILE="$1" log_message "Patching $I2PD_CLIENT_CONTEXT_FILE" # Validate tunnels file exists if [ ! -f "$TUNNELS_FILE" ]; then log_message "[ERROR] Tunnels configuration file not found: $TUNNELS_FILE" return 1 fi cd "$DEV_PATH/i2pd" git checkout "$I2PD_VERSION" -- "$I2PD_CLIENT_CONTEXT_FILE" # Create backup cp "$I2PD_CLIENT_CONTEXT_FILE" "${I2PD_CLIENT_CONTEXT_FILE}.bkp" # Create a temporary file for converted tunnels local TEMP_CONVERTED=$(mktemp) # Convert tunnels.conf to property tree format using strict sed and bash local tunnel_name="" local first_tunnel=true while IFS= read -r line; do if [[ $line =~ ^\[([^]]+)\]$ ]]; then tunnel_name="${BASH_REMATCH[1]}" # Add section separator for tunnels if [[ "$first_tunnel" = true ]]; then echo " pt.put(\"\", \"[$tunnel_name]\");" >> "$TEMP_CONVERTED" first_tunnel=false else echo " pt.put(\"\", \"[$tunnel_name]\");" >> "$TEMP_CONVERTED" fi elif [[ $line =~ ^type[[:space:]]*=[[:space:]]*(.+)$ ]]; then echo " pt.put(\"$tunnel_name.type\", \"${BASH_REMATCH[1]}\");" >> "$TEMP_CONVERTED" elif [[ $line =~ ^address[[:space:]]*=[[:space:]]*(.+)$ ]]; then echo " pt.put(\"$tunnel_name.address\", \"${BASH_REMATCH[1]}\");" >> "$TEMP_CONVERTED" elif [[ $line =~ ^host[[:space:]]*=[[:space:]]*(.+)$ ]]; then echo " pt.put(\"$tunnel_name.host\", \"${BASH_REMATCH[1]}\");" >> "$TEMP_CONVERTED" elif [[ $line =~ ^port[[:space:]]*=[[:space:]]*(.+)$ ]]; then port="${BASH_REMATCH[1]}" echo " pt.put(\"$tunnel_name.port\", \"$port\");" >> "$TEMP_CONVERTED" echo " pt.put(\"$tunnel_name.destinationport\", \"$port\");" >> "$TEMP_CONVERTED" elif [[ $line =~ ^destination[[:space:]]*=[[:space:]]*(.+)$ ]]; then echo " pt.put(\"$tunnel_name.destination\", \"${BASH_REMATCH[1]}\");" >> "$TEMP_CONVERTED" elif [[ $line =~ ^keys[[:space:]]*=[[:space:]]*(.+)$ ]]; then echo " pt.put(\"$tunnel_name.keys\", \"${BASH_REMATCH[1]}\");" >> "$TEMP_CONVERTED" fi done < "$TUNNELS_FILE" # Find the line to replace local LINE_NUM=$(grep -n "boost::property_tree::ptree pt;" "$I2PD_CLIENT_CONTEXT_FILE" | cut -d: -f1 | tail -1) local LINE_TO_REPLACE=$((LINE_NUM + 5)) # Create a sed script for replacement local SED_SCRIPT=$(mktemp) echo "${LINE_TO_REPLACE}r $TEMP_CONVERTED" > "$SED_SCRIPT" echo "${LINE_TO_REPLACE}d" >> "$SED_SCRIPT" # Use sed to replace the lines sed -f "$SED_SCRIPT" "$I2PD_CLIENT_CONTEXT_FILE" > "${I2PD_CLIENT_CONTEXT_FILE}.new" # Check if replacement was successful if [ $? -eq 0 ]; then # Replace the original file mv "${I2PD_CLIENT_CONTEXT_FILE}.new" "$I2PD_CLIENT_CONTEXT_FILE" log_message "[INFO] Successfully patched ClientContext.cpp with tunnels from $TUNNELS_FILE" # Verify the changes echo "Verifying changes:" sed -n "$((LINE_TO_REPLACE-2)),$((LINE_TO_REPLACE+20))p" "$I2PD_CLIENT_CONTEXT_FILE" # Clean up temporary files rm -f "$TEMP_CONVERTED" "$SED_SCRIPT" return 0 else log_message "[ERROR] Failed to patch ClientContext.cpp" # Restore from backup if patch fails cp "${I2PD_CLIENT_CONTEXT_FILE}.bkp" "$I2PD_CLIENT_CONTEXT_FILE" # Clean up temporary files rm -f "$TEMP_CONVERTED" "$SED_SCRIPT" "${I2PD_CLIENT_CONTEXT_FILE}.new" # Additional debugging information echo "Detailed error information:" echo "Line to replace: $LINE_TO_REPLACE" # Check file permissions and writability ls -l "$I2PD_CLIENT_CONTEXT_FILE" # Additional system-level diagnostics echo "Disk space:" df -h return 1 fi } build_i2pd() { log_message "Building i2pd" # Prepare toolchain files log_message "Preparing toolchain files" echo ' set(CMAKE_SYSTEM_NAME Darwin) set(CMAKE_C_COMPILER gcc) set(CMAKE_CXX_COMPILER g++) set(CMAKE_FIND_ROOT_PATH /usr) set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_VERBOSE_MAKEFILE on) ' > "$DEV_PATH/toolchain-x86_64.cmake" echo ' set(CMAKE_SYSTEM_NAME Darwin) set(CMAKE_C_COMPILER gcc) set(CMAKE_CXX_COMPILER g++) set(CMAKE_FIND_ROOT_PATH /usr) set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_VERBOSE_MAKEFILE on) ' > "$DEV_PATH/toolchain-arm64.cmake" # Clone i2pd if not exists [ ! -d "$DEV_PATH/i2pd" ] && { cd "$DEV_PATH" git clone https://github.com/PurpleI2P/i2pd } # Checkout specific version cd "$DEV_PATH/i2pd" git stash git checkout "$I2PD_VERSION" # Apply patches patch_config # Optional tunnels configuration patching if [ -n "$TUNNELS_CONFIG" ]; then patch_client_context "$TUNNELS_CONFIG" fi # Modify CMakeLists.txt to always build boost static log_message "Modifying CMakeLists.txt" sed -i '' 's/set_target_properties("${PROJECT_NAME}" PROPERTIES LINK_FLAGS "-static")/#/g' "$DEV_PATH/i2pd/build/CMakeLists.txt" # i2pd build for x86_64 log_message "Building i2pd for x86_64" [ -d "$DEV_PATH/i2pd-x86_64-build" ] && rm -rf "$DEV_PATH/i2pd-x86_64-build" mkdir "$DEV_PATH/i2pd-x86_64-build" cd "$DEV_PATH/i2pd-x86_64-build" execute_with_logging BOOST_ROOT="$DEV_PATH/boost_$BOOST_VERSION" cmake -G 'Unix Makefiles' "$DEV_PATH/i2pd/build" \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_TOOLCHAIN_FILE="$DEV_PATH/toolchain-x86_64.cmake" \ -DWITH_AESNI=OFF \ -DWITH_UPNP=OFF \ -DWITH_LIBRARY=OFF \ -DWITH_BINARY=ON \ -DWITH_STATIC=ON \ -DWITH_HARDENING=ON \ -DCMAKE_INSTALL_PREFIX:PATH="$DEV_PATH/bin/x86_64" \ -DZLIB_ROOT="$DEV_PATH/stage-x86_64" \ -DBOOST_LIBRARYDIR:PATH="$DEV_PATH/boost_$BOOST_VERSION/stage-x86_64/lib" \ -DOPENSSL_ROOT_DIR:PATH="$DEV_PATH/stage-x86_64" \ -DCMAKE_CXX_FLAGS="-arch x86_64 -target x86_64-apple-macos10.12" || { log_message "[ERROR] i2pd x86_64 CMake configuration failed!" exit 1 } execute_with_logging make VERBOSE=1 || { log_message "[ERROR] i2pd x86_64 build failed!" exit 1 } execute_with_logging strip i2pd || { log_message "[ERROR] Stripping x86_64 binary failed!" exit 1 } # i2pd build for arm64 log_message "Building i2pd for arm64" [ -d "$DEV_PATH/i2pd-arm64-build" ] && rm -rf "$DEV_PATH/i2pd-arm64-build" mkdir "$DEV_PATH/i2pd-arm64-build" cd "$DEV_PATH/i2pd-arm64-build" execute_with_logging BOOST_ROOT="$DEV_PATH/boost_$BOOST_VERSION" cmake -G 'Unix Makefiles' "$DEV_PATH/i2pd/build" \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_TOOLCHAIN_FILE="$DEV_PATH/toolchain-arm64.cmake" \ -DWITH_AESNI=OFF \ -DWITH_UPNP=OFF \ -DWITH_LIBRARY=OFF \ -DWITH_BINARY=ON \ -DWITH_STATIC=ON \ -DWITH_HARDENING=ON \ -DCMAKE_INSTALL_PREFIX:PATH="$DEV_PATH/bin/arm64" \ -DZLIB_ROOT="$DEV_PATH/stage-arm64" \ -DBOOST_LIBRARYDIR:PATH="$DEV_PATH/boost_$BOOST_VERSION/stage-arm64/lib" \ -DOPENSSL_ROOT_DIR:PATH="$DEV_PATH/stage-arm64" \ -DCMAKE_CXX_FLAGS="-arch arm64 -target arm64-apple-macos11" || { log_message "[ERROR] i2pd arm64 CMake configuration failed!" exit 1 } execute_with_logging make VERBOSE=1 || { log_message "[ERROR] i2pd arm64 build failed!" exit 1 } execute_with_logging strip i2pd || { log_message "[ERROR] Stripping arm64 binary failed!" exit 1 } log_message "i2pd build completed successfully" } build_i2pd_make() { cd "$DEV_PATH/i2pd" git checkout "$I2PD_VERSION" -- "$I2PD_CONFIG_FILE" # Ask user about patching config log_message "Do you want to patch the Config.cpp file? (y/N): " read patch_config_response # Convert response to lowercase for case-insensitive comparison patch_config_response=$(echo "$patch_config_response" | tr '[:upper:]' '[:lower:]') if [[ "$patch_config_response" == "y" || "$patch_config_response" == "yes" ]]; then log_message "User selected to patch Config.cpp" patch_config else log_message "Skipping Config.cpp patching as per user request" fi # Optional tunnels configuration patching if [ -n "$TUNNELS_CONFIG" ]; then patch_client_context "$TUNNELS_CONFIG" fi log_message "Building i2pd using make for x86_64" cd "$DEV_PATH/i2pd-x86_64-build" execute_with_logging make VERBOSE=1 || { log_message "[ERROR] i2pd x86_64 make build failed!" exit 1 } execute_with_logging strip i2pd || { log_message "[ERROR] Stripping x86_64 binary failed!" exit 1 } log_message "Building i2pd using make for arm64" cd "$DEV_PATH/i2pd-arm64-build" execute_with_logging make VERBOSE=1 || { log_message "[ERROR] i2pd arm64 make build failed!" exit 1 } execute_with_logging strip i2pd || { log_message "[ERROR] Stripping arm64 binary failed!" exit 1 } log_message "i2pd make build completed successfully" } create_universal_binary() { log_message "Creating universal binary" # Verify x86_64 and arm64 binaries exist [ -f "$DEV_PATH/i2pd-x86_64-build/i2pd" ] || { log_message "[ERROR] x86_64 i2pd binary not found!" exit 1 } [ -f "$DEV_PATH/i2pd-arm64-build/i2pd" ] || { log_message "[ERROR] arm64 i2pd binary not found!" exit 1 } # Create universal binary directory mkdir -p "$DEV_PATH/universal" # Check architectures of individual binaries log_message "x86_64 binary architectures:" execute_with_logging lipo -archs "$DEV_PATH/i2pd-x86_64-build/i2pd" log_message "arm64 binary architectures:" execute_with_logging lipo -archs "$DEV_PATH/i2pd-arm64-build/i2pd" # Create universal binary execute_with_logging lipo -create -output "$DEV_PATH/universal/i2pd" \ "$DEV_PATH/i2pd-x86_64-build/i2pd" \ "$DEV_PATH/i2pd-arm64-build/i2pd" || { log_message "[ERROR] Creating universal binary failed!" exit 1 } # Verify universal binary log_message "Universal binary architectures:" execute_with_logging lipo -archs "$DEV_PATH/universal/i2pd" log_message "Universal binary created successfully" # Prepare success message local SUCCESS_MESSAGE=" 🎉 i2pd Universal Binary Successfully Built! 🚀 Binary Location: $DEV_PATH/universal/i2pd 🔧 Usage Instructions: 1. Show Help/Available Options: $DEV_PATH/universal/i2pd --help This will display all available command-line options and their descriptions. 2. Default Behavior (Daemonized): - Runs in background - All services (HTTP, HTTP Proxy, SOCKS Proxy) are DISABLED - Only tunnels from configuration file are available Simply run: $DEV_PATH/universal/i2pd 3. Console/Foreground Mode: Use these options to customize behavior: --http.enabled=1 : Enable HTTP server --http.port=PORT : Set HTTP server port --httpproxy.enabled=1 : Enable HTTP Proxy --httpproxy.port=PORT : Set HTTP Proxy port --socksproxy.enabled=1 : Enable SOCKS Proxy --socksproxy.port=PORT : Set SOCKS Proxy port --sam.enabled=1 : Enable SAM interface --log=file : Logs destination to a file --logfile=/tmp/i2pd.log : Path to logfile --loglevel=info : Set the minimal level of log messages Example: $DEV_PATH/universal/i2pd --http.enabled=1 --http.port=7070 --httpproxy.enabled=1 --httpproxy.port=4444 --socksproxy.enabled=1 --socksproxy.port=4447 --log=file --logfile=/tmp/i2pd.log --loglevel=debug 4. Tunnels Configuration: - If a tunnels configuration file was provided during build: Tunnels will be available at their specified b32.i2p addresses - To modify tunnels, edit the configuration file used during build ⚠️ Notes: - Always ensure you have the latest configuration - Check i2pd documentation for advanced configuration options " # Log the success message log_message "$SUCCESS_MESSAGE" # Also echo to console echo "$SUCCESS_MESSAGE" } # Run the main function main "$@"