From 8e2b7f09a3984085a93fd0ff2991741caec92cc1 Mon Sep 17 00:00:00 2001 From: Max Rydahl Andersen Date: Sat, 23 May 2026 08:43:00 +0200 Subject: [PATCH 1/4] fix: use manual retry loop in bash download() with user feedback Replace curl --retry / wget --tries with a manual retry loop that provides clear feedback on each failed attempt. This matches the PowerShell Invoke-Download behavior already on main: - Shows 'Download attempt N/M failed, retrying in Xs...' on stderr - Uses exponential backoff (1, 2, 4, 8s) by default - Respects JBANG_DOWNLOAD_RETRY_DELAY for fixed delay override The built-in curl/wget retry flags were problematic: - curl --retry-delay 0 means exponential backoff, but wget --waitretry=0 means no delay at all - Neither tool provides user-visible progress on retries --- src/main/scripts/jbang | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/src/main/scripts/jbang b/src/main/scripts/jbang index d8effa46d..b1f2f34b4 100755 --- a/src/main/scripts/jbang +++ b/src/main/scripts/jbang @@ -25,20 +25,32 @@ script_dir() { } download() { - if [ -x "$(command -v curl)" ]; then - curl -sLf --retry "$downloadRetry" --retry-delay "$downloadRetryDelay" -o "$2" "$1" - retval=$? - elif [ -x "$(command -v wget)" ]; then + local attempt=0 + local maxAttempts=$((downloadRetry + 1)) + while true; do + attempt=$((attempt + 1)) + if [ -x "$(command -v curl)" ]; then + curl -sLf -o "$2" "$1" + retval=$? + elif [ -x "$(command -v wget)" ]; then + wget -q -O "$2" "$1" + retval=$? + else + echo "Error: curl or wget not found, please make sure one of them is installed" 1>&2 + exit 1 + fi + if [ $retval -eq 0 ] || [ $attempt -ge $maxAttempts ]; then + break + fi if [ "$downloadRetryDelay" -gt 0 ] 2>/dev/null; then - wget -q --tries="$downloadRetry" --waitretry="$downloadRetryDelay" -O "$2" "$1" + sleepSeconds=$downloadRetryDelay else - wget -q --tries="$downloadRetry" -O "$2" "$1" + # Exponential backoff: 1, 2, 4, 8, ... + sleepSeconds=$((1 << (attempt - 1))) fi - retval=$? - else - echo "Error: curl or wget not found, please make sure one of them is installed" 1>&2 - exit 1 - fi + echo "Download attempt $attempt/$maxAttempts failed, retrying in $sleepSeconds second(s)..." 1>&2 + sleep $sleepSeconds + done } unpack() { From 775f46027696107590dc18553c9f7737617a5641 Mon Sep 17 00:00:00 2001 From: Max Rydahl Andersen Date: Sat, 23 May 2026 08:49:14 +0200 Subject: [PATCH 2/4] fix: print download URL in startup script feedback Show the resolved URL in the download message so users can see exactly where JBang is being downloaded from. Helps with debugging mirror/proxy issues and custom JBANG_DOWNLOAD_URL overrides. Before: Downloading JBang 0.138.0... After: Downloading JBang 0.138.0 from https://github.com/.../jbang.tar... --- src/main/scripts/jbang | 2 +- src/main/scripts/jbang.ps1 | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/scripts/jbang b/src/main/scripts/jbang index b1f2f34b4..83545d71f 100755 --- a/src/main/scripts/jbang +++ b/src/main/scripts/jbang @@ -270,7 +270,6 @@ if [[ -z "$binaryPath" ]]; then fi if [[ -z "$binaryPath" && -z "$jarPath" ]]; then if [[ ! -f "$JBDIR/bin/jbang.jar" || ! -f "$JBDIR/bin/jbang" ]]; then - echo "Downloading JBang $JBANG_DOWNLOAD_VERSION..." 1>&2 mkdir -p "$TDIR/urls" if [ -n "$JBANG_DOWNLOAD_URL" ]; then jburl="$JBANG_DOWNLOAD_URL"; @@ -279,6 +278,7 @@ if [[ -z "$binaryPath" && -z "$jarPath" ]]; then else jburl="https://github.com/jbangdev/jbang/releases/download/v$JBANG_DOWNLOAD_VERSION/jbang.tar"; fi + echo "Downloading JBang ${JBANG_DOWNLOAD_VERSION:-latest} from $jburl..." 1>&2 download "$jburl" "$TDIR/urls/jbang.tar" if [ $retval -ne 0 ]; then echo "Error downloading JBang from $jburl" 1>&2; exit $retval; fi echo "Installing JBang..." 1>&2 diff --git a/src/main/scripts/jbang.ps1 b/src/main/scripts/jbang.ps1 index 2cc5a2142..bfd9c4ba9 100644 --- a/src/main/scripts/jbang.ps1 +++ b/src/main/scripts/jbang.ps1 @@ -146,7 +146,8 @@ if (-not $binaryPath -and -not $jarPath) { } else { $jburl="https://github.com/jbangdev/jbang/releases/download/v$env:JBANG_DOWNLOAD_VERSION/jbang.zip"; } - [Console]::Error.WriteLine("Downloading JBang $env:JBANG_DOWNLOAD_VERSION...") + $dlVersion = if ($env:JBANG_DOWNLOAD_VERSION) { $env:JBANG_DOWNLOAD_VERSION } else { 'latest' } + [Console]::Error.WriteLine("Downloading JBang $dlVersion from $jburl...") $ok = Invoke-Download "$jburl" "$TDIR\urls\jbang.zip" if (-not ($ok)) { [Console]::Error.WriteLine("Error downloading JBang from $jburl to $TDIR\urls\jbang.zip") From 7defd27303828a810881f3dc38e12ffd6fee7636 Mon Sep 17 00:00:00 2001 From: Max Rydahl Andersen Date: Sat, 23 May 2026 09:00:25 +0200 Subject: [PATCH 3/4] feat: add JBANG_DOWNLOAD_BASEURL for script testing and mirrors Add JBANG_DOWNLOAD_BASEURL env var to bash and PowerShell scripts, allowing the download base URL to be overridden. This enables: - Pointing scripts at WireMock/local HTTP servers for integration tests - Corporate mirror support - file:// URL testing Defaults to https://github.com/jbangdev/jbang/releases when unset. JBANG_DOWNLOAD_URL (full URL override) still takes precedence. --- src/main/scripts/jbang | 14 +++++++++++--- src/main/scripts/jbang.ps1 | 14 +++++++++++--- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/main/scripts/jbang b/src/main/scripts/jbang index 83545d71f..a87071693 100755 --- a/src/main/scripts/jbang +++ b/src/main/scripts/jbang @@ -9,6 +9,11 @@ # The Java version to install when it's not installed on the system yet javaVersion=${JBANG_DEFAULT_JAVA_VERSION:-17} +# Base URL for downloading JBang releases. +# Override for testing or corporate mirrors. +# Example: JBANG_DOWNLOAD_BASEURL=http://localhost:18080 +jbangDownloadBaseUrl=${JBANG_DOWNLOAD_BASEURL:-https://github.com/jbangdev/jbang/releases} + # Number of retry attempts for downloads (curl/wget) downloadRetry=${JBANG_DOWNLOAD_RETRY:-5} downloadRetryDelay=${JBANG_DOWNLOAD_RETRY_DELAY:-0} @@ -48,7 +53,10 @@ download() { # Exponential backoff: 1, 2, 4, 8, ... sleepSeconds=$((1 << (attempt - 1))) fi - echo "Download attempt $attempt/$maxAttempts failed, retrying in $sleepSeconds second(s)..." 1>&2 + if [ $attempt -eq 1 ]; then + echo "Download failed. Retrying... (JBANG_DOWNLOAD_RETRY=0 to disable)" 1>&2 + fi + echo "Download $attempt/$maxAttempts failed. Retry in $sleepSeconds second(s)..." 1>&2 sleep $sleepSeconds done } @@ -274,9 +282,9 @@ if [[ -z "$binaryPath" && -z "$jarPath" ]]; then if [ -n "$JBANG_DOWNLOAD_URL" ]; then jburl="$JBANG_DOWNLOAD_URL"; elif [ -z "$JBANG_DOWNLOAD_VERSION" ]; then - jburl="https://github.com/jbangdev/jbang/releases/latest/download/jbang.tar"; + jburl="${jbangDownloadBaseUrl}/latest/download/jbang.tar"; else - jburl="https://github.com/jbangdev/jbang/releases/download/v$JBANG_DOWNLOAD_VERSION/jbang.tar"; + jburl="${jbangDownloadBaseUrl}/download/v$JBANG_DOWNLOAD_VERSION/jbang.tar"; fi echo "Downloading JBang ${JBANG_DOWNLOAD_VERSION:-latest} from $jburl..." 1>&2 download "$jburl" "$TDIR/urls/jbang.tar" diff --git a/src/main/scripts/jbang.ps1 b/src/main/scripts/jbang.ps1 index bfd9c4ba9..61390aadc 100644 --- a/src/main/scripts/jbang.ps1 +++ b/src/main/scripts/jbang.ps1 @@ -60,6 +60,11 @@ if (-not (Test-Path env:JBANG_DIR)) { $JBDIR="$env:userprofile\.jbang" } else { if (-not (Test-Path env:JBANG_CACHE_DIR)) { $TDIR="$JBDIR\cache" } else { $TDIR=$env:JBANG_CACHE_DIR } if (-not (Test-Path env:JBANG_USE_NATIVE)) { $env:JBANG_USE_NATIVE="false" } +# Base URL for downloading JBang releases. +# Override for testing or corporate mirrors. +# Example: $env:JBANG_DOWNLOAD_BASEURL='http://localhost:18080' +if (-not (Test-Path env:JBANG_DOWNLOAD_BASEURL)) { $jbangDownloadBaseUrl='https://github.com/jbangdev/jbang/releases' } else { $jbangDownloadBaseUrl=$env:JBANG_DOWNLOAD_BASEURL } + # Number of retry attempts for downloads if (-not (Test-Path env:JBANG_DOWNLOAD_RETRY)) { $downloadRetry=5 } else { $downloadRetry=[int]$env:JBANG_DOWNLOAD_RETRY } if (-not (Test-Path env:JBANG_DOWNLOAD_RETRY_DELAY)) { $downloadRetryDelay=0 } else { $downloadRetryDelay=[int]$env:JBANG_DOWNLOAD_RETRY_DELAY } @@ -82,7 +87,10 @@ function Invoke-Download { # Exponential backoff: 1, 2, 4, 8, ... $sleepSeconds = [Math]::Pow(2, $attempt - 1) } - [Console]::Error.WriteLine("Download attempt $attempt/$($downloadRetry + 1) failed, retrying in $sleepSeconds second(s)...") + if ($attempt -eq 1) { + [Console]::Error.WriteLine("Download failed. Retrying... (JBANG_DOWNLOAD_RETRY=0 to disable)") + } + [Console]::Error.WriteLine("Download $attempt/$($downloadRetry + 1) failed. Retry in $sleepSeconds second(s)...") Start-Sleep -Seconds $sleepSeconds } } @@ -142,9 +150,9 @@ if (-not $binaryPath -and -not $jarPath) { if (Test-Path env:JBANG_DOWNLOAD_URL) { $jburl=$env:JBANG_DOWNLOAD_URL } elseif (-not (Test-Path env:JBANG_DOWNLOAD_VERSION)) { - $jburl="https://github.com/jbangdev/jbang/releases/latest/download/jbang.zip" + $jburl="$jbangDownloadBaseUrl/latest/download/jbang.zip" } else { - $jburl="https://github.com/jbangdev/jbang/releases/download/v$env:JBANG_DOWNLOAD_VERSION/jbang.zip"; + $jburl="$jbangDownloadBaseUrl/download/v$env:JBANG_DOWNLOAD_VERSION/jbang.zip"; } $dlVersion = if ($env:JBANG_DOWNLOAD_VERSION) { $env:JBANG_DOWNLOAD_VERSION } else { 'latest' } [Console]::Error.WriteLine("Downloading JBang $dlVersion from $jburl...") From c2ca48a40bc37e7dc18a360e3c3717f5278af0f2 Mon Sep 17 00:00:00 2001 From: Max Rydahl Andersen Date: Sat, 23 May 2026 09:13:59 +0200 Subject: [PATCH 4/4] docs: update AGENTS.md env var guideline to be pragmatic Only require defaults in scripts that actually use the variable, not blindly in all three. jbang.cmd delegates downloads to jbang.ps1, so download-related env vars don't need defaults there. --- AGENTS.md | 2 +- src/main/scripts/jbang | 4 ++-- src/main/scripts/jbang.ps1 | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 5c724c0e7..6ecd3740b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -20,7 +20,7 @@ - Logging/output: use `dev.jbang.util.Util` helpers (e.g., `infoMsg`, `verboseMsg`). - Commits: use conventional/semantic format — `feat:`, `fix:`, `build:`, `docs:`, etc. PR titles follow the same convention. - Startup scripts live in `src/main/scripts/`: `jbang` (bash), `jbang.cmd` (CMD), `jbang.ps1` (PowerShell). The CMD script delegates downloads and JDK installs to `jbang.ps1`. Changes affecting downloads or bootstrap must be applied consistently across all three. Behavior (e.g., retry backoff) must be consistent across tools — watch for tool-specific quirks like `curl --retry-delay 0` meaning exponential backoff while `wget --waitretry=0` meaning no delay. -- Environment variables follow `JBANG_*` naming. New env vars must have defaults in all three scripts, be documented in the reference table in `installation.adoc` ("Startup Script Environment Variables"), and behave identically across platforms. +- Environment variables follow `JBANG_*` naming. New env vars must have defaults in each script that actually uses them (e.g., `jbang.cmd` delegates downloads to `jbang.ps1`, so download-related vars only need defaults in `jbang` and `jbang.ps1`). Document new vars in `installation.adoc` ("Startup Script Environment Variables") and ensure consistent behavior across platforms. - Documentation is AsciiDoc under `docs/modules/ROOT/pages/` (e.g., `installation.adoc`, `troubleshooting.adoc`). - Test infrastructure: `BaseTest` includes WireMock for HTTP mocking (records/replays requests). `BaseIT` provides `shell()` helpers for running CLI commands. Use `assumeTrue` for conditionally skipping tests (e.g., `assumeTrue(isCommandAvailable("bash"))` or `assumeTrue(isCommandAvailable("pwsh"))`). - Script tests should run the real scripts from `src/main/scripts/` — not synthetic copies or extracted functions. Use env var overrides (e.g., `JBANG_DOWNLOAD_URL`) to point real scripts at WireMock. diff --git a/src/main/scripts/jbang b/src/main/scripts/jbang index a87071693..12d66433f 100755 --- a/src/main/scripts/jbang +++ b/src/main/scripts/jbang @@ -53,10 +53,10 @@ download() { # Exponential backoff: 1, 2, 4, 8, ... sleepSeconds=$((1 << (attempt - 1))) fi + echo "Download $attempt/$maxAttempts failed. Retry in $sleepSeconds second(s)..." 1>&2 if [ $attempt -eq 1 ]; then - echo "Download failed. Retrying... (JBANG_DOWNLOAD_RETRY=0 to disable)" 1>&2 + echo "(Set JBANG_DOWNLOAD_RETRY=0 to disable retries)" 1>&2 fi - echo "Download $attempt/$maxAttempts failed. Retry in $sleepSeconds second(s)..." 1>&2 sleep $sleepSeconds done } diff --git a/src/main/scripts/jbang.ps1 b/src/main/scripts/jbang.ps1 index 61390aadc..d613ef9a6 100644 --- a/src/main/scripts/jbang.ps1 +++ b/src/main/scripts/jbang.ps1 @@ -87,10 +87,10 @@ function Invoke-Download { # Exponential backoff: 1, 2, 4, 8, ... $sleepSeconds = [Math]::Pow(2, $attempt - 1) } + [Console]::Error.WriteLine("Download $attempt/$($downloadRetry + 1) failed. Retry in $sleepSeconds second(s)...") if ($attempt -eq 1) { - [Console]::Error.WriteLine("Download failed. Retrying... (JBANG_DOWNLOAD_RETRY=0 to disable)") + [Console]::Error.WriteLine("(Set JBANG_DOWNLOAD_RETRY=0 to disable retries)") } - [Console]::Error.WriteLine("Download $attempt/$($downloadRetry + 1) failed. Retry in $sleepSeconds second(s)...") Start-Sleep -Seconds $sleepSeconds } }