i2pd-static-macos/build_i2pd.sh

1155 lines
38 KiB
Bash

#!/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<bool>()->default_value(true)/("http.enabled", value<bool>()->default_value(false)/' \
-e 's/("httpproxy.enabled", value<bool>()->default_value(true)/("httpproxy.enabled", value<bool>()->default_value(false)/' \
-e 's/("socksproxy.enabled", value<bool>()->default_value(true)/("socksproxy.enabled", value<bool>()->default_value(false)/' \
-e 's/("sam.enabled", value<bool>()->default_value(true)/("sam.enabled", value<bool>()->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 "$@"