From d8686a9a163511e17047a54d1a05a05617880ff1 Mon Sep 17 00:00:00 2001 From: Jim McKeeth Date: Tue, 23 Jun 2026 21:33:12 -0600 Subject: [PATCH 01/10] Add URL guessing for unlisted Delphi versions When a numeric version is requested that isn't in the explicit case list, probe candidate URLs based on Embarcadero's observed path patterns: - Tokyo/Rio (19.0/20.0): PAServer/Release{N}/ for updates - Sydney (21.0): {N}/PAServer/ for updates - Alexandria (22.0): {digits}1/ then {digits}/ for updates - Athens/Florence+: {digits}/ then {digits}/{digits}1/ Probes are done with a HEAD request (curl -sI); the first 200 response wins. Prints a warning so the user knows the URL was guessed rather than verified. Falls through to the existing error path if no candidate hits. Co-Authored-By: Claude Sonnet 4.6 --- scripts/SetupLinux4Delphi.sh | 107 ++++++++++++++++++++++++++++++++++- 1 file changed, 105 insertions(+), 2 deletions(-) diff --git a/scripts/SetupLinux4Delphi.sh b/scripts/SetupLinux4Delphi.sh index b2a0376..2157b97 100644 --- a/scripts/SetupLinux4Delphi.sh +++ b/scripts/SetupLinux4Delphi.sh @@ -159,9 +159,112 @@ case "$PARAM" in ;; esac +# Attempt to locate a PAServer URL for versions not explicitly listed. +# Probes candidate URLs derived from observed Embarcadero URL patterns. +try_guess_paserver_url() { + local input="$1" + local base="https://altd.embarcadero.com/releases/studio" + local major minor patch has_patch compiler release product digits + + if [[ "$input" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then + major="${BASH_REMATCH[1]}"; minor="${BASH_REMATCH[2]}"; patch="${BASH_REMATCH[3]}"; has_patch=1 + elif [[ "$input" =~ ^([0-9]+)\.([0-9]+)$ ]]; then + major="${BASH_REMATCH[1]}"; minor="${BASH_REMATCH[2]}"; patch="0"; has_patch=0 + else + return 1 + fi + + # Map product major version to internal compiler version and release name. + # Compiler numbers jumped from 23.0 (Athens/12.x) to 37.0 (Florence/13.x); + # future releases cannot be reliably predicted beyond 13.x. + case "$major" in + 10) + case "$minor" in + 2) compiler="19.0"; release="Tokyo" ;; + 3) compiler="20.0"; release="Rio" ;; + 4) compiler="21.0"; release="Sydney" ;; + *) return 1 ;; + esac + product="${major}.${minor}.${patch}" + digits="${major}${minor}${patch}" + ;; + 11) compiler="22.0"; release="Alexandria"; product="${major}.${minor}"; digits="${major}${minor}" ;; + 12) compiler="23.0"; release="Athens"; product="${major}.${minor}"; digits="${major}${minor}" ;; + 13) compiler="37.0"; release="Florence"; product="${major}.${minor}"; digits="${major}${minor}" ;; + *) return 1 ;; + esac + + # Build ordered candidate list based on the URL pattern for each era. + local candidates=() + case "$compiler" in + "19.0"|"20.0") + # Tokyo/Rio: updates use PAServer/Release{N}/ + if [ "$patch" -eq 0 ]; then + candidates+=("$base/$compiler/PAServer/LinuxPAServer${compiler}.tar.gz") + else + candidates+=( + "$base/$compiler/PAServer/Release${patch}/LinuxPAServer${compiler}.tar.gz" + "$base/$compiler/PAServer/LinuxPAServer${compiler}.tar.gz" + ) + fi + ;; + "21.0") + # Sydney: updates use {N}/PAServer/ + if [ "$patch" -eq 0 ]; then + candidates+=("$base/$compiler/PAServer/LinuxPAServer${compiler}.tar.gz") + else + candidates+=( + "$base/$compiler/${patch}/PAServer/LinuxPAServer${compiler}.tar.gz" + "$base/$compiler/PAServer/LinuxPAServer${compiler}.tar.gz" + ) + fi + ;; + "22.0") + # Alexandria: base release has no subdir; updates use {digits}[1]/ + if [ "$minor" -eq 0 ]; then + candidates+=("$base/$compiler/LinuxPAServer${compiler}.tar.gz") + else + candidates+=( + "$base/$compiler/${digits}1/LinuxPAServer${compiler}.tar.gz" + "$base/$compiler/${digits}/LinuxPAServer${compiler}.tar.gz" + ) + fi + ;; + *) + # Athens/Florence and future: flat {digits}/ or nested {digits}/{digits}1/ + candidates+=( + "$base/$compiler/${digits}/LinuxPAServer${compiler}.tar.gz" + "$base/$compiler/${digits}/${digits}1/LinuxPAServer${compiler}.tar.gz" + ) + ;; + esac + + echo "Version '$input' is not explicitly listed. Probing for PAServer..." + local url status + for url in "${candidates[@]}"; do + printf " Trying: %s\n" "$url" + status=$(curl -sI --max-time 10 "$url" 2>/dev/null | awk 'NR==1{print $2}') + if [ "$status" = "200" ]; then + PASERVER_URL="$url" + COMPILER="$compiler" + RELEASE="$release" + PRODUCT="$product" + echo " Found!" + echo "WARNING: Using a guessed URL — verify this PAServer matches your IDE version." + return 0 + fi + done + + echo "Could not locate a PAServer for '$input'. Check https://altd.embarcadero.com/releases/studio/ manually." + return 1 +} + if [ -z "$PASERVER_URL" ]; then - echo "Unknown version: $PARAM" - exit 1 + if ! try_guess_paserver_url "$PARAM"; then + echo "Unknown version: $PARAM" + echo "Run with 'help' to see supported versions." + exit 1 + fi fi ARCHIVE="${PASERVER_URL##*/}" From 608e058fc59410f07e1d860b1842954f289f091c Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 22 Sep 2026 02:02:39 +0000 Subject: [PATCH 02/10] Add explicit URL for Delphi 13.2 Florence, promote to default Delphi 13.2 has been released with PAServer at https://altd.embarcadero.com/releases/studio/37.0/132/LinuxPAServer37.0.tar.gz which matches the flat {digits}/ pattern the URL-guessing logic already predicts for Athens/Florence-era releases. Add it as an explicit case (following the existing convention of moving the bare compiler alias to the newest point release) so it no longer relies on guessing, and shift 13.1 to its own explicit entry. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01Q3a4oiwydRpUbFEJaxbCQU --- README.md | 5 +++-- scripts/SetupLinux4Delphi.sh | 11 +++++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 1108fb8..2486387 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,8 @@ Usage: `sudo SetupLinux4Delphi.sh [version] [pkgmgr]` Where [version] is one of the following: -* Florence 13.1 = `Florence`, `37.0`, `13.1` or *blank* +* Florence 13.2 = `Florence`, `37.0`, `13.2` or *blank* +* Florence 13.1 = `13.1` * Florence 13.0 = `13.0` * Athens 12.3 = `Athens`, `23.0`, `12.3`, or `12` * Athens 12.2 = `12.2` @@ -111,7 +112,7 @@ The installation locations are as follows: * `SCRIPT_PATH="/usr/local/bin/pa$PRODUCT.sh"` * `SCRATCH_DIR="$REAL_HOME/.PAServer/$PRODUCT-scratch"` -Where `$PRODUCT` is 13.1, 13.0, 12.2, etc. So you launch the latest *Florence* PAServer with `pa13.1.sh`. +Where `$PRODUCT` is 13.2, 13.1, 12.2, etc. So you launch the latest *Florence* PAServer with `pa13.2.sh`. ## More information diff --git a/scripts/SetupLinux4Delphi.sh b/scripts/SetupLinux4Delphi.sh index 335c173..7eda982 100644 --- a/scripts/SetupLinux4Delphi.sh +++ b/scripts/SetupLinux4Delphi.sh @@ -52,7 +52,8 @@ while [[ $# -gt 0 ]]; do echo " manager = apt, pacman, dnf, or yum (force specific package manager)" echo "" echo "Where [version] is one of the following:" - echo " 37.0, 13.1 = Florence 13.1 [DEFAULT]" + echo " 37.0, 13.2 = Florence 13.2 [DEFAULT]" + echo " 13.1 = Florence 13.1" echo " 13.0 = Florence 13.0" echo " 23.0, 12.3, 12 = Athens 12.3" echo " 12.2 = Athens 12.2" @@ -82,7 +83,13 @@ done case "$PARAM" in # Florence - "37.0"|"13.1"|"florence") + "37.0"|"13.2"|"florence") + COMPILER="37.0" + PRODUCT="13.2" + RELEASE="Florence" + PASERVER_URL="https://altd.embarcadero.com/releases/studio/37.0/132/LinuxPAServer37.0.tar.gz" + ;; + "13.1") COMPILER="37.0" PRODUCT="13.1" RELEASE="Florence" From 82087aa68ff151e60687dbbe096e1cd3839dff42 Mon Sep 17 00:00:00 2001 From: Jim McKeeth Date: Mon, 21 Sep 2026 21:33:00 -0600 Subject: [PATCH 03/10] CI: auto-derive test matrix from the 5 newest explicit versions Copilot's review on PR #12 flagged that CI never validated the new 13.2 default because commit_test.yml hardcoded 13.1. Instead of hand-maintaining that list, add a discover_versions job that greps the script's own PRODUCT="..." entries, sorts them, and takes the newest 5; ubuntu_test and rhel_test now run as a matrix over that list. Adding a new explicit version to SetupLinux4Delphi.sh picks it up in CI automatically. --- .github/workflows/commit_test.yml | 39 ++++++++++++++++++++++++++----- AGENTS.md | 7 +++--- 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/.github/workflows/commit_test.yml b/.github/workflows/commit_test.yml index e04759f..a76f9a3 100644 --- a/.github/workflows/commit_test.yml +++ b/.github/workflows/commit_test.yml @@ -18,9 +18,31 @@ jobs: scandir: './scripts' severity: error + discover_versions: + name: Discover versions to test + runs-on: ubuntu-latest + outputs: + versions: ${{ steps.list.outputs.versions }} + steps: + - uses: actions/checkout@v5 + - name: List the 5 newest explicitly supported versions + id: list + run: | + set -e + versions=$(grep -oP 'PRODUCT="\K[0-9]+\.[0-9]+(\.[0-9]+)?' scripts/SetupLinux4Delphi.sh \ + | sort -Vu | tail -5) + json=$(printf '%s\n' "$versions" | jq -R . | jq -sc .) + echo "versions=$json" >> "$GITHUB_OUTPUT" + echo "Testing versions: $json" + ubuntu_test: - name: Ubuntu 26.04 + name: Ubuntu 26.04 (${{ matrix.version }}) + needs: discover_versions runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + version: ${{ fromJson(needs.discover_versions.outputs.versions) }} steps: - uses: actions/checkout@v5 - name: Run Setup and Verify PAServer in Ubuntu 26.04 @@ -32,25 +54,30 @@ jobs: bash -c " set -e chmod +x /workspace/scripts/SetupLinux4Delphi.sh - /workspace/scripts/SetupLinux4Delphi.sh 13.1 - /usr/local/bin/pa13.1.sh & + /workspace/scripts/SetupLinux4Delphi.sh ${{ matrix.version }} + /usr/local/bin/pa${{ matrix.version }}.sh & sleep 15 pgrep paserver " rhel_test: - name: RHEL 10 + name: RHEL 10 (${{ matrix.version }}) + needs: discover_versions runs-on: ubuntu-latest container: image: redhat/ubi10:latest + strategy: + fail-fast: false + matrix: + version: ${{ fromJson(needs.discover_versions.outputs.versions) }} steps: - uses: actions/checkout@v5 - name: Run Setup run: | chmod +x ./scripts/SetupLinux4Delphi.sh - ./scripts/SetupLinux4Delphi.sh 13.1 + ./scripts/SetupLinux4Delphi.sh ${{ matrix.version }} - name: Verify PAServer run: | - /usr/local/bin/pa13.1.sh & + /usr/local/bin/pa${{ matrix.version }}.sh & sleep 15 pgrep paserver diff --git a/AGENTS.md b/AGENTS.md index 5e7f636..6601550 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -60,7 +60,7 @@ Update all of: 1. `scripts/SetupLinux4Delphi.sh` alias map and `PASERVER_URL` entries. 2. Script help text (`--help`). 3. `README.md` version matrix/alias descriptions. -4. `.github/workflows/commit_test.yml` validation target(s) when coverage changes. +4. No CI edit needed for routine version additions: `.github/workflows/commit_test.yml`'s `discover_versions` job derives its test matrix from the `PRODUCT="..."` entries in the script (newest 5 by version sort). Only touch the workflow file itself if the *mechanism* needs to change (e.g. how many versions are covered, or the discovery logic). Keep compiler aliases (`37.0`, `23.0`, etc.) mapping to latest point release, while explicit product aliases (`13.0`, `12.2`) stay exact. @@ -83,8 +83,9 @@ Current automated checks are shell/install focused: - `shellcheck scripts/SetupLinux4Delphi.sh` - `bash -n scripts/SetupLinux4Delphi.sh` - CI in `.github/workflows/commit_test.yml`: - - Ubuntu 26.04 installs 13.1, starts `pa13.1.sh`, verifies `pgrep paserver` - - RHEL 10 does the same + - `discover_versions` extracts the 5 newest `PRODUCT="..."` versions directly from the script (no hardcoded version list to maintain). + - Ubuntu 26.04 and RHEL 10 each run as a matrix over those versions: install, start `pa.sh`, verify `pgrep paserver`. + - The URL-guessing path (`try_guess_paserver_url`, for versions not explicitly listed) is not covered by this matrix and has no automated test yet. For pacommander router work, testing is mandatory before calling behavior stable. Validate at least: 1. **Version identification**: incoming connection metadata is parsed into the correct target PAServer version. From 3fd85777f8dca1fd5c3d50f3cd79440a1f1f5579 Mon Sep 17 00:00:00 2001 From: Jim McKeeth Date: Mon, 21 Sep 2026 21:37:53 -0600 Subject: [PATCH 04/10] Extend URL-guessing to 14.x/15.x as a speculative compiler mapping Before Florence's release, the compiler-number-to-product mapping had tracked 1:1 (19->20->21->22->23 for 10.2 through 12.x) until it jumped +14 to 37.0 for 13.x. Since 14.0 is the plausible next release and having to patch this table the moment it ships defeats the purpose of guessing ahead of time, extend the mapping with 14.0->38.0 and 15.0->39.0, assuming the pre-jump +1-per-major pattern resumes. This is explicitly labeled as unverified: probing still gates whether the guess is actually used, and an extra warning fires whenever the release codename is unknown, prompting a DocWiki double-check. --- scripts/SetupLinux4Delphi.sh | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/scripts/SetupLinux4Delphi.sh b/scripts/SetupLinux4Delphi.sh index 7eda982..90097bf 100644 --- a/scripts/SetupLinux4Delphi.sh +++ b/scripts/SetupLinux4Delphi.sh @@ -226,8 +226,12 @@ try_guess_paserver_url() { fi # Map product major version to internal compiler version and release name. - # Compiler numbers jumped from 23.0 (Athens/12.x) to 37.0 (Florence/13.x); - # future releases cannot be reliably predicted beyond 13.x. + # Compiler numbers tracked the product major 1:1 through 23.0 (Athens/12.x), + # then jumped to 37.0 for Florence/13.x. 14.x/15.x aren't released yet, so + # 38.0/39.0 below are an unverified extrapolation (assuming the pre-jump + # +1-per-major pattern resumed) rather than a confirmed mapping — probing + # still decides whether a guess is actually used, and the caller prints an + # extra warning whenever `release` is empty. case "$major" in 10) case "$minor" in @@ -242,6 +246,8 @@ try_guess_paserver_url() { 11) compiler="22.0"; release="Alexandria"; product="${major}.${minor}"; digits="${major}${minor}" ;; 12) compiler="23.0"; release="Athens"; product="${major}.${minor}"; digits="${major}${minor}" ;; 13) compiler="37.0"; release="Florence"; product="${major}.${minor}"; digits="${major}${minor}" ;; + 14) compiler="38.0"; release=""; product="${major}.${minor}"; digits="${major}${minor}" ;; + 15) compiler="39.0"; release=""; product="${major}.${minor}"; digits="${major}${minor}" ;; *) return 1 ;; esac @@ -298,10 +304,14 @@ try_guess_paserver_url() { if [ "$status" = "200" ]; then PASERVER_URL="$url" COMPILER="$compiler" - RELEASE="$release" + RELEASE="${release:-Unreleased}" PRODUCT="$product" echo " Found!" echo "WARNING: Using a guessed URL — verify this PAServer matches your IDE version." + if [ -z "$release" ]; then + echo "WARNING: Compiler $compiler for $product is an unverified extrapolation," \ + "not a confirmed mapping — double-check against the DocWiki before relying on it." + fi return 0 fi done From 7a86c30631f5dcb05660f19ab14b0dc575277e15 Mon Sep 17 00:00:00 2001 From: Jim McKeeth Date: Mon, 21 Sep 2026 21:52:31 -0600 Subject: [PATCH 05/10] CI: replace 5-version matrix with a leaner probe+latest strategy Running full docker installs for the 5 newest versions on both OSes was overkill for what's really being validated. Replace it with: - probe_urls: a cheap HEAD check against every hardcoded PASERVER_URL, independent of the install jobs, so a stale link is caught without needing a full install to notice. - plan: installs/starts only the single newest hardcoded version, and additionally probes try_guess_paserver_url for the next minor/major version; if guessing finds a real not-yet-listed URL, that version is added to the test matrix too and flagged via a workflow warning so it gets added explicitly. This drops the full-install job count from 5x2=10 down to typically 1-2 per OS, while still catching both dead links across the whole table and a newly-released version the script hasn't been told about yet. --- .github/workflows/commit_test.yml | 78 +++++++++++++++++++++++++------ AGENTS.md | 9 ++-- 2 files changed, 68 insertions(+), 19 deletions(-) diff --git a/.github/workflows/commit_test.yml b/.github/workflows/commit_test.yml index a76f9a3..5a54017 100644 --- a/.github/workflows/commit_test.yml +++ b/.github/workflows/commit_test.yml @@ -18,31 +18,79 @@ jobs: scandir: './scripts' severity: error - discover_versions: - name: Discover versions to test + probe_urls: + name: Probe all hardcoded PAServer URLs + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - name: Check every explicit PASERVER_URL is still reachable + run: | + set +e + fail=0 + while IFS= read -r url; do + status=$(curl -sI --max-time 15 "$url" 2>/dev/null | awk 'NR==1{print $2}' | tr -d '\r') + if [ "$status" = "200" ]; then + echo "OK ($status): $url" + else + echo "::error::Broken PASERVER_URL ($status): $url" + fail=1 + fi + done < <(grep -oP 'PASERVER_URL="\K[^"]+' scripts/SetupLinux4Delphi.sh) + exit "$fail" + + plan: + name: Plan install tests runs-on: ubuntu-latest outputs: - versions: ${{ steps.list.outputs.versions }} + versions: ${{ steps.plan.outputs.versions }} steps: - uses: actions/checkout@v5 - - name: List the 5 newest explicitly supported versions - id: list + - name: Determine latest hardcoded version, probe for a newer guessed one + id: plan run: | - set -e - versions=$(grep -oP 'PRODUCT="\K[0-9]+\.[0-9]+(\.[0-9]+)?' scripts/SetupLinux4Delphi.sh \ - | sort -Vu | tail -5) - json=$(printf '%s\n' "$versions" | jq -R . | jq -sc .) - echo "versions=$json" >> "$GITHUB_OUTPUT" - echo "Testing versions: $json" + set +e + latest=$(grep -oP 'PRODUCT="\K[0-9]+\.[0-9]+(\.[0-9]+)?' scripts/SetupLinux4Delphi.sh \ + | sort -Vu | tail -1) + echo "Latest hardcoded version: $latest" + + # Pull in just the guessing function; the full script requires root + # before it's even defined, and we don't want to install anything here. + source <(sed -n '/^try_guess_paserver_url()/,/^}/p' scripts/SetupLinux4Delphi.sh) + + major="${latest%%.*}" + rest="${latest#*.}" + minor="${rest%%.*}" + candidates=("${major}.$((minor + 1))" "$((major + 1)).0") + + guessed="" + for c in "${candidates[@]}"; do + if try_guess_paserver_url "$c"; then + echo "::warning::URL guessing found a newer, not-yet-listed PAServer version ($c -> $PASERVER_URL). Consider adding it to scripts/SetupLinux4Delphi.sh." + { + echo "### Newer version found by guessing" + echo "- **$c** -> \`$PASERVER_URL\`" + } >> "$GITHUB_STEP_SUMMARY" + guessed="$c" + break + fi + done + + if [ -n "$guessed" ]; then + versions=$(printf '%s\n' "$latest" "$guessed" | jq -R . | jq -sc .) + else + versions=$(printf '%s\n' "$latest" | jq -R . | jq -sc .) + fi + echo "versions=$versions" >> "$GITHUB_OUTPUT" + echo "Testing versions: $versions" ubuntu_test: name: Ubuntu 26.04 (${{ matrix.version }}) - needs: discover_versions + needs: plan runs-on: ubuntu-latest strategy: fail-fast: false matrix: - version: ${{ fromJson(needs.discover_versions.outputs.versions) }} + version: ${{ fromJson(needs.plan.outputs.versions) }} steps: - uses: actions/checkout@v5 - name: Run Setup and Verify PAServer in Ubuntu 26.04 @@ -62,14 +110,14 @@ jobs: rhel_test: name: RHEL 10 (${{ matrix.version }}) - needs: discover_versions + needs: plan runs-on: ubuntu-latest container: image: redhat/ubi10:latest strategy: fail-fast: false matrix: - version: ${{ fromJson(needs.discover_versions.outputs.versions) }} + version: ${{ fromJson(needs.plan.outputs.versions) }} steps: - uses: actions/checkout@v5 - name: Run Setup diff --git a/AGENTS.md b/AGENTS.md index 6601550..9ffff02 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -60,7 +60,7 @@ Update all of: 1. `scripts/SetupLinux4Delphi.sh` alias map and `PASERVER_URL` entries. 2. Script help text (`--help`). 3. `README.md` version matrix/alias descriptions. -4. No CI edit needed for routine version additions: `.github/workflows/commit_test.yml`'s `discover_versions` job derives its test matrix from the `PRODUCT="..."` entries in the script (newest 5 by version sort). Only touch the workflow file itself if the *mechanism* needs to change (e.g. how many versions are covered, or the discovery logic). +4. No CI edit needed for routine version additions: `.github/workflows/commit_test.yml`'s `probe_urls` job picks up every new `PASERVER_URL` automatically, and `plan` always targets whatever is newest. Only touch the workflow file itself if the *mechanism* needs to change (e.g. how many versions get full install tests, or the guess-probing candidates). Keep compiler aliases (`37.0`, `23.0`, etc.) mapping to latest point release, while explicit product aliases (`13.0`, `12.2`) stay exact. @@ -83,9 +83,10 @@ Current automated checks are shell/install focused: - `shellcheck scripts/SetupLinux4Delphi.sh` - `bash -n scripts/SetupLinux4Delphi.sh` - CI in `.github/workflows/commit_test.yml`: - - `discover_versions` extracts the 5 newest `PRODUCT="..."` versions directly from the script (no hardcoded version list to maintain). - - Ubuntu 26.04 and RHEL 10 each run as a matrix over those versions: install, start `pa.sh`, verify `pgrep paserver`. - - The URL-guessing path (`try_guess_paserver_url`, for versions not explicitly listed) is not covered by this matrix and has no automated test yet. + - `probe_urls` does a lightweight `HEAD` check against every hardcoded `PASERVER_URL` and fails if any have gone dead — cheap enough to run for the whole table, independently of the install jobs below. + - `plan` finds the newest `PRODUCT="..."` version and also probes `try_guess_paserver_url` for the next minor/major version; if guessing finds a real, not-yet-listed URL, it's added to the test list and flagged via a workflow warning + summary (a prompt to add it explicitly, not an assertion that it's already covered). + - Ubuntu 26.04 and RHEL 10 each run as a matrix over `plan`'s version list (normally just the latest version; two when guessing finds something newer): install, start `pa.sh`, verify `pgrep paserver`. + - Older explicit versions are covered only by the `probe_urls` reachability check, not a full install — full install/start testing is deliberately limited to the newest version(s) to keep CI light. For pacommander router work, testing is mandatory before calling behavior stable. Validate at least: 1. **Version identification**: incoming connection metadata is parsed into the correct target PAServer version. From 01252078d5ae2a8a7794a903c7f7f7dfc6a682d1 Mon Sep 17 00:00:00 2001 From: Jim McKeeth Date: Mon, 21 Sep 2026 21:58:29 -0600 Subject: [PATCH 06/10] Fix URL-guess review findings: unused var, silent patch truncation Addresses two Copilot findings on PR #12: - has_patch was set but never read (ShellCheck SC2034, fails the severity:error job). Now it gates a real check instead. - For eras 11-15 (Alexandria/Athens/Florence/guessed future), a 3-component input like "13.2.1" was silently probed as "13.2", since patch was discarded when building product/digits. These eras only ever ship as major.minor, unlike 10.x's Release1/2/3, so a 3-component input for them is rejected outright instead of quietly resolving to a different version than what was typed. --- scripts/SetupLinux4Delphi.sh | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/scripts/SetupLinux4Delphi.sh b/scripts/SetupLinux4Delphi.sh index 90097bf..f3515c0 100644 --- a/scripts/SetupLinux4Delphi.sh +++ b/scripts/SetupLinux4Delphi.sh @@ -243,11 +243,23 @@ try_guess_paserver_url() { product="${major}.${minor}.${patch}" digits="${major}${minor}${patch}" ;; - 11) compiler="22.0"; release="Alexandria"; product="${major}.${minor}"; digits="${major}${minor}" ;; - 12) compiler="23.0"; release="Athens"; product="${major}.${minor}"; digits="${major}${minor}" ;; - 13) compiler="37.0"; release="Florence"; product="${major}.${minor}"; digits="${major}${minor}" ;; - 14) compiler="38.0"; release=""; product="${major}.${minor}"; digits="${major}${minor}" ;; - 15) compiler="39.0"; release=""; product="${major}.${minor}"; digits="${major}${minor}" ;; + 11|12|13|14|15) + # These eras are only ever named major.minor (no third-level patch + # release like 10.x's Release1/2/3); reject a 3-component input + # instead of silently ignoring the patch and guessing major.minor. + if [ "$has_patch" -eq 1 ]; then + return 1 + fi + case "$major" in + 11) compiler="22.0"; release="Alexandria" ;; + 12) compiler="23.0"; release="Athens" ;; + 13) compiler="37.0"; release="Florence" ;; + 14) compiler="38.0"; release="" ;; + 15) compiler="39.0"; release="" ;; + esac + product="${major}.${minor}" + digits="${major}${minor}" + ;; *) return 1 ;; esac From 9b43fbd052f1642dffc473e05a411d1fad6ef08c Mon Sep 17 00:00:00 2001 From: Jim McKeeth Date: Mon, 21 Sep 2026 21:59:46 -0600 Subject: [PATCH 07/10] Guess probe: fall back to wget when curl isn't installed yet Third Copilot finding on PR #12 (pre-existing, not introduced by this branch): try_guess_paserver_url runs before the package-manager step installs anything, so hardcoding curl meant a host with only wget (or neither, yet) would hit "curl: command not found" under set -e and abort the whole script instead of falling through to "Unknown version". Mirror download_file's wget-then-curl fallback here too. --- scripts/SetupLinux4Delphi.sh | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/scripts/SetupLinux4Delphi.sh b/scripts/SetupLinux4Delphi.sh index f3515c0..4e0a012 100644 --- a/scripts/SetupLinux4Delphi.sh +++ b/scripts/SetupLinux4Delphi.sh @@ -312,7 +312,16 @@ try_guess_paserver_url() { local url status for url in "${candidates[@]}"; do printf " Trying: %s\n" "$url" - status=$(curl -sI --max-time 10 "$url" 2>/dev/null | awk 'NR==1{print $2}') + # This runs before prerequisites are installed, so only wget or curl + # (whichever, if either, is already present) can be relied on here. + if command -v wget >/dev/null 2>&1; then + status=$(wget -q --spider --server-response --timeout=10 "$url" 2>&1 \ + | awk '/^ *HTTP\// {code=$2} END {print code}') + elif command -v curl >/dev/null 2>&1; then + status=$(curl -sI --max-time 10 "$url" 2>/dev/null | awk 'NR==1{print $2}' | tr -d '\r') + else + status="" + fi if [ "$status" = "200" ]; then PASERVER_URL="$url" COMPILER="$compiler" From d61ac62bbe2b01b94a27914cdefac1bead574a35 Mon Sep 17 00:00:00 2001 From: Jim McKeeth Date: Mon, 21 Sep 2026 22:10:18 -0600 Subject: [PATCH 08/10] Fix probe_urls false positive on try_guess_paserver_url's $url assignment The URL-extraction regex matched any PASERVER_URL="..." assignment in the script, including the literal `PASERVER_URL="$url"` line inside try_guess_paserver_url()'s success path. That fed the literal string "$url" to curl as a 21st "hardcoded URL", which always failed and printed a useless "Broken PASERVER_URL (): $url" with no way to tell it wasn't a real link. Restrict the regex to actual http(s) literals, and also write failed URLs to the job summary so a real failure is easy to spot instead of only showing up as an inline ::error:: annotation. Co-Authored-By: Claude Sonnet 5 --- .github/workflows/commit_test.yml | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/.github/workflows/commit_test.yml b/.github/workflows/commit_test.yml index 5a54017..48f4b99 100644 --- a/.github/workflows/commit_test.yml +++ b/.github/workflows/commit_test.yml @@ -27,15 +27,24 @@ jobs: run: | set +e fail=0 + broken=() while IFS= read -r url; do status=$(curl -sI --max-time 15 "$url" 2>/dev/null | awk 'NR==1{print $2}' | tr -d '\r') if [ "$status" = "200" ]; then echo "OK ($status): $url" else - echo "::error::Broken PASERVER_URL ($status): $url" + echo "::error::Broken PASERVER_URL (status='$status'): $url" + broken+=("$url (status='$status')") fail=1 fi - done < <(grep -oP 'PASERVER_URL="\K[^"]+' scripts/SetupLinux4Delphi.sh) + done < <(grep -oP 'PASERVER_URL="\Khttps?://[^"]+' scripts/SetupLinux4Delphi.sh) + + if [ "$fail" -ne 0 ]; then + { + echo "### Broken PASERVER_URL(s)" + printf -- '- `%s`\n' "${broken[@]}" + } >> "$GITHUB_STEP_SUMMARY" + fi exit "$fail" plan: From b902e3c6ec01ed5854272a624ee067dcd74bbb40 Mon Sep 17 00:00:00 2001 From: Jim McKeeth Date: Mon, 21 Sep 2026 22:28:20 -0600 Subject: [PATCH 09/10] Fix wget probe false-negative and stale test-local.ps1 default version wget's -q flag suppressed --server-response output entirely, so the guess probe never saw an HTTP status line and always fell through (the curl branch was unreachable on hosts with wget, since it's checked first). Drop -q; the existing awk filter already ignores the non-HTTP-header noise this exposes. test-local.ps1 still defaulted to 13.1 and claimed to match the CI default, but CI now always targets whatever is newest (currently 13.2). Co-Authored-By: Claude Sonnet 5 --- scripts/SetupLinux4Delphi.sh | 2 +- test-local.ps1 | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/SetupLinux4Delphi.sh b/scripts/SetupLinux4Delphi.sh index 4e0a012..6846433 100644 --- a/scripts/SetupLinux4Delphi.sh +++ b/scripts/SetupLinux4Delphi.sh @@ -315,7 +315,7 @@ try_guess_paserver_url() { # This runs before prerequisites are installed, so only wget or curl # (whichever, if either, is already present) can be relied on here. if command -v wget >/dev/null 2>&1; then - status=$(wget -q --spider --server-response --timeout=10 "$url" 2>&1 \ + status=$(wget --spider --server-response --timeout=10 "$url" 2>&1 \ | awk '/^ *HTTP\// {code=$2} END {print code}') elif command -v curl >/dev/null 2>&1; then status=$(curl -sI --max-time 10 "$url" 2>/dev/null | awk 'NR==1{print $2}' | tr -d '\r') diff --git a/test-local.ps1 b/test-local.ps1 index cf84c34..328c6cd 100644 --- a/test-local.ps1 +++ b/test-local.ps1 @@ -9,13 +9,13 @@ # .\test-local.ps1 -Ubuntu # + Ubuntu 26.04 install test # .\test-local.ps1 -RHEL # + RHEL 10 install test # .\test-local.ps1 -All # + both install tests -# .\test-local.ps1 -Version 13.0 # Override PAServer version (default: 13.1) +# .\test-local.ps1 -Version 13.0 # Override PAServer version (default: 13.2) param( [switch]$Ubuntu, [switch]$RHEL, [switch]$All, - [string]$Version = "13.1" # Match CI default + [string]$Version = "13.2" # Match CI default ) $Root = $PSScriptRoot From 11bdbaa3c37509eb6277dea030b97a023c0d61cc Mon Sep 17 00:00:00 2001 From: Jim McKeeth Date: Mon, 21 Sep 2026 22:34:14 -0600 Subject: [PATCH 10/10] Derive "latest version" from an array instead of scattered hardcodes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The default PARAM alias, the help text's [DEFAULT] marker, and test-local.ps1's default -Version were three separate hardcoded literals that all had to be bumped together whenever a new version was promoted to default — exactly the kind of drift that got missed going from 13.1 to 13.2. Add KNOWN_VERSIONS (a plain array of the product versions already covered by case branches) and compute LATEST_VERSION from it via `sort -V | tail -1`. PARAM's default and the help text's [DEFAULT] marker (via a new version_tag helper) both derive from that instead of independent literals. test-local.ps1 can't read a bash array directly, so it mirrors what commit_test.yml's `plan` job already does: grep the script's own PRODUCT="..." lines via WSL and sort -V for the newest one, falling back to a literal only if WSL can't do the detection. Document the extra KNOWN_VERSIONS/version_tag step in AGENTS.md's version-mapping playbook so it isn't missed next time either. Co-Authored-By: Claude Sonnet 5 --- AGENTS.md | 7 ++-- scripts/SetupLinux4Delphi.sh | 64 ++++++++++++++++++++++++------------ test-local.ps1 | 18 +++++++--- 3 files changed, 61 insertions(+), 28 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 9ffff02..acaa9ba 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -58,9 +58,10 @@ Avoid split ownership of version mapping logic long-term. Target owner is pacomm ### A) Add or change PAServer version mapping Update all of: 1. `scripts/SetupLinux4Delphi.sh` alias map and `PASERVER_URL` entries. -2. Script help text (`--help`). -3. `README.md` version matrix/alias descriptions. -4. No CI edit needed for routine version additions: `.github/workflows/commit_test.yml`'s `probe_urls` job picks up every new `PASERVER_URL` automatically, and `plan` always targets whatever is newest. Only touch the workflow file itself if the *mechanism* needs to change (e.g. how many versions get full install tests, or the guess-probing candidates). +2. `scripts/SetupLinux4Delphi.sh`'s `KNOWN_VERSIONS` array — add the new product version string here too. This is the single source of truth `LATEST_VERSION` (`sort -V | tail -1` over it) is computed from, which in turn drives the default `PARAM` value and the help text's `[DEFAULT]` marker (via `version_tag`). Forgetting this step leaves the default pointed at an old version even though the case branch exists. +3. Script help text (`--help`) — add a line with a `$(version_tag X.Y)` call so the `[DEFAULT]` marker can land on it automatically once it's newest. +4. `README.md` version matrix/alias descriptions. +5. No CI edit needed for routine version additions: `.github/workflows/commit_test.yml`'s `probe_urls` job picks up every new `PASERVER_URL` automatically, and `plan` always targets whatever is newest (via its own independent `grep`/`sort -V` over `PRODUCT="..."` lines — it does not read `KNOWN_VERSIONS`). Only touch the workflow file itself if the *mechanism* needs to change (e.g. how many versions get full install tests, or the guess-probing candidates). Keep compiler aliases (`37.0`, `23.0`, etc.) mapping to latest point release, while explicit product aliases (`13.0`, `12.2`) stay exact. diff --git a/scripts/SetupLinux4Delphi.sh b/scripts/SetupLinux4Delphi.sh index 6846433..1ad81b8 100644 --- a/scripts/SetupLinux4Delphi.sh +++ b/scripts/SetupLinux4Delphi.sh @@ -21,8 +21,30 @@ if [[ $EUID -ne 0 ]]; then exit 1 fi +# Every explicit PAServer product version below needs a matching case branch +# further down; this array only tracks which one is newest, so the default +# alias and the help text's [DEFAULT] marker can't drift out of sync with +# each other (or get left pointing at an old version) the way separate +# hardcoded literals did. +KNOWN_VERSIONS=( + 10.2.0 10.2.3 + 10.3.0 10.3.1 10.3.2 10.3.3 + 10.4.0 10.4.1 10.4.2 + 11.0 11.1 11.2 11.3 + 12.0 12.1 12.2 12.3 + 13.0 13.1 13.2 +) +LATEST_VERSION="$(printf '%s\n' "${KNOWN_VERSIONS[@]}" | sort -V | tail -1)" + +# Prints " [DEFAULT]" when $1 is the newest entry in KNOWN_VERSIONS, for tagging help text. +version_tag() { + if [[ "$1" == "$LATEST_VERSION" ]]; then + echo " [DEFAULT]" + fi +} + # Parse arguments -PARAM="37.0" # Default version +PARAM="$LATEST_VERSION" # Default version PKG_OVERRIDE="" # Function to download files using whichever tool is available @@ -52,26 +74,26 @@ while [[ $# -gt 0 ]]; do echo " manager = apt, pacman, dnf, or yum (force specific package manager)" echo "" echo "Where [version] is one of the following:" - echo " 37.0, 13.2 = Florence 13.2 [DEFAULT]" - echo " 13.1 = Florence 13.1" - echo " 13.0 = Florence 13.0" - echo " 23.0, 12.3, 12 = Athens 12.3" - echo " 12.2 = Athens 12.2" - echo " 12.1 = Athens 12.1" - echo " 12.0 = Athens 12.0" - echo " 22.0, 11.3, 11 = Alexandria 11.3" - echo " 11.2 = Alexandria 11.2" - echo " 11.1 = Alexandria 11.1" - echo " 11.0 = Alexandria 11.0" - echo " 21.0, 10.4, 10.4.2 = Sydney 10.4.2" - echo " 10.4.1 = Sydney 10.4.1" - echo " 10.4.0 = Sydney 10.4.0" - echo " 20.0, 10.3, 10.3.3 = Rio 10.3.3" - echo " 10.3.2 = Rio 10.3.2" - echo " 10.3.1 = Rio 10.3.1" - echo " 10.3.0 = Rio 10.3.0" - echo " 19.0, 10.2, 10.2.3 = Tokyo 10.2.3" - echo " 10.2 = Tokyo 10.2.0" + echo " 37.0, 13.2 = Florence 13.2$(version_tag 13.2)" + echo " 13.1 = Florence 13.1$(version_tag 13.1)" + echo " 13.0 = Florence 13.0$(version_tag 13.0)" + echo " 23.0, 12.3, 12 = Athens 12.3$(version_tag 12.3)" + echo " 12.2 = Athens 12.2$(version_tag 12.2)" + echo " 12.1 = Athens 12.1$(version_tag 12.1)" + echo " 12.0 = Athens 12.0$(version_tag 12.0)" + echo " 22.0, 11.3, 11 = Alexandria 11.3$(version_tag 11.3)" + echo " 11.2 = Alexandria 11.2$(version_tag 11.2)" + echo " 11.1 = Alexandria 11.1$(version_tag 11.1)" + echo " 11.0 = Alexandria 11.0$(version_tag 11.0)" + echo " 21.0, 10.4, 10.4.2 = Sydney 10.4.2$(version_tag 10.4.2)" + echo " 10.4.1 = Sydney 10.4.1$(version_tag 10.4.1)" + echo " 10.4.0 = Sydney 10.4.0$(version_tag 10.4.0)" + echo " 20.0, 10.3, 10.3.3 = Rio 10.3.3$(version_tag 10.3.3)" + echo " 10.3.2 = Rio 10.3.2$(version_tag 10.3.2)" + echo " 10.3.1 = Rio 10.3.1$(version_tag 10.3.1)" + echo " 10.3.0 = Rio 10.3.0$(version_tag 10.3.0)" + echo " 19.0, 10.2, 10.2.3 = Tokyo 10.2.3$(version_tag 10.2.3)" + echo " 10.2 = Tokyo 10.2.0$(version_tag 10.2.0)" exit 0 ;; *) diff --git a/test-local.ps1 b/test-local.ps1 index 328c6cd..89bf99c 100644 --- a/test-local.ps1 +++ b/test-local.ps1 @@ -9,18 +9,30 @@ # .\test-local.ps1 -Ubuntu # + Ubuntu 26.04 install test # .\test-local.ps1 -RHEL # + RHEL 10 install test # .\test-local.ps1 -All # + both install tests -# .\test-local.ps1 -Version 13.0 # Override PAServer version (default: 13.2) +# .\test-local.ps1 -Version 13.0 # Override PAServer version (default: newest hardcoded version, auto-detected via WSL) param( [switch]$Ubuntu, [switch]$RHEL, [switch]$All, - [string]$Version = "13.2" # Match CI default + [string]$Version = "13.2" # Fallback only, used if WSL can't auto-detect the newest version below ) $Root = $PSScriptRoot $Failures = 0 $Skipped = 0 +$WslAvailable = $null -ne (Get-Command wsl -ErrorAction SilentlyContinue) + +# Mirror commit_test.yml's `plan` job: derive "latest" from the script's own +# PRODUCT="..." lines instead of a second hardcoded literal here, so this +# can't silently fall behind after a version bump. +if (-not $PSBoundParameters.ContainsKey('Version') -and $WslAvailable) { + $ScriptPathWsl = (wsl wslpath ($Root -replace "\\", "/")) + "/scripts/SetupLinux4Delphi.sh" + $Detected = (wsl bash -c "grep -oP 'PRODUCT=`"\K[0-9]+\.[0-9]+(\.[0-9]+)?' '$ScriptPathWsl' | sort -V | tail -1").Trim() + if ($Detected) { + $Version = $Detected + } +} function Write-Header([string]$Text) { Write-Host "" @@ -124,8 +136,6 @@ Invoke-Check "docker installed and daemon ready" { Set-CheckExitCode $script:DockerOk } -$WslAvailable = $null -ne (Get-Command wsl -ErrorAction SilentlyContinue) - # 1. bash -n syntax check (via WSL, no Docker needed) Write-Header "Syntax check (bash -n)"