# pack_offline_bundle_airgap.ps1 # ============================================================================ # Gradle Offline Bundle Packer # ============================================================================ # Version: 4.0 # # WORKFLOW: # 1. [ONLINE] Build project (./gradlew.bat bootJar) - downloads all deps # 2. [ONLINE] Test run (./gradlew.bat 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.bat # ============================================================================ $ErrorActionPreference = "Stop" $ProgressPreference = "SilentlyContinue" # ============================================================================ # Configuration # ============================================================================ $WRAPPER_SEED_PATH = "wrapper_jar_seed" $OFFLINE_HOME_NAME = "_offline_gradle_home" $BOOTRUN_TIMEOUT_SECONDS = 60 Write-Host "" Write-Host "============================================================" -ForegroundColor Cyan Write-Host " Gradle Offline Bundle Packer v4.0" -ForegroundColor Cyan Write-Host "============================================================" -ForegroundColor Cyan Write-Host "" Write-Host " This script will:" -ForegroundColor White Write-Host " 1. Build project with internet (download dependencies)" -ForegroundColor Gray Write-Host " 2. Test run application (verify it works)" -ForegroundColor Gray Write-Host " 3. Test offline build (verify cache is complete)" -ForegroundColor Gray Write-Host " 4. Create offline bundle for air-gapped environment" -ForegroundColor Gray Write-Host "" Write-Host "============================================================" -ForegroundColor Cyan Write-Host "" # ============================================================================ # [1/20] Check Current Directory # ============================================================================ Write-Host "==[1/20] Check Current Directory ==" -ForegroundColor Yellow $Root = (Get-Location).Path Write-Host ("ROOT_DIR: " + $Root) Write-Host "" # ============================================================================ # [2/20] Check Required Files # ============================================================================ Write-Host "==[2/20] Check Required Files ==" -ForegroundColor Yellow if (!(Test-Path -LiteralPath ".\gradlew.bat")) { throw "ERROR: gradlew.bat not found. Run from project root." } Write-Host "[OK] gradlew.bat" -ForegroundColor Green $buildFile = $null if (Test-Path -LiteralPath ".\build.gradle") { $buildFile = "build.gradle" } elseif (Test-Path -LiteralPath ".\build.gradle.kts") { $buildFile = "build.gradle.kts" } else { throw "ERROR: build.gradle(.kts) not found." } Write-Host ("[OK] " + $buildFile) -ForegroundColor Green $settingsFile = $null if (Test-Path -LiteralPath ".\settings.gradle") { $settingsFile = "settings.gradle" } elseif (Test-Path -LiteralPath ".\settings.gradle.kts") { $settingsFile = "settings.gradle.kts" } if ($settingsFile) { Write-Host ("[OK] " + $settingsFile) -ForegroundColor Green } Write-Host "" # ============================================================================ # [3/20] Check Gradle Wrapper # ============================================================================ Write-Host "==[3/20] Check Gradle Wrapper ==" -ForegroundColor Yellow $WrapperDir = Join-Path $Root "gradle\wrapper" $WrapperJar = Join-Path $WrapperDir "gradle-wrapper.jar" $WrapperProp = Join-Path $WrapperDir "gradle-wrapper.properties" New-Item -ItemType Directory -Force -Path $WrapperDir | Out-Null if (!(Test-Path -LiteralPath $WrapperProp)) { throw "ERROR: gradle-wrapper.properties not found." } if (!(Test-Path -LiteralPath $WrapperJar)) { $SeedJar = Join-Path $Root "$WRAPPER_SEED_PATH\gradle-wrapper.jar" if (Test-Path -LiteralPath $SeedJar) { Copy-Item -Force -LiteralPath $SeedJar -Destination $WrapperJar Write-Host "[OK] Wrapper jar injected from seed" -ForegroundColor Green } else { throw "ERROR: gradle-wrapper.jar missing" } } else { Write-Host "[OK] gradle-wrapper.jar exists" -ForegroundColor Green } # Create seed backup $SeedDir = Join-Path $Root $WRAPPER_SEED_PATH if (!(Test-Path -LiteralPath $SeedDir)) { New-Item -ItemType Directory -Force -Path $SeedDir | Out-Null Copy-Item -Force -LiteralPath $WrapperJar -Destination (Join-Path $SeedDir "gradle-wrapper.jar") } Write-Host "" # ============================================================================ # [4/20] Set GRADLE_USER_HOME (Project Local) # ============================================================================ Write-Host "==[4/20] Set GRADLE_USER_HOME ==" -ForegroundColor Yellow $OfflineHome = Join-Path $Root $OFFLINE_HOME_NAME New-Item -ItemType Directory -Force -Path $OfflineHome | Out-Null $env:GRADLE_USER_HOME = $OfflineHome Write-Host ("GRADLE_USER_HOME = " + $env:GRADLE_USER_HOME) -ForegroundColor Cyan Write-Host "[INFO] All dependencies will be cached in project folder" -ForegroundColor Gray Write-Host "" # ============================================================================ # [5/20] Check Internet Connection # ============================================================================ Write-Host "==[5/20] Check Internet Connection ==" -ForegroundColor Yellow $hasInternet = $false $testHosts = @("plugins.gradle.org", "repo.maven.apache.org", "repo1.maven.org") foreach ($testHost in $testHosts) { try { $result = Test-Connection -ComputerName $testHost -Count 1 -Quiet -TimeoutSeconds 3 -ErrorAction SilentlyContinue if ($result) { $hasInternet = $true Write-Host ("[OK] Connected to " + $testHost) -ForegroundColor Green break } } catch { } } if (-not $hasInternet) { # Try DNS resolution as fallback try { [System.Net.Dns]::GetHostAddresses("google.com") | Out-Null $hasInternet = $true Write-Host "[OK] Internet available (DNS)" -ForegroundColor Green } catch { } } if (-not $hasInternet) { Write-Host "" Write-Host "============================================================" -ForegroundColor Red Write-Host " ERROR: No Internet Connection!" -ForegroundColor Red Write-Host "============================================================" -ForegroundColor Red Write-Host "" Write-Host "This script requires internet for initial build." -ForegroundColor Yellow Write-Host "Please connect to internet and run again." -ForegroundColor Yellow Write-Host "" throw "No internet connection" } Write-Host "" # ============================================================================ # [6/20] Initial Gradle Setup # ============================================================================ Write-Host "==[6/20] Initial Gradle Setup ==" -ForegroundColor Yellow Write-Host "[INFO] Downloading Gradle distribution..." -ForegroundColor Gray try { $output = cmd /c ".\gradlew.bat --version 2>&1" if ($LASTEXITCODE -eq 0) { $gradleVersion = $output | Select-String "Gradle\s+(\d+\.\d+)" | ForEach-Object { $_.Matches[0].Groups[1].Value } Write-Host ("[OK] Gradle " + $gradleVersion) -ForegroundColor Green } else { throw "Gradle setup failed" } } catch { Write-Host "[ERROR] Gradle setup failed" -ForegroundColor Red throw $_ } Write-Host "" # ============================================================================ # [7/20] ONLINE BUILD - bootJar (Download All Dependencies) # ============================================================================ Write-Host "==[7/20] ONLINE BUILD - bootJar ==" -ForegroundColor Yellow Write-Host "" Write-Host "============================================================" -ForegroundColor Cyan Write-Host " Building project (downloading all dependencies)" -ForegroundColor Cyan Write-Host " This may take several minutes on first run..." -ForegroundColor Cyan Write-Host "============================================================" -ForegroundColor Cyan Write-Host "" $buildSuccess = $false try { cmd /c ".\gradlew.bat clean bootJar --no-daemon" if ($LASTEXITCODE -eq 0) { $buildSuccess = $true } } catch { } if (-not $buildSuccess) { Write-Host "" Write-Host "============================================================" -ForegroundColor Red Write-Host " BUILD FAILED!" -ForegroundColor Red Write-Host "============================================================" -ForegroundColor Red Write-Host "" Write-Host "Please fix build errors and run this script again." -ForegroundColor Yellow throw "Build failed" } Write-Host "" Write-Host "[OK] bootJar build SUCCESS" -ForegroundColor Green Write-Host "" # ============================================================================ # [8/20] ONLINE TEST - bootRun (Verify Application Works) # ============================================================================ Write-Host "==[8/20] ONLINE TEST - bootRun ==" -ForegroundColor Yellow Write-Host "" Write-Host "============================================================" -ForegroundColor Cyan Write-Host " Starting application to verify it works..." -ForegroundColor Cyan Write-Host " Will run for $BOOTRUN_TIMEOUT_SECONDS seconds then stop" -ForegroundColor Cyan Write-Host "============================================================" -ForegroundColor Cyan Write-Host "" $bootRunSuccess = $false try { # Start bootRun as background job $job = Start-Job -ScriptBlock { param($projectDir, $gradleHome) Set-Location $projectDir $env:GRADLE_USER_HOME = $gradleHome cmd /c ".\gradlew.bat bootRun --no-daemon 2>&1" } -ArgumentList $Root, $OfflineHome Write-Host "[INFO] Application starting..." -ForegroundColor Gray # Wait for startup (check for typical Spring Boot messages) $startTime = Get-Date $startupDetected = $false while (((Get-Date) - $startTime).TotalSeconds -lt $BOOTRUN_TIMEOUT_SECONDS) { Start-Sleep -Seconds 3 # Check if job has output $jobOutput = Receive-Job -Job $job -Keep -ErrorAction SilentlyContinue if ($jobOutput) { $outputText = $jobOutput -join "`n" # Check for Spring Boot startup success indicators if ($outputText -match "Started .+ in .+ seconds" -or $outputText -match "Tomcat started on port" -or $outputText -match "Netty started on port" -or $outputText -match "Application .+ started") { $startupDetected = $true Write-Host "[OK] Application started successfully!" -ForegroundColor Green break } # Check for errors if ($outputText -match "APPLICATION FAILED TO START" -or $outputText -match "Error starting ApplicationContext") { Write-Host "[ERROR] Application failed to start" -ForegroundColor Red break } } $elapsed = [math]::Round(((Get-Date) - $startTime).TotalSeconds) Write-Host ("[INFO] Waiting... " + $elapsed + "s") -ForegroundColor Gray } # Stop the job Write-Host "[INFO] Stopping application..." -ForegroundColor Gray Stop-Job -Job $job -ErrorAction SilentlyContinue Remove-Job -Job $job -Force -ErrorAction SilentlyContinue # Also stop any remaining Gradle processes cmd /c ".\gradlew.bat --stop 2>&1" | Out-Null if ($startupDetected) { $bootRunSuccess = $true } } catch { Write-Host "[WARN] bootRun test encountered error: $_" -ForegroundColor DarkYellow } # Cleanup any remaining processes try { Get-Process -Name "java" -ErrorAction SilentlyContinue | Where-Object { $_.CommandLine -match "bootRun|spring" } | Stop-Process -Force -ErrorAction SilentlyContinue } catch { } Write-Host "" if ($bootRunSuccess) { Write-Host "[OK] bootRun test PASSED" -ForegroundColor Green } else { Write-Host "[WARN] bootRun test could not verify startup" -ForegroundColor DarkYellow Write-Host "[INFO] Continuing anyway - bootJar succeeded" -ForegroundColor Gray } Write-Host "" # ============================================================================ # [9/20] Stop All Gradle Daemons # ============================================================================ Write-Host "==[9/20] Stop Gradle Daemons ==" -ForegroundColor Yellow cmd /c ".\gradlew.bat --stop 2>&1" | Out-Null Start-Sleep -Seconds 3 Write-Host "[OK] Daemons stopped" -ForegroundColor Green Write-Host "" # ============================================================================ # [10/20] OFFLINE TEST - Verify Offline Build Works # ============================================================================ Write-Host "==[10/20] OFFLINE TEST - Verify Cache ==" -ForegroundColor Yellow Write-Host "" Write-Host "============================================================" -ForegroundColor Cyan Write-Host " Testing offline build (--offline flag)" -ForegroundColor Cyan Write-Host " This verifies all dependencies are cached" -ForegroundColor Cyan Write-Host "============================================================" -ForegroundColor Cyan Write-Host "" $offlineSuccess = $false try { cmd /c ".\gradlew.bat --offline clean bootJar --no-daemon" if ($LASTEXITCODE -eq 0) { $offlineSuccess = $true } } catch { } Write-Host "" if ($offlineSuccess) { Write-Host "[OK] Offline build SUCCESS - Cache is complete!" -ForegroundColor Green } else { Write-Host "============================================================" -ForegroundColor Red Write-Host " OFFLINE BUILD FAILED!" -ForegroundColor Red Write-Host "============================================================" -ForegroundColor Red Write-Host "" Write-Host "Some dependencies may not be cached properly." -ForegroundColor Yellow Write-Host "" $continue = Read-Host "Continue creating bundle anyway? (y/N)" if ($continue -ne "y" -and $continue -ne "Y") { throw "Offline verification failed" } } Write-Host "" # ============================================================================ # [11/20] Backup Original settings.gradle # ============================================================================ Write-Host "==[11/20] Backup settings.gradle ==" -ForegroundColor Yellow if ($settingsFile) { $settingsPath = Join-Path $Root $settingsFile $settingsBackup = Join-Path $Root "${settingsFile}.original.bak" if (!(Test-Path -LiteralPath $settingsBackup)) { Copy-Item -Force -LiteralPath $settingsPath -Destination $settingsBackup Write-Host "[OK] Backup: ${settingsFile}.original.bak" -ForegroundColor Green } else { Write-Host "[OK] Backup exists" -ForegroundColor Green } } Write-Host "" # ============================================================================ # [12/20] Modify settings.gradle for Offline # ============================================================================ Write-Host "==[12/20] Configure settings.gradle for Offline ==" -ForegroundColor Yellow if ($settingsFile) { $settingsPath = Join-Path $Root $settingsFile $content = Get-Content -LiteralPath $settingsPath -Raw # --- Always strip BOM if present (prevents Groovy 'Unexpected character: ') $hadBom = $false if ($content.Length -gt 0 -and $content[0] -eq [char]0xFEFF) { $hadBom = $true $content = $content -replace "^\uFEFF", "" } $isOfflineConfigured = ($content -match "mavenLocal\(\)") -and ($content -match "pluginManagement[\s\S]*repositories") if ($isOfflineConfigured) { # Even if already configured, re-save without BOM if needed if ($hadBom) { [System.IO.File]::WriteAllText( $settingsPath, $content, (New-Object System.Text.UTF8Encoding($false)) ) Write-Host "[FIX] settings.gradle BOM removed (saved as UTF-8 without BOM)" -ForegroundColor Green } else { Write-Host "[OK] Already configured for offline" -ForegroundColor Green } } else { $newHeader = @" // ============================================================================ // OFFLINE BUILD CONFIGURATION (Auto-generated by pack script) // Original backup: ${settingsFile}.original.bak // ============================================================================ pluginManagement { repositories { mavenLocal() gradlePluginPortal() } } "@ # Remove existing pluginManagement $cleaned = $content -replace '(?s)pluginManagement\s*\{[^{}]*(\{[^{}]*\}[^{}]*)*\}\s*', '' $final = $newHeader + $cleaned.Trim() # --- Write as UTF-8 WITHOUT BOM [System.IO.File]::WriteAllText( $settingsPath, $final, (New-Object System.Text.UTF8Encoding($false)) ) Write-Host "[OK] settings.gradle updated for offline (UTF-8 without BOM)" -ForegroundColor Green } } Write-Host "" # ============================================================================ # [13/20] Copy Maven Local Repository # ============================================================================ Write-Host "==[13/20] Copy Maven Local (.m2) ==" -ForegroundColor Yellow $m2Repo = Join-Path $env:USERPROFILE ".m2\repository" $localM2 = Join-Path $OfflineHome "m2_repository" if (Test-Path -LiteralPath $m2Repo) { $m2Size = (Get-ChildItem -Path $m2Repo -Recurse -File -ErrorAction SilentlyContinue | Measure-Object -Property Length -Sum).Sum if ($m2Size -gt 1MB) { $m2SizeMB = [math]::Round($m2Size / 1MB, 2) Write-Host ("[INFO] .m2 size: " + $m2SizeMB + " MB") -ForegroundColor Cyan # Copy important plugin directories $pluginDirs = @( "org\springframework\boot", "io\spring", "com\diffplug" ) foreach ($dir in $pluginDirs) { $src = Join-Path $m2Repo $dir if (Test-Path -LiteralPath $src) { $dst = Join-Path $localM2 $dir New-Item -ItemType Directory -Force -Path (Split-Path $dst -Parent) | Out-Null if (!(Test-Path -LiteralPath $dst)) { Copy-Item -Recurse -Force -LiteralPath $src -Destination $dst -ErrorAction SilentlyContinue Write-Host ("[OK] Copied " + $dir) -ForegroundColor Green } } } } } else { Write-Host "[INFO] No .m2 repository found" -ForegroundColor Gray } Write-Host "" # ============================================================================ # [14/20] Create Helper Scripts # ============================================================================ Write-Host "==[14/20] Create Helper Scripts ==" -ForegroundColor Yellow # run_offline_build.ps1 $runScript = @' # run_offline_build.ps1 - Quick offline build script $env:GRADLE_USER_HOME = Join-Path (Get-Location).Path "_offline_gradle_home" Write-Host "GRADLE_USER_HOME = $env:GRADLE_USER_HOME" -ForegroundColor Cyan Write-Host "" .\gradlew.bat --offline bootJar --no-daemon if ($LASTEXITCODE -eq 0) { Write-Host "" Write-Host "BUILD SUCCESS!" -ForegroundColor Green Write-Host "" Write-Host "JAR files:" -ForegroundColor Cyan Get-ChildItem .\build\libs\*.jar | ForEach-Object { Write-Host (" " + $_.Name) } } else { Write-Host "BUILD FAILED" -ForegroundColor Red } '@ [System.IO.File]::WriteAllText((Join-Path $Root "run_offline_build.ps1"), $runScript, (New-Object System.Text.UTF8Encoding($false))) Write-Host "[OK] run_offline_build.ps1" -ForegroundColor Green # run_offline_bootrun.ps1 $bootRunScript = @' # run_offline_bootrun.ps1 - Run application offline $env:GRADLE_USER_HOME = Join-Path (Get-Location).Path "_offline_gradle_home" Write-Host "GRADLE_USER_HOME = $env:GRADLE_USER_HOME" -ForegroundColor Cyan Write-Host "" Write-Host "Starting application (Ctrl+C to stop)..." -ForegroundColor Yellow Write-Host "" .\gradlew.bat --offline bootRun --no-daemon '@ [System.IO.File]::WriteAllText((Join-Path $Root "run_offline_bootrun.ps1"), $bootRunScript, (New-Object System.Text.UTF8Encoding($false))) Write-Host "[OK] run_offline_bootrun.ps1" -ForegroundColor Green Write-Host "" # ============================================================================ # [15/20] Stop Daemons Again # ============================================================================ Write-Host "==[15/20] Final Daemon Cleanup ==" -ForegroundColor Yellow cmd /c ".\gradlew.bat --stop 2>&1" | Out-Null Start-Sleep -Seconds 2 Write-Host "[OK] Daemons stopped" -ForegroundColor Green Write-Host "" # ============================================================================ # [16/20] Clean Lock Files # ============================================================================ Write-Host "==[16/20] Clean Lock Files ==" -ForegroundColor Yellow try { $DaemonDir = Join-Path $OfflineHome "daemon" if (Test-Path -LiteralPath $DaemonDir) { Remove-Item -Recurse -Force -LiteralPath $DaemonDir -ErrorAction SilentlyContinue } Get-ChildItem -Path $OfflineHome -Recurse -Include "*.lock","*.log","*.tmp" -File -ErrorAction SilentlyContinue | ForEach-Object { Remove-Item -Force -LiteralPath $_.FullName -ErrorAction SilentlyContinue } Write-Host "[OK] Lock files cleaned" -ForegroundColor Green } catch { Write-Host "[WARN] Some files could not be cleaned" -ForegroundColor DarkYellow } Write-Host "" # ============================================================================ # [17/20] Calculate Cache Size # ============================================================================ Write-Host "==[17/20] Cache Summary ==" -ForegroundColor Yellow $CachesDir = Join-Path $OfflineHome "caches" $WrapperDists = Join-Path $OfflineHome "wrapper\dists" $totalSize = 0 if (Test-Path -LiteralPath $CachesDir) { $size = (Get-ChildItem -Path $CachesDir -Recurse -File -ErrorAction SilentlyContinue | Measure-Object -Property Length -Sum).Sum $totalSize += $size Write-Host ("[INFO] Dependencies: " + [math]::Round($size/1MB, 2) + " MB") -ForegroundColor Cyan } if (Test-Path -LiteralPath $WrapperDists) { $size = (Get-ChildItem -Path $WrapperDists -Recurse -File -ErrorAction SilentlyContinue | Measure-Object -Property Length -Sum).Sum $totalSize += $size Write-Host ("[INFO] Gradle dist: " + [math]::Round($size/1MB, 2) + " MB") -ForegroundColor Cyan } Write-Host ("[INFO] Total cache: " + [math]::Round($totalSize/1MB, 2) + " MB") -ForegroundColor Cyan Write-Host "" # ============================================================================ # [18/20] Create Archive # ============================================================================ Write-Host "==[18/20] Create Archive ==" -ForegroundColor Yellow $BaseName = Split-Path $Root -Leaf $Ts = Get-Date -Format "yyyyMMdd_HHmmss" $Parent = Split-Path $Root -Parent $ArchivePath = Join-Path $Parent "${BaseName}_offline_bundle_${Ts}.tar.gz" Write-Host ("Archive: " + $ArchivePath) $tar = Get-Command tar.exe -ErrorAction SilentlyContinue if (-not $tar) { throw "ERROR: tar.exe not found" } Write-Host "[INFO] Creating archive (this may take several minutes)..." -ForegroundColor Gray & tar.exe -czf $ArchivePath ` --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 ($LASTEXITCODE -ne 0) { throw "ERROR: tar failed" } $archiveSize = (Get-Item -LiteralPath $ArchivePath).Length $archiveSizeMB = [math]::Round($archiveSize / 1MB, 2) Write-Host ("[OK] Archive created: " + $archiveSizeMB + " MB") -ForegroundColor Green Write-Host "" # ============================================================================ # [19/20] Verify Archive # ============================================================================ Write-Host "==[19/20] Verify Archive ==" -ForegroundColor Yellow $listOutput = & tar.exe -tzf $ArchivePath 2>&1 $checks = @( "gradle/wrapper/gradle-wrapper.jar", "gradlew.bat", "_offline_gradle_home/caches", "run_offline_build.ps1" ) foreach ($check in $checks) { $found = $listOutput | Select-String -SimpleMatch $check if ($found) { Write-Host (" [OK] " + $check) -ForegroundColor Green } else { Write-Host (" [WARN] " + $check) -ForegroundColor DarkYellow } } Write-Host "" # ============================================================================ # [20/20] Complete # ============================================================================ Write-Host "============================================================" -ForegroundColor Green Write-Host " BUNDLE CREATION COMPLETE!" -ForegroundColor Green Write-Host "============================================================" -ForegroundColor Green Write-Host "" Write-Host ("Archive: " + $ArchivePath) -ForegroundColor Cyan Write-Host ("Size: " + $archiveSizeMB + " MB") -ForegroundColor Cyan Write-Host "" Write-Host "============================================================" -ForegroundColor Cyan Write-Host " Test Results" -ForegroundColor Cyan Write-Host "============================================================" -ForegroundColor Cyan Write-Host (" Online build (bootJar): " + $(if($buildSuccess){"PASSED"}else{"FAILED"})) -ForegroundColor $(if($buildSuccess){"Green"}else{"Red"}) Write-Host (" Online test (bootRun): " + $(if($bootRunSuccess){"PASSED"}else{"SKIPPED"})) -ForegroundColor $(if($bootRunSuccess){"Green"}else{"Yellow"}) Write-Host (" Offline build test: " + $(if($offlineSuccess){"PASSED"}else{"FAILED"})) -ForegroundColor $(if($offlineSuccess){"Green"}else{"Red"}) Write-Host "" Write-Host "============================================================" -ForegroundColor Yellow Write-Host " Usage in Air-gapped Environment" -ForegroundColor Yellow Write-Host "============================================================" -ForegroundColor Yellow Write-Host "" Write-Host "Option 1: Use unpack script" -ForegroundColor White Write-Host " .\unpack_and_offline_build_airgap.ps1" -ForegroundColor Gray Write-Host "" Write-Host "Option 2: Manual extraction" -ForegroundColor White Write-Host " tar -xzf .tar.gz" -ForegroundColor Gray Write-Host " cd " -ForegroundColor Gray Write-Host " .\run_offline_build.ps1" -ForegroundColor Gray Write-Host "" Write-Host "Option 3: Direct commands" -ForegroundColor White Write-Host ' $env:GRADLE_USER_HOME = ".\\_offline_gradle_home"' -ForegroundColor Gray Write-Host " .\gradlew.bat --offline bootJar --no-daemon" -ForegroundColor Gray Write-Host ""