#!/bin/bash # pack_offline_bundle_airgap_macos.sh # ============================================================================ # Gradle Offline Bundle Packer (macOS) # ============================================================================ # Version: 4.0 # # WORKFLOW: # 1. [ONLINE] Build project (./gradlew bootJar) - downloads all deps # 2. [ONLINE] Test run (./gradlew bootRun) - verify app works # 3. [OFFLINE TEST] Verify offline build works # 4. Create bundle with all cached dependencies # # REQUIREMENTS: # - Internet connection (for initial build) # - Project with gradlew # - macOS 10.13+ (High Sierra or later) # ============================================================================ set -e # ============================================================================ # Configuration # ============================================================================ WRAPPER_SEED_PATH="wrapper_jar_seed" OFFLINE_HOME_NAME="_offline_gradle_home" BOOTRUN_TIMEOUT_SECONDS=60 # Color codes RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' CYAN='\033[0;36m' GRAY='\033[0;90m' WHITE='\033[1;37m' NC='\033[0m' # No Color echo "" echo -e "${CYAN}============================================================${NC}" echo -e "${CYAN} Gradle Offline Bundle Packer v4.0 (macOS)${NC}" echo -e "${CYAN}============================================================${NC}" echo "" echo -e "${WHITE} This script will:${NC}" echo -e "${GRAY} 1. Build project with internet (download dependencies)${NC}" echo -e "${GRAY} 2. Test run application (verify it works)${NC}" echo -e "${GRAY} 3. Test offline build (verify cache is complete)${NC}" echo -e "${GRAY} 4. Create offline bundle for air-gapped environment${NC}" echo "" echo -e "${CYAN}============================================================${NC}" echo "" # ============================================================================ # [1/20] Check Current Directory # ============================================================================ echo -e "${YELLOW}==[1/20] Check Current Directory ==${NC}" ROOT="$(pwd)" echo "ROOT_DIR: $ROOT" echo "" # ============================================================================ # [2/20] Check Required Files # ============================================================================ echo -e "${YELLOW}==[2/20] Check Required Files ==${NC}" if [ ! -f "./gradlew" ]; then echo -e "${RED}ERROR: gradlew not found. Run from project root.${NC}" exit 1 fi chmod +x ./gradlew echo -e "${GREEN}[OK] gradlew${NC}" BUILD_FILE="" if [ -f "./build.gradle" ]; then BUILD_FILE="build.gradle" elif [ -f "./build.gradle.kts" ]; then BUILD_FILE="build.gradle.kts" else echo -e "${RED}ERROR: build.gradle(.kts) not found.${NC}" exit 1 fi echo -e "${GREEN}[OK] $BUILD_FILE${NC}" SETTINGS_FILE="" if [ -f "./settings.gradle" ]; then SETTINGS_FILE="settings.gradle" echo -e "${GREEN}[OK] $SETTINGS_FILE${NC}" elif [ -f "./settings.gradle.kts" ]; then SETTINGS_FILE="settings.gradle.kts" echo -e "${GREEN}[OK] $SETTINGS_FILE${NC}" fi echo "" # ============================================================================ # [3/20] Check Gradle Wrapper # ============================================================================ echo -e "${YELLOW}==[3/20] Check Gradle Wrapper ==${NC}" WRAPPER_DIR="$ROOT/gradle/wrapper" WRAPPER_JAR="$WRAPPER_DIR/gradle-wrapper.jar" WRAPPER_PROP="$WRAPPER_DIR/gradle-wrapper.properties" mkdir -p "$WRAPPER_DIR" if [ ! -f "$WRAPPER_PROP" ]; then echo -e "${RED}ERROR: gradle-wrapper.properties not found.${NC}" exit 1 fi if [ ! -f "$WRAPPER_JAR" ]; then SEED_JAR="$ROOT/$WRAPPER_SEED_PATH/gradle-wrapper.jar" if [ -f "$SEED_JAR" ]; then cp "$SEED_JAR" "$WRAPPER_JAR" echo -e "${GREEN}[OK] Wrapper jar injected from seed${NC}" else echo -e "${RED}ERROR: gradle-wrapper.jar missing${NC}" exit 1 fi else echo -e "${GREEN}[OK] gradle-wrapper.jar exists${NC}" fi # Create seed backup SEED_DIR="$ROOT/$WRAPPER_SEED_PATH" if [ ! -d "$SEED_DIR" ]; then mkdir -p "$SEED_DIR" cp "$WRAPPER_JAR" "$SEED_DIR/gradle-wrapper.jar" fi echo "" # ============================================================================ # [4/20] Set GRADLE_USER_HOME (Project Local) # ============================================================================ echo -e "${YELLOW}==[4/20] Set GRADLE_USER_HOME ==${NC}" OFFLINE_HOME="$ROOT/$OFFLINE_HOME_NAME" mkdir -p "$OFFLINE_HOME" export GRADLE_USER_HOME="$OFFLINE_HOME" echo -e "${CYAN}GRADLE_USER_HOME = $GRADLE_USER_HOME${NC}" echo -e "${GRAY}[INFO] All dependencies will be cached in project folder${NC}" echo "" # ============================================================================ # [5/20] Check Internet Connection # ============================================================================ echo -e "${YELLOW}==[5/20] Check Internet Connection ==${NC}" HAS_INTERNET=false TEST_HOSTS=("plugins.gradle.org" "repo.maven.apache.org" "repo1.maven.org") for TEST_HOST in "${TEST_HOSTS[@]}"; do # macOS ping doesn't have -W, use -t instead if ping -c 1 -t 3 "$TEST_HOST" &>/dev/null; then HAS_INTERNET=true echo -e "${GREEN}[OK] Connected to $TEST_HOST${NC}" break fi done if [ "$HAS_INTERNET" = false ]; then # Try DNS resolution as fallback if nslookup google.com &>/dev/null || host google.com &>/dev/null; then HAS_INTERNET=true echo -e "${GREEN}[OK] Internet available (DNS)${NC}" fi fi if [ "$HAS_INTERNET" = false ]; then echo "" echo -e "${RED}============================================================${NC}" echo -e "${RED} ERROR: No Internet Connection!${NC}" echo -e "${RED}============================================================${NC}" echo "" echo -e "${YELLOW}This script requires internet for initial build.${NC}" echo -e "${YELLOW}Please connect to internet and run again.${NC}" echo "" exit 1 fi echo "" # ============================================================================ # [6/20] Initial Gradle Setup # ============================================================================ echo -e "${YELLOW}==[6/20] Initial Gradle Setup ==${NC}" echo -e "${GRAY}[INFO] Downloading Gradle distribution...${NC}" if ./gradlew --version &>/dev/null; then GRADLE_VERSION=$(./gradlew --version 2>&1 | grep "^Gradle" | awk '{print $2}') echo -e "${GREEN}[OK] Gradle $GRADLE_VERSION${NC}" else echo -e "${RED}[ERROR] Gradle setup failed${NC}" exit 1 fi echo "" # ============================================================================ # [7/20] ONLINE BUILD - bootJar (Download All Dependencies) # ============================================================================ echo -e "${YELLOW}==[7/20] ONLINE BUILD - bootJar ==${NC}" echo "" echo -e "${CYAN}============================================================${NC}" echo -e "${CYAN} ONLINE BUILD (with Internet)${NC}" echo -e "${CYAN} Downloading all dependencies to local cache${NC}" echo -e "${CYAN}============================================================${NC}" echo "" BUILD_SUCCESS=false ./gradlew clean bootJar --no-daemon if [ $? -eq 0 ]; then BUILD_SUCCESS=true echo "" echo -e "${GREEN}============================================================${NC}" echo -e "${GREEN} ONLINE BUILD SUCCESS!${NC}" echo -e "${GREEN}============================================================${NC}" echo "" if [ -d "./build/libs" ]; then echo -e "${CYAN}JAR files:${NC}" ls -lh ./build/libs/*.jar 2>/dev/null | awk '{print " " $9 " (" $5 ")"}' fi else echo "" echo -e "${RED}============================================================${NC}" echo -e "${RED} BUILD FAILED!${NC}" echo -e "${RED}============================================================${NC}" echo "" echo -e "${YELLOW}Build failed. Cannot continue.${NC}" exit 1 fi echo "" # ============================================================================ # [8/20] Stop Daemons # ============================================================================ echo -e "${YELLOW}==[8/20] Stop Daemons ==${NC}" ./gradlew --stop &>/dev/null || true sleep 2 echo -e "${GREEN}[OK] Daemons stopped${NC}" echo "" # ============================================================================ # [9/20] ONLINE TEST - bootRun (Verify Application Works) # ============================================================================ echo -e "${YELLOW}==[9/20] ONLINE TEST - bootRun ==${NC}" echo "" echo -e "${CYAN}============================================================${NC}" echo -e "${CYAN} Testing application startup (timeout: ${BOOTRUN_TIMEOUT_SECONDS}s)${NC}" echo -e "${CYAN} Will automatically stop after successful startup${NC}" echo -e "${CYAN}============================================================${NC}" echo "" BOOTRUN_SUCCESS=false # macOS uses gtimeout if available, otherwise perl-based timeout if command -v gtimeout &>/dev/null; then gtimeout ${BOOTRUN_TIMEOUT_SECONDS}s ./gradlew bootRun --no-daemon & else # Fallback: start in background and kill after timeout ./gradlew bootRun --no-daemon & fi BOOTRUN_PID=$! sleep 10 if ps -p $BOOTRUN_PID &>/dev/null; then BOOTRUN_SUCCESS=true echo "" echo -e "${GREEN}[OK] Application started successfully${NC}" kill $BOOTRUN_PID &>/dev/null || true sleep 2 else echo "" echo -e "${YELLOW}[WARN] Application may not have started properly${NC}" fi # Cleanup - macOS process cleanup pkill -f "gradle.*bootRun" &>/dev/null || true sleep 2 echo "" # ============================================================================ # [10/20] Stop Daemons Again # ============================================================================ echo -e "${YELLOW}==[10/20] Stop Daemons Again ==${NC}" ./gradlew --stop &>/dev/null || true sleep 2 echo -e "${GREEN}[OK] Daemons stopped${NC}" echo "" # ============================================================================ # [11/20] OFFLINE BUILD TEST (Verify Cache Completeness) # ============================================================================ echo -e "${YELLOW}==[11/20] OFFLINE BUILD TEST ==${NC}" echo "" echo -e "${CYAN}============================================================${NC}" echo -e "${CYAN} OFFLINE BUILD TEST (--offline flag)${NC}" echo -e "${CYAN} Verifying all dependencies are cached${NC}" echo -e "${CYAN}============================================================${NC}" echo "" OFFLINE_SUCCESS=false ./gradlew clean bootJar --offline --no-daemon if [ $? -eq 0 ]; then OFFLINE_SUCCESS=true echo "" echo -e "${GREEN}============================================================${NC}" echo -e "${GREEN} OFFLINE BUILD TEST PASSED!${NC}" echo -e "${GREEN}============================================================${NC}" echo "" echo -e "${GREEN}[OK] All dependencies are cached${NC}" else echo "" echo -e "${RED}============================================================${NC}" echo -e "${RED} OFFLINE BUILD TEST FAILED!${NC}" echo -e "${RED}============================================================${NC}" echo "" echo -e "${YELLOW}Some dependencies may be missing from cache.${NC}" echo -e "${YELLOW}The bundle may not work in air-gapped environment.${NC}" echo "" read -p "Continue anyway? (y/N): " -n 1 -r echo if [[ ! $REPLY =~ ^[Yy]$ ]]; then exit 1 fi fi echo "" # ============================================================================ # [12/20] Stop Daemons Before Archive # ============================================================================ echo -e "${YELLOW}==[12/20] Stop Daemons Before Archive ==${NC}" ./gradlew --stop &>/dev/null || true sleep 2 echo -e "${GREEN}[OK] Daemons stopped${NC}" echo "" # ============================================================================ # [13/20] Verify settings.gradle for Offline # ============================================================================ echo -e "${YELLOW}==[13/20] Verify settings.gradle ==${NC}" if [ -n "$SETTINGS_FILE" ]; then if grep -q "mavenLocal()" "$SETTINGS_FILE" && grep -q "pluginManagement" "$SETTINGS_FILE"; then echo -e "${GREEN}[OK] settings.gradle configured for offline${NC}" else echo -e "${YELLOW}[WARN] settings.gradle may need offline configuration${NC}" echo -e "${GRAY}[INFO] Consider adding mavenLocal() to pluginManagement and repositories${NC}" fi else echo -e "${GRAY}[INFO] No settings.gradle found${NC}" fi echo "" # ============================================================================ # [14/20] Create Helper Scripts # ============================================================================ echo -e "${YELLOW}==[14/20] Create Helper Scripts ==${NC}" # run_offline_build.sh cat > "$ROOT/run_offline_build.sh" << 'EOF' #!/bin/bash # run_offline_build.sh - Build JAR offline export GRADLE_USER_HOME="$(pwd)/_offline_gradle_home" echo "GRADLE_USER_HOME = $GRADLE_USER_HOME" echo "" ./gradlew --offline bootJar --no-daemon if [ $? -eq 0 ]; then echo "" echo "BUILD SUCCESS!" echo "" echo "JAR files:" ls -lh ./build/libs/*.jar 2>/dev/null | awk '{print " " $9}' else echo "BUILD FAILED" fi EOF chmod +x "$ROOT/run_offline_build.sh" echo -e "${GREEN}[OK] run_offline_build.sh${NC}" # run_offline_bootrun.sh cat > "$ROOT/run_offline_bootrun.sh" << 'EOF' #!/bin/bash # run_offline_bootrun.sh - Run application offline export GRADLE_USER_HOME="$(pwd)/_offline_gradle_home" echo "GRADLE_USER_HOME = $GRADLE_USER_HOME" echo "" echo "Starting application (Ctrl+C to stop)..." echo "" ./gradlew --offline bootRun --no-daemon EOF chmod +x "$ROOT/run_offline_bootrun.sh" echo -e "${GREEN}[OK] run_offline_bootrun.sh${NC}" echo "" # ============================================================================ # [15/20] Final Daemon Cleanup # ============================================================================ echo -e "${YELLOW}==[15/20] Final Daemon Cleanup ==${NC}" ./gradlew --stop &>/dev/null || true sleep 2 echo -e "${GREEN}[OK] Daemons stopped${NC}" echo "" # ============================================================================ # [16/20] Clean Lock Files # ============================================================================ echo -e "${YELLOW}==[16/20] Clean Lock Files ==${NC}" DAEMON_DIR="$OFFLINE_HOME/daemon" if [ -d "$DAEMON_DIR" ]; then rm -rf "$DAEMON_DIR" 2>/dev/null || true fi find "$OFFLINE_HOME" -type f \( -name "*.lock" -o -name "*.log" -o -name "*.tmp" \) -delete 2>/dev/null || true echo -e "${GREEN}[OK] Lock files cleaned${NC}" echo "" # ============================================================================ # [17/20] Calculate Cache Size # ============================================================================ echo -e "${YELLOW}==[17/20] Cache Summary ==${NC}" CACHES_DIR="$OFFLINE_HOME/caches" WRAPPER_DISTS="$OFFLINE_HOME/wrapper/dists" TOTAL_SIZE=0 if [ -d "$CACHES_DIR" ]; then # macOS uses different options for du if du -k "$CACHES_DIR" &>/dev/null; then SIZE=$(du -sk "$CACHES_DIR" 2>/dev/null | cut -f1) SIZE=$((SIZE * 1024)) # Convert to bytes else SIZE=0 fi TOTAL_SIZE=$((TOTAL_SIZE + SIZE)) SIZE_MB=$(awk "BEGIN {printf \"%.2f\", $SIZE / 1048576}") echo -e "${CYAN}[INFO] Dependencies: ${SIZE_MB} MB${NC}" fi if [ -d "$WRAPPER_DISTS" ]; then if du -k "$WRAPPER_DISTS" &>/dev/null; then SIZE=$(du -sk "$WRAPPER_DISTS" 2>/dev/null | cut -f1) SIZE=$((SIZE * 1024)) else SIZE=0 fi TOTAL_SIZE=$((TOTAL_SIZE + SIZE)) SIZE_MB=$(awk "BEGIN {printf \"%.2f\", $SIZE / 1048576}") echo -e "${CYAN}[INFO] Gradle dist: ${SIZE_MB} MB${NC}" fi TOTAL_MB=$(awk "BEGIN {printf \"%.2f\", $TOTAL_SIZE / 1048576}") echo -e "${CYAN}[INFO] Total cache: ${TOTAL_MB} MB${NC}" echo "" # ============================================================================ # [18/20] Create Archive # ============================================================================ echo -e "${YELLOW}==[18/20] Create Archive ==${NC}" BASE_NAME=$(basename "$ROOT") TIMESTAMP=$(date +"%Y%m%d_%H%M%S") PARENT=$(dirname "$ROOT") ARCHIVE_PATH="${PARENT}/${BASE_NAME}_offline_bundle_${TIMESTAMP}.tar.gz" echo "Archive: $ARCHIVE_PATH" echo -e "${GRAY}[INFO] Creating archive (this may take several minutes)...${NC}" # macOS tar with BSD options tar -czf "$ARCHIVE_PATH" \ --exclude=".git" \ --exclude=".idea" \ --exclude=".DS_Store" \ --exclude="*.log" \ --exclude="*.lock" \ --exclude="_offline_gradle_home/daemon" \ --exclude="_offline_gradle_home/native" \ --exclude="_offline_gradle_home/jdks" \ --exclude="build" \ --exclude="out" \ --exclude=".gradle" \ -C "$ROOT" . if [ $? -ne 0 ]; then echo -e "${RED}ERROR: tar failed${NC}" exit 1 fi # macOS stat command ARCHIVE_SIZE=$(stat -f%z "$ARCHIVE_PATH" 2>/dev/null) ARCHIVE_SIZE_MB=$(awk "BEGIN {printf \"%.2f\", $ARCHIVE_SIZE / 1048576}") echo -e "${GREEN}[OK] Archive created: ${ARCHIVE_SIZE_MB} MB${NC}" echo "" # ============================================================================ # [19/20] Verify Archive # ============================================================================ echo -e "${YELLOW}==[19/20] Verify Archive ==${NC}" CHECKS=( "gradle/wrapper/gradle-wrapper.jar" "gradlew" "_offline_gradle_home/caches" "run_offline_build.sh" ) for CHECK in "${CHECKS[@]}"; do if tar -tzf "$ARCHIVE_PATH" | grep -q "$CHECK"; then echo -e " ${GREEN}[OK] $CHECK${NC}" else echo -e " ${YELLOW}[WARN] $CHECK${NC}" fi done echo "" # ============================================================================ # [20/20] Complete # ============================================================================ echo -e "${GREEN}============================================================${NC}" echo -e "${GREEN} BUNDLE CREATION COMPLETE!${NC}" echo -e "${GREEN}============================================================${NC}" echo "" echo -e "${CYAN}Archive: $ARCHIVE_PATH${NC}" echo -e "${CYAN}Size: ${ARCHIVE_SIZE_MB} MB${NC}" echo "" echo -e "${CYAN}============================================================${NC}" echo -e "${CYAN} Test Results${NC}" echo -e "${CYAN}============================================================${NC}" if [ "$BUILD_SUCCESS" = true ]; then echo -e " Online build (bootJar): ${GREEN}PASSED${NC}" else echo -e " Online build (bootJar): ${RED}FAILED${NC}" fi if [ "$BOOTRUN_SUCCESS" = true ]; then echo -e " Online test (bootRun): ${GREEN}PASSED${NC}" else echo -e " Online test (bootRun): ${YELLOW}SKIPPED${NC}" fi if [ "$OFFLINE_SUCCESS" = true ]; then echo -e " Offline build test: ${GREEN}PASSED${NC}" else echo -e " Offline build test: ${RED}FAILED${NC}" fi echo "" echo -e "${YELLOW}============================================================${NC}" echo -e "${YELLOW} Usage in Air-gapped Environment${NC}" echo -e "${YELLOW}============================================================${NC}" echo "" echo -e "${WHITE}Option 1: Use unpack script${NC}" echo -e "${GRAY} ./unpack_and_offline_build_airgap.sh${NC}" echo "" echo -e "${WHITE}Option 2: Manual extraction${NC}" echo -e "${GRAY} tar -xzf .tar.gz${NC}" echo -e "${GRAY} cd ${NC}" echo -e "${GRAY} ./run_offline_build.sh${NC}" echo "" echo -e "${WHITE}Option 3: Direct commands${NC}" echo -e "${GRAY} export GRADLE_USER_HOME=\"./_offline_gradle_home\"${NC}" echo -e "${GRAY} ./gradlew --offline bootJar --no-daemon${NC}" echo ""